diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json
index b949b13320..18477f9129 100644
--- a/data/mappings/info_config.json
+++ b/data/mappings/info_config.json
@@ -1,21 +1,46 @@
-# This file maps keys between `config.h` and `info.json`. It is used by QMK 
+# This file maps keys between `config.h` and `info.json`. It is used by QMK
 # to correctly and consistently map back and forth between the two systems.
 {
     # Format:
     # <config.h key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
-    # value_type: one of "array", "array.int", "int", "hex", "list", "mapping"
+    # value_type: one of "array", "array.int", "bool", "int", "hex", "list", "mapping"
     # to_json: Default `true`. Set to `false` to exclude this mapping from info.json
     # to_c: Default `true`. Set to `false` to exclude this mapping from config.h
     # warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
-    "DEBOUNCE": {"info_key": "debounce", "value_type": "int"}
+    "AUDIO_VOICES": {"info_key": "audio.voices", "value_type": "bool"},
+    "BACKLIGHT_BREATHING": {"info_key": "backlight.breathing", "value_type": "bool"},
+    "BREATHING_PERIOD": {"info_key": "backlight.breathing_period", "value_type": "int"},
+    "BACKLIGHT_PIN": {"info_key": "backlight.pin"},
+    "COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"},
+    "COMBO_TERM": {"info_key": "combo.term", "value_type": "int"},
+    "DEBOUNCE": {"info_key": "debounce", "value_type": "int"},
     "DEVICE_VER": {"info_key": "usb.device_ver", "value_type": "hex"},
     "DESCRIPTION": {"info_key": "keyboard_folder", "to_json": false},
     "DIODE_DIRECTION": {"info_key": "diode_direction"},
+    "FORCE_NKRO": {"info_key": "usb.force_nkro", "value_type": "bool"},
+    "IGNORE_MOD_TAP_INTERRUPT": {"info_key": "tapping.ignore_mod_tap_interrupt", "value_type": "bool"},
+    "IGNORE_MOD_TAP_INTERRUPT_PER_KEY": {"info_key": "tapping.ignore_mod_tap_interrupt_per_key", "value_type": "bool"},
     "LAYOUTS": {"info_key": "layout_aliases", "value_type": "mapping"},
+    "LEADER_PER_KEY_TIMING": {"info_key": "leader_key.timing", "value_type": "bool"},
+    "LEADER_KEY_STRICT_KEY_PROCESSING": {"info_key": "leader_key.strict_processing", "value_type": "bool"},
+    "LEADER_TIMEOUT": {"info_key": "leader_key.timeout", "value_type": "int"},
     "LED_CAPS_LOCK_PIN": {"info_key": "indicators.caps_lock"},
     "LED_NUM_LOCK_PIN": {"info_key": "indicators.num_lock"},
     "LED_SCROLL_LOCK_PIN": {"info_key": "indicators.scroll_lock"},
     "MANUFACTURER": {"info_key": "manufacturer"},
+    "MATRIX_HAS_GHOST": {"info_key": "matrix_pins.ghost", "value_type": "bool"},
+    "MATRIX_IO_DELAY": {"info_key": "matrix_pins.io_delay", "value_type": "int"},
+    "MOUSEKEY_DELAY": {"info_key": "mousekey.delay", "value_type": "int"},
+    "MOUSEKEY_INTERVAL": {"info_key": "mousekey.interval", "value_type": "int"},
+    "MOUSEKEY_MAX_SPEED": {"info_key": "mousekey.max_speed", "value_type": "int"},
+    "MOUSEKEY_TIME_TO_MAX": {"info_key": "mousekey.time_to_max", "value_type": "int"},
+    "MOUSEKEY_WHEEL_DELAY": {"info_key": "mousekey.wheel_delay", "value_type": "int"},
+    "ONESHOT_TIMEOUT": {"info_key": "oneshot.timeout", "value_type": "int"},
+    "ONESHOT_TAP_TOGGLE": {"info_key": "oneshot.tap_toggle", "value_type": "int"},
+    "PERMISSIVE_HOLD": {"info_key": "tapping.permissive_hold", "value_type": "bool"},
+    "PERMISSIVE_HOLD_PER_KEY": {"info_key": "tapping.permissive_hold_per_key", "value_type": "bool"},
+    "RETRO_TAPPING": {"info_key": "tapping.retro", "value_type": "bool"},
+    "RETRO_TAPPING_PER_KEY": {"info_key": "tapping.retro_per_key", "value_type": "bool"},
     "RGB_DI_PIN": {"info_key": "rgblight.pin"},
     "RGBLED_NUM": {"info_key": "rgblight.led_count", "value_type": "int"},
     "RGBLED_SPLIT": {"info_key": "rgblight.split_count", "value_type": "array.int"},
@@ -30,17 +55,40 @@
     "RGBLIGHT_EFFECT_SNAKE": {"info_key": "rgblight.animations.snake", "value_type": "bool"},
     "RGBLIGHT_EFFECT_STATIC_GRADIENT": {"info_key": "rgblight.animations.static_gradient", "value_type": "bool"},
     "RGBLIGHT_EFFECT_TWINKLE": {"info_key": "rgblight.animations.twinkle"},
+    "RGBLIGHT_LAYER_BLINK": {"info_key": "rgblight.layers.blink", "value_type": "bool"},
+    "RGBLIGHT_LAYERS": {"info_key": "rgblight.layers.enabled", "value_type": "bool"},
+    "RGBLIGHT_LAYERS_OVERRIDE_RGB_OFF": {"info_key": "rgblight.layers.override_rgb", "value_type": "bool"},
     "RGBLIGHT_LIMIT_VAL": {"info_key": "rgblight.max_brightness", "value_type": "int"},
+    "RGBLIGHT_MAX_LAYERS": {"info_key": "rgblight.layers.max", "value_type": "int"},
     "RGBLIGHT_HUE_STEP": {"info_key": "rgblight.hue_steps", "value_type": "int"},
     "RGBLIGHT_SAT_STEP": {"info_key": "rgblight.saturation_steps", "value_type": "int"},
     "RGBLIGHT_VAL_STEP": {"info_key": "rgblight.brightness_steps", "value_type": "int"},
     "RGBLIGHT_SLEEP": {"info_key": "rgblight.sleep", "value_type": "bool"},
     "RGBLIGHT_SPLIT": {"info_key": "rgblight.split", "value_type": "bool"},
+    "RGBW": {"info_key": "rgblight.rgbw", "value_type": "bool"},
     "PRODUCT": {"info_key": "keyboard_folder", "to_json": false},
     "PRODUCT_ID": {"info_key": "usb.pid", "value_type": "hex"},
     "VENDOR_ID": {"info_key": "usb.vid", "value_type": "hex"},
     "QMK_ESC_OUTPUT": {"info_key": "qmk_lufa_bootloader.esc_output"},
     "QMK_ESC_INPUT": {"info_key": "qmk_lufa_bootloader.esc_input"},
+    "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
     "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
-    "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}
+    "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
+    "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
+    "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
+    "SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},
+    "SPLIT_USB_TIMEOUT": {"info_key": "split.usb_detect.timeout", "value_type": "int"},
+    "SPLIT_USB_TIMEOUT_POLL": {"info_key": "split.usb_detect.polling_interval", "value_type": "int"},
+    "SOFT_SERIAL_PIN": {"info_key": "split.soft_serial_pin"},
+    "SOFT_SERIAL_SPEED": {"info_key": "split.soft_serial_speed"},
+    "TAP_CODE_DELAY": {"info_key": "qmk.tap_keycode_delay", "value_type": "int"},
+    "TAP_HOLD_CAPS_DELAY": {"info_key": "qmk.tap_capslock_delay", "value_type": "int"},
+    "TAPPING_FORCE_HOLD": {"info_key": "tapping.force_hold", "value_type": "bool"},
+    "TAPPING_FORCE_HOLD_PER_KEY": {"info_key": "tapping.force_hold_per_key", "value_type": "bool"},
+    "TAPPING_TERM": {"info_key": "tapping.term", "value_type": "int"},
+    "TAPPING_TERM_PER_KEY": {"info_key": "tapping.term_per_key", "value_type": "bool"},
+    "TAPPING_TOGGLE": {"info_key": "tapping.toggle", "value_type": "int"},
+    "USB_MAX_POWER_CONSUMPTION": {"info_key": "usb.max_power", "value_type": "int"},
+    "USB_POLLING_INTERVAL_MS": {"info_key": "usb.polling_interval", "value_type": "int"},
+    "USB_SUSPEND_WAKEUP_DELAY": {"info_key": "usb.suspend_wakeup_delay", "value_type": "int"},
 }
diff --git a/data/mappings/info_rules.json b/data/mappings/info_rules.json
index 97f772c4d5..aea67e04c8 100644
--- a/data/mappings/info_rules.json
+++ b/data/mappings/info_rules.json
@@ -1,15 +1,25 @@
-# This file maps keys between `rules.mk` and `info.json`. It is used by QMK 
+# This file maps keys between `rules.mk` and `info.json`. It is used by QMK
 # to correctly and consistently map back and forth between the two systems.
 {
     # Format:
     # <rules.mk key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
-    # value_type: one of "array", "array.int", "int", "list", "hex", "mapping"
+    # value_type: one of "array", "array.int", "bool", "int", "list", "hex", "mapping"
     # to_json: Default `true`. Set to `false` to exclude this mapping from info.json
     # to_c: Default `true`. Set to `false` to exclude this mapping from rules.mk
     # warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
     "BOARD": {"info_key": "board"},
     "BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},
+    "BLUETOOTH": {"info_key": "bluetooth.driver"},
+    "FIRMWARE_FORMAT": {"info_key": "build.firmware_format"},
+    "KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},
+    "MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"},
     "LAYOUTS": {"info_key": "community_layouts", "value_type": "list"},
     "LED_MATRIX_DRIVER": {"info_key": "led_matrix.driver"},
+    "LTO_ENABLE": {"info_key": "build.lto", "value_type": "bool"},
     "MCU": {"info_key": "processor", "warn_duplicate": false},
+    "MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
+    "NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
+    "SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
+    "SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "value_type": "str", "to_c": false},
+    "WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"}
 }
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index 3a3fa4b738..78c9a8d36b 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -15,6 +15,40 @@
             "type": "string",
             "enum": ["cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m4", "MKL26Z64", "MK20DX128", "MK20DX256", "MK66F18", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F407", "STM32F411", "STM32F446", "STM32G431", "STM32G474", "STM32L412", "STM32L422", "STM32L433", "STM32L443", "atmega16u2", "atmega32u2", "atmega16u4", "atmega32u4", "at90usb162", "at90usb646", "at90usb647", "at90usb1286", "at90usb1287", "atmega32a", "atmega328p", "atmega328", "attiny85", "unknown"]
         },
+        "audio": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
+                "voices": {"type": "boolean"}
+            }
+        },
+        "backlight": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "breathing": {"type": "boolean"},
+                "breathing_period": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "levels": {
+                    "type": "number",
+                    "min": 1,
+                    "max": 31,
+                    "multipleOf": 1
+                },
+                "pin": {"$ref": "qmk.definitions.v1#/mcu_pin"}
+            }
+        },
+        "bluetooth": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "driver": {
+                    "type": "string",
+                    "enum": ["AdafruitBLE", "RN42"]
+                },
+                "lto": {"type": "boolean"},
+            }
+        },
         "board": {
             "type": "string",
             "minLength": 2,
@@ -22,13 +56,39 @@
         },
         "bootloader": {
             "type": "string",
-            "enum": ["atmel-dfu", "bootloadHID", "caterina", "halfkay", "kiibohd", "lufa-dfu", "lufa-ms", "micronucleus", "qmk-dfu", "qmk-hid", "stm32-dfu", "stm32duino", "unknown", "USBasp", "tinyuf2"]
+            "enum": ["atmel-dfu", "bootloadHID", "caterina", "halfkay", "kiibohd", "lufa-dfu", "lufa-ms", "micronucleus", "qmk-dfu", "qmk-hid", "stm32-dfu", "stm32duino", "unknown", "USBasp", "tinyuf2"],
+        },
+        "bootloader_instructions": {
+            "type": "string",
+            "description": "Instructions for putting the keyboard into a mode that allows for firmware flashing."
+        },
+        "build": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "debounce_type": {
+                    "type": "string",
+                    "enum": ["custom", "eager_pk", "eager_pr", "sym_defer_pk", "sym_eager_pk"]
+                },
+                "firmware_format": {
+                    "type": "string",
+                    "enum": ["bin", "hex", "uf2"]
+                },
+                "lto": {"type": "boolean"},
+            }
         },
         "diode_direction": {
             "type": "string",
             "enum": ["COL2ROW", "ROW2COL"]
         },
         "debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+        "combo": {
+            "type": "object",
+            "properties": {
+                "count": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "term": {"$ref": "qmk.definitions.v1#/unsigned_int"}
+            }
+        },
         "community_layouts": {
             "type": "array",
             "items": {"$ref": "qmk.definitions.v1#/filename"}
@@ -90,16 +150,47 @@
                 }
             }
         },
+        "leader_key": {
+            "type": "object",
+            "properties": {
+                "timing": {"type": "boolean"},
+                "strict_processing": {"type": "boolean"},
+                "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
+            }
+        },
         "matrix_pins": {
             "type": "object",
             "additionalProperties": false,
             "properties": {
+                "custom": {"type": "boolean"},
+                "custom_lite": {"type": "boolean"},
+                "ghost": {"type": "boolean"},
+                "io_delay": {"$ref": "qmk.definitions.v1#/unsigned_int"},
                 "direct": {
                     "type": "array",
-                    "items": {$ref": "qmk.definitions.v1#/mcu_pin_array"}
+                    "items": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
                 },
                 "cols": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
-                "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
+                "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
+                "unused": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
+            }
+        },
+        "mouse_key": {
+            "type": "object",
+            "properties": {
+                "enabled": {"type": "boolean"},
+                "delay": {"$ref": "qmk.definitions.v1#/unsigned_int_8"}
+                "interval": {"$ref": "qmk.definitions.v1#/unsigned_int_8"}
+                "max_speed": {"$ref": "qmk.definitions.v1#/unsigned_int_8"}
+                "time_to_max": {"$ref": "qmk.definitions.v1#/unsigned_int_8"}
+                "wheel_delay": {"$ref": "qmk.definitions.v1#/unsigned_int_8"}
+            }
+        },
+        "oneshot": {
+            "type": "object",
+            "properties": {
+                "tap_toggle": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
             }
         },
         "rgblight": {
@@ -114,9 +205,25 @@
                 },
                 "brightness_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
                 "hue_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "layers": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                        "blink": {"type": "boolean"},
+                        "enabled": {"type": "boolean"},
+                        "max": {
+                            "type": "number",
+                            "min": 1,
+                            "max": 32,
+                            "multipleOf": 1
+                        },
+                        "override_rgb": {"type": "boolean"}
+                    }
+                },
                 "led_count": {"$ref": "qmk.definitions.v1#/unsigned_int"},
                 "max_brightness": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
                 "pin": {"$ref": "qmk.definitions.v1#/mcu_pin"},
+                "rgbw": {"type": "boolean"},
                 "saturation_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
                 "sleep": {"type": "boolean"},
                 "split": {"type": "boolean"},
@@ -128,13 +235,118 @@
                 }
             }
         },
+        "split": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "enabled": {"type": "boolean"},
+                "matrix_grid": {
+                    "type": "array",
+                    "items": {"$ref": "qmk.definitions.v1#/mcu_pin"}
+                },
+                "matrix_pins": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                        "right": {
+                            "type": "object",
+                            "additionalProperties": false,
+                            "properties": {
+                                "direct": {
+                                    "type": "array",
+                                    "items": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
+                                },
+                                "cols": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
+                                "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
+                                "unused": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
+                            }
+                        }
+                    }
+                },
+                "main": {
+                    "type": "string",
+                    "enum": ["eeprom", "left", "matrix_grid", "pin", "right"]
+                },
+                "soft_serial_pin": {"$ref": "qmk.definitions.v1#/mcu_pin"},
+                "soft_serial_speed": {
+                    "type": "number",
+                    "min": 0,
+                    "max": 5,
+                    "multipleOf": 1
+                },
+                "transport": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                        "protocol": {
+                            "type": "string",
+                            "enum": ["custom", "i2c", "serial", "serial_usart"]
+                        },
+                        "sync_matrix_state": {"type": "boolean"},
+                        "sync_modifiers": {"type": "boolean"}
+                    }
+                },
+                "usb_detect": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                        "enabled": {"type": "boolean"},
+                        "polling_interval": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                        "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
+                    }
+                }
+            }
+        },
+        "tags": {
+            "type": "array",
+            "items": {"type": "string"}
+        },
+        "tapping": {
+            "type": "object",
+            "properties": {
+                "force_hold": {"type": "boolean"},
+                "force_hold_per_key": {"type": "boolean"},
+                "ignore_mod_tap_interrupt": {"type": "boolean"},
+                "ignore_mod_tap_interrupt_per_key": {"type": "boolean"},
+                "permissive_hold": {"type": "boolean"},
+                "permissive_hold_per_key": {"type": "boolean"},
+                "retro": {"type": "boolean"},
+                "retro_per_key": {"type": "boolean"},
+                "term": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+                "term_per_key": {"type": "boolean"},
+                "toggle": {"$ref": "qmk.definitions.v1#/unsigned_int"},
+            }
+        },
         "usb": {
             "type": "object",
             "additionalProperties": false,
             "properties": {
                 "device_ver": {"$ref": "qmk.definitions.v1#/hex_number_4d"},
+                "force_nkro": {"type": "boolean"},
                 "pid": {"$ref": "qmk.definitions.v1#/hex_number_4d"},
-                "vid": {"$ref": "qmk.definitions.v1#/hex_number_4d"}
+                "vid": {"$ref": "qmk.definitions.v1#/hex_number_4d"},
+                "max_power": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "no_startup_check": {"type": "boolean"},
+                "polling_interval": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "shared_endpoint": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                        "keyboard": {"type": "boolean"},
+                        "mouse": {"type": "boolean"}
+                    }
+                },
+                "suspend_wakeup_delay": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "wait_for": {"type": "boolean"},
+            }
+        },
+        "qmk": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "keys_per_scan": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "tap_keycode_delay": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
+                "tap_capslock_delay": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
             }
         },
         "qmk_lufa_bootloader": {
diff --git a/keyboards/bpiphany/frosty_flake/config.h b/keyboards/bpiphany/frosty_flake/config.h
index 344db5f0d9..ae19184a6a 100644
--- a/keyboards/bpiphany/frosty_flake/config.h
+++ b/keyboards/bpiphany/frosty_flake/config.h
@@ -40,7 +40,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 /*
  * Keyboard Matrix Assignments
+ *
+ * MATRIX_ROW_PINS and MATRIX_COL_PINS aren't actually used and are included
+ * for data driven compatibility.
  */
+#define MATRIX_COL_PINS { B0, B3, B2, B1, B6, B4, B5, C7 }
+#define MATRIX_ROW_PINS { NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }
 #define UNUSED_PINS { C0, C1, C2, C3, C4, D2, D7 }
 
 /* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */
diff --git a/keyboards/bpiphany/tiger_lily/config.h b/keyboards/bpiphany/tiger_lily/config.h
index a86cffd16b..d3e2464bee 100644
--- a/keyboards/bpiphany/tiger_lily/config.h
+++ b/keyboards/bpiphany/tiger_lily/config.h
@@ -40,7 +40,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 /*
  * Keyboard Matrix Assignments
+ *
+ * MATRIX_ROW_PINS and MATRIX_COL_PINS aren't actually used and are included
+ * for data driven compatibility.
  */
+#define MATRIX_ROW_PINS { C2, B3, B4, B2, B1, C7, B6, B5 }
+#define MATRIX_COL_PINS { NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN, NO_PIN }
 #define UNUSED_PINS { B0, C4, D3 }
 
 #define LED_NUM_LOCK_PIN C5
diff --git a/keyboards/cest73/tkm/config.h b/keyboards/cest73/tkm/config.h
index deef979b2c..e925f39715 100644
--- a/keyboards/cest73/tkm/config.h
+++ b/keyboards/cest73/tkm/config.h
@@ -34,7 +34,7 @@
 #define MATRIX_ROW_PINS { B0, B1, B2, B3, B4, B5, B6, B7, C6, C7, D0 }
 /*    column handy ruler: c0  c1  c2  c3  c4  c5  c6  c7  c8  c9 */
 #define MATRIX_COL_PINS { D1, D2, D3, D4, D5, D6, D7, F0, F1, F4 }
-#define UNUSED_PINS { AF }
+#define UNUSED_PINS { }
 
 //NOTE: if D6 pin shows any issues in exploatation the LED on the Teensy is to be removed
 
diff --git a/keyboards/esca/getawayvan/config.h b/keyboards/esca/getawayvan/config.h
index e9fbc6b404..3b718c5835 100644
--- a/keyboards/esca/getawayvan/config.h
+++ b/keyboards/esca/getawayvan/config.h
@@ -35,7 +35,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #define MATRIX_ROW_PINS { A9, A8, A3, A5 }
 #define DIODE_DIRECTION COL2ROW
 
-#define BACKLIGHT_PIN  
 #define BACKLIGHT_LEVELS 6
 
 #define GRAVE_ESC_CTRL_OVERRIDE
diff --git a/keyboards/esca/getawayvan_f042/config.h b/keyboards/esca/getawayvan_f042/config.h
index f83ec74341..8921e9db4d 100644
--- a/keyboards/esca/getawayvan_f042/config.h
+++ b/keyboards/esca/getawayvan_f042/config.h
@@ -34,7 +34,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #define MATRIX_ROW_PINS { A9, A8, A3, A5 }
 #define DIODE_DIRECTION COL2ROW
 
-#define BACKLIGHT_PIN  
 #define BACKLIGHT_LEVELS 6
 
 #define GRAVE_ESC_CTRL_OVERRIDE
diff --git a/keyboards/handwired/tennie/config.h b/keyboards/handwired/tennie/config.h
index fee1258dfd..b1bddac45b 100644
--- a/keyboards/handwired/tennie/config.h
+++ b/keyboards/handwired/tennie/config.h
@@ -42,7 +42,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 #define MATRIX_ROW_PINS { C6, D4, D0}
 #define MATRIX_COL_PINS { D7, E6, B4, B5 }
-#define UNUSED_PINS { B1, B2, B3, B6, F4, F5, F6, F7 D1}
+#define UNUSED_PINS { B1, B2, B3, B6, F4, F5, F6, F7, D1}
 
 /* COL2ROW, ROW2COL */
 #define DIODE_DIRECTION COL2ROW
diff --git a/keyboards/mesa/mesa_tkl/config.h b/keyboards/mesa/mesa_tkl/config.h
index f4c26e2a0f..23da260bcf 100644
--- a/keyboards/mesa/mesa_tkl/config.h
+++ b/keyboards/mesa/mesa_tkl/config.h
@@ -42,7 +42,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 #define MATRIX_ROW_PINS { D2, D1, D0, B0, C6, C7 }
 #define MATRIX_COL_PINS { D3, D5, D4, D6, D7, B4, B5, B6, F7, F6, F5, F4, F1, F0, B1, B2, B3 }
-#define UNUSED_PINS     { B7 E6 }
+#define UNUSED_PINS     { B7, E6 }
 
 /* COL2ROW, ROW2COL*/
 #define DIODE_DIRECTION COL2ROW
diff --git a/keyboards/ploopyco/trackball_nano/rev1_001/config.h b/keyboards/ploopyco/trackball_nano/rev1_001/config.h
index 63780ef048..6d265d7233 100644
--- a/keyboards/ploopyco/trackball_nano/rev1_001/config.h
+++ b/keyboards/ploopyco/trackball_nano/rev1_001/config.h
@@ -29,7 +29,7 @@
  *                  ROW2COL = ROW = Anode (+), COL = Cathode (-, marked on diode)
  *
  */
-#define DIRECT_PINS {}
+#define DIRECT_PINS {{NO_PIN}}
 
 // These pins are not broken out, and cannot be used normally.
 // They are set as output and pulled high, by default
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py
index 54cd5b96a8..c0c148f1c0 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -12,7 +12,7 @@ from qmk.keyboard import keyboard_completer, keyboard_folder
 from qmk.path import is_keyboard, normpath
 
 
-def direct_pins(direct_pins):
+def direct_pins(direct_pins, postfix):
     """Return the config.h lines that set the direct pins.
     """
     rows = []
@@ -24,81 +24,60 @@ def direct_pins(direct_pins):
     col_count = len(direct_pins[0])
     row_count = len(direct_pins)
 
-    return """
-#ifndef MATRIX_COLS
-#   define MATRIX_COLS %s
-#endif // MATRIX_COLS
+    return f"""
+#ifndef MATRIX_COLS{postfix}
+#   define MATRIX_COLS{postfix} {col_count}
+#endif // MATRIX_COLS{postfix}
 
-#ifndef MATRIX_ROWS
-#   define MATRIX_ROWS %s
-#endif // MATRIX_ROWS
+#ifndef MATRIX_ROWS{postfix}
+#   define MATRIX_ROWS{postfix} {row_count}
+#endif // MATRIX_ROWS{postfix}
 
-#ifndef DIRECT_PINS
-#   define DIRECT_PINS {%s}
-#endif // DIRECT_PINS
-""" % (col_count, row_count, ','.join(rows))
+#ifndef DIRECT_PINS{postfix}
+#   define DIRECT_PINS{postfix} {{ {", ".join(rows)} }}
+#endif // DIRECT_PINS{postfix}
+"""
 
 
-def pin_array(define, pins):
+def pin_array(define, pins, postfix):
     """Return the config.h lines that set a pin array.
     """
     pin_num = len(pins)
     pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))
 
     return f"""
-#ifndef {define}S
-#   define {define}S {pin_num}
-#endif // {define}S
+#ifndef {define}S{postfix}
+#   define {define}S{postfix} {pin_num}
+#endif // {define}S{postfix}
 
-#ifndef {define}_PINS
-#   define {define}_PINS {{ {pin_array} }}
-#endif // {define}_PINS
+#ifndef {define}_PINS{postfix}
+#   define {define}_PINS{postfix} {{ {pin_array} }}
+#endif // {define}_PINS{postfix}
 """
 
 
-def matrix_pins(matrix_pins):
+def matrix_pins(matrix_pins, postfix=''):
     """Add the matrix config to the config.h.
     """
     pins = []
 
     if 'direct' in matrix_pins:
-        pins.append(direct_pins(matrix_pins['direct']))
+        pins.append(direct_pins(matrix_pins['direct'], postfix))
 
     if 'cols' in matrix_pins:
-        pins.append(pin_array('MATRIX_COL', matrix_pins['cols']))
+        pins.append(pin_array('MATRIX_COL', matrix_pins['cols'], postfix))
 
     if 'rows' in matrix_pins:
-        pins.append(pin_array('MATRIX_ROW', matrix_pins['rows']))
+        pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'], postfix))
 
     return '\n'.join(pins)
 
 
-@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
-@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
-@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
-@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
-@automagic_keyboard
-@automagic_keymap
-def generate_config_h(cli):
-    """Generates the info_config.h file.
+def generate_config_items(kb_info_json, config_h_lines):
+    """Iterate through the info_config map to generate basic config values.
     """
-    # Determine our keyboard(s)
-    if not cli.config.generate_config_h.keyboard:
-        cli.log.error('Missing parameter: --keyboard')
-        cli.subcommands['info'].print_help()
-        return False
-
-    if not is_keyboard(cli.config.generate_config_h.keyboard):
-        cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard)
-        return False
-
-    # Build the info_config.h file.
-    kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
     info_config_map = json_load(Path('data/mappings/info_config.json'))
 
-    config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
-
-    # Iterate through the info_config map to generate basic things
     for config_key, info_dict in info_config_map.items():
         info_key = info_dict['info_key']
         key_type = info_dict.get('value_type', 'str')
@@ -135,9 +114,78 @@ def generate_config_h(cli):
             config_h_lines.append(f'#   define {config_key} {config_value}')
             config_h_lines.append(f'#endif // {config_key}')
 
+
+def generate_split_config(kb_info_json, config_h_lines):
+    """Generate the config.h lines for split boards."""
+    if 'primary' in kb_info_json['split']:
+        if kb_info_json['split']['primary'] in ('left', 'right'):
+            config_h_lines.append('')
+            config_h_lines.append('#ifndef MASTER_LEFT')
+            config_h_lines.append('#   ifndef MASTER_RIGHT')
+            if kb_info_json['split']['primary'] == 'left':
+                config_h_lines.append('#       define MASTER_LEFT')
+            elif kb_info_json['split']['primary'] == 'right':
+                config_h_lines.append('#       define MASTER_RIGHT')
+            config_h_lines.append('#   endif // MASTER_RIGHT')
+            config_h_lines.append('#endif // MASTER_LEFT')
+        elif kb_info_json['split']['primary'] == 'pin':
+            config_h_lines.append('')
+            config_h_lines.append('#ifndef SPLIT_HAND_PIN')
+            config_h_lines.append('#   define SPLIT_HAND_PIN')
+            config_h_lines.append('#endif // SPLIT_HAND_PIN')
+        elif kb_info_json['split']['primary'] == 'matrix_grid':
+            config_h_lines.append('')
+            config_h_lines.append('#ifndef SPLIT_HAND_MATRIX_GRID')
+            config_h_lines.append('#   define SPLIT_HAND_MATRIX_GRID {%s}' % (','.join(kb_info_json["split"]["matrix_grid"],)))
+            config_h_lines.append('#endif // SPLIT_HAND_MATRIX_GRID')
+        elif kb_info_json['split']['primary'] == 'eeprom':
+            config_h_lines.append('')
+            config_h_lines.append('#ifndef EE_HANDS')
+            config_h_lines.append('#   define EE_HANDS')
+            config_h_lines.append('#endif // EE_HANDS')
+
+    if 'protocol' in kb_info_json['split'].get('transport', {}):
+        if kb_info_json['split']['transport']['protocol'] == 'i2c':
+            config_h_lines.append('')
+            config_h_lines.append('#ifndef USE_I2C')
+            config_h_lines.append('#   define USE_I2C')
+            config_h_lines.append('#endif // USE_I2C')
+
+    if 'right' in kb_info_json['split'].get('matrix_pins', {}):
+        config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT'))
+
+
+@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
+@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
+@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
+@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
+@automagic_keyboard
+@automagic_keymap
+def generate_config_h(cli):
+    """Generates the info_config.h file.
+    """
+    # Determine our keyboard(s)
+    if not cli.config.generate_config_h.keyboard:
+        cli.log.error('Missing parameter: --keyboard')
+        cli.subcommands['info'].print_help()
+        return False
+
+    if not is_keyboard(cli.config.generate_config_h.keyboard):
+        cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard)
+        return False
+
+    # Build the info_config.h file.
+    kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
+    config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
+
+    generate_config_items(kb_info_json, config_h_lines)
+
     if 'matrix_pins' in kb_info_json:
         config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
 
+    if 'split' in kb_info_json:
+        generate_split_config(kb_info_json, config_h_lines)
+
     # Show the results
     config_h = '\n'.join(config_h_lines)
 
diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py
index 8931b68b6f..284d1a8510 100755
--- a/lib/python/qmk/cli/generate/info_json.py
+++ b/lib/python/qmk/cli/generate/info_json.py
@@ -4,15 +4,17 @@ Compile an info.json for a particular keyboard and pretty-print it.
 """
 import json
 
-from jsonschema import Draft7Validator, validators
+from argcomplete.completers import FilesCompleter
+from jsonschema import Draft7Validator, RefResolver, validators
 from milc import cli
+from pathlib import Path
 
 from qmk.decorators import automagic_keyboard, automagic_keymap
 from qmk.info import info_json
 from qmk.json_encoders import InfoJSONEncoder
-from qmk.json_schema import load_jsonschema
+from qmk.json_schema import compile_schema_store
 from qmk.keyboard import keyboard_completer, keyboard_folder
-from qmk.path import is_keyboard
+from qmk.path import is_keyboard, normpath
 
 
 def pruning_validator(validator_class):
@@ -34,15 +36,19 @@ def pruning_validator(validator_class):
 def strip_info_json(kb_info_json):
     """Remove the API-only properties from the info.json.
     """
+    schema_store = compile_schema_store()
     pruning_draft_7_validator = pruning_validator(Draft7Validator)
-    schema = load_jsonschema('keyboard')
-    validator = pruning_draft_7_validator(schema).validate
+    schema = schema_store['qmk.keyboard.v1']
+    resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
+    validator = pruning_draft_7_validator(schema, resolver=resolver).validate
 
     return validator(kb_info_json)
 
 
 @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
 @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
+@cli.argument('-o', '--output', arg_only=True, completer=FilesCompleter, help='Write the output the specified file, overwriting if necessary.')
+@cli.argument('-ow', '--overwrite', arg_only=True, action='store_true', help='Overwrite the existing info.json. (Overrides the location of --output)')
 @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
 @automagic_keyboard
 @automagic_keymap
@@ -59,9 +65,29 @@ def generate_info_json(cli):
         cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard)
         return False
 
+    if cli.args.overwrite:
+        output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve()
+
+        if cli.args.output:
+            cli.log.warning('Overwriting user supplied --output with %s', output_path)
+
+        cli.args.output = output_path
+
     # Build the info.json file
     kb_info_json = info_json(cli.config.generate_info_json.keyboard)
     strip_info_json(kb_info_json)
+    info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder)
 
-    # Display the results
-    print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder))
+    if cli.args.output:
+        # Write to a file
+        output_path = normpath(cli.args.output)
+
+        if output_path.exists():
+            cli.log.warning('Overwriting output file %s', output_path)
+
+        output_path.write_text(info_json_text + '\n')
+        cli.log.info('Wrote info.json to %s.', output_path)
+
+    else:
+        # Display the results
+        print(info_json_text)
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 41c94e16b5..2712b81cb5 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -76,6 +76,17 @@ def generate_rules_mk(cli):
                 enabled = 'yes' if enabled else 'no'
                 rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
 
+    # Set SPLIT_TRANSPORT, if needed
+    if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom':
+        rules_mk_lines.append('SPLIT_TRANSPORT ?= custom')
+
+    # Set CUSTOM_MATRIX, if needed
+    if kb_info_json.get('matrix_pins', {}).get('custom'):
+        if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
+            rules_mk_lines.append('CUSTOM_MATRIX ?= lite')
+        else:
+            rules_mk_lines.append('CUSTOM_MATRIX ?= yes')
+
     # Show the results
     rules_mk = '\n'.join(rules_mk_lines) + '\n'
 
diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py
index 337b494a99..3131d4b53f 100755
--- a/lib/python/qmk/cli/info.py
+++ b/lib/python/qmk/cli/info.py
@@ -24,19 +24,15 @@ def show_keymap(kb_info_json, title_caps=True):
     keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)
 
     if keymap_path and keymap_path.suffix == '.json':
-        if title_caps:
-            cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap)
-        else:
-            cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap)
-
         keymap_data = json.load(keymap_path.open(encoding='utf-8'))
         layout_name = keymap_data['layout']
+        layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name)  # Resolve alias names
 
         for layer_num, layer in enumerate(keymap_data['layers']):
             if title_caps:
-                cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num)
+                cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)
             else:
-                cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num)
+                cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)
 
             print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer))
 
@@ -45,7 +41,7 @@ def show_layouts(kb_info_json, title_caps=True):
     """Render the layouts with info.json labels.
     """
     for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items():
-        title = layout_name.title() if title_caps else layout_name
+        title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}'
         cli.echo('{fg_cyan}%s{fg_reset}:', title)
         print(layout_art)  # Avoid passing dirty data to cli.echo()
 
@@ -93,15 +89,6 @@ def print_friendly_output(kb_info_json):
         aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()]
         cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),))
 
-    if cli.config.info.layouts:
-        show_layouts(kb_info_json, True)
-
-    if cli.config.info.matrix:
-        show_matrix(kb_info_json, True)
-
-    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
-        show_keymap(kb_info_json, True)
-
 
 def print_text_output(kb_info_json):
     """Print the info.json in a plain text format.
@@ -122,6 +109,24 @@ def print_text_output(kb_info_json):
         show_keymap(kb_info_json, False)
 
 
+def print_dotted_output(kb_info_json, prefix=''):
+    """Print the info.json in a plain text format with dot-joined keys.
+    """
+    for key in sorted(kb_info_json):
+        new_prefix = f'{prefix}.{key}' if prefix else key
+
+        if key in ['parse_errors', 'parse_warnings']:
+            continue
+        elif key == 'layouts' and prefix == '':
+            cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
+        elif isinstance(kb_info_json[key], dict):
+            print_dotted_output(kb_info_json[key], new_prefix)
+        elif isinstance(kb_info_json[key], list):
+            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key]))))
+        else:
+            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
+
+
 def print_parsed_rules_mk(keyboard_name):
     rules = rules_mk(keyboard_name)
     for k in sorted(rules.keys()):
@@ -162,10 +167,22 @@ def info(cli):
     # Output in the requested format
     if cli.args.format == 'json':
         print(json.dumps(kb_info_json, cls=InfoJSONEncoder))
+        return True
     elif cli.args.format == 'text':
-        print_text_output(kb_info_json)
+        print_dotted_output(kb_info_json)
+        title_caps = False
     elif cli.args.format == 'friendly':
         print_friendly_output(kb_info_json)
+        title_caps = True
     else:
         cli.log.error('Unknown format: %s', cli.args.format)
         return False
+
+    if cli.config.info.layouts:
+        show_layouts(kb_info_json, title_caps)
+
+    if cli.config.info.matrix:
+        show_matrix(kb_info_json, title_caps)
+
+    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
+        show_keymap(kb_info_json, title_caps)
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 858fbab335..b6f2ecf644 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -61,8 +61,8 @@ def info_json(keyboard):
 
     # Merge in the data from info.json, config.h, and rules.mk
     info_data = merge_info_jsons(keyboard, info_data)
-    info_data = _extract_config_h(info_data)
     info_data = _extract_rules_mk(info_data)
+    info_data = _extract_config_h(info_data)
 
     # Ensure that we have matrix row and column counts
     info_data = _matrix_size(info_data)
@@ -161,10 +161,9 @@ def _extract_pins(pins):
     return [_pin_name(pin) for pin in pins.split(',')]
 
 
-def _extract_direct_matrix(info_data, direct_pins):
+def _extract_direct_matrix(direct_pins):
     """
     """
-    info_data['matrix_pins'] = {}
     direct_pin_array = []
 
     while direct_pins[-1] != '}':
@@ -188,12 +187,157 @@ def _extract_direct_matrix(info_data, direct_pins):
     return direct_pin_array
 
 
+def _extract_audio(info_data, config_c):
+    """Populate data about the audio configuration
+    """
+    audio_pins = []
+
+    for pin in 'B5', 'B6', 'B7', 'C4', 'C5', 'C6':
+        if config_c.get(f'{pin}_AUDIO'):
+            audio_pins.append(pin)
+
+    if audio_pins:
+        info_data['audio'] = {'pins': audio_pins}
+
+
+def _extract_split_main(info_data, config_c):
+    """Populate data about the split configuration
+    """
+    # Figure out how the main half is determined
+    if config_c.get('SPLIT_HAND_PIN') is True:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'main' in info_data['split']:
+            _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_PIN) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
+
+        info_data['split']['main'] = 'pin'
+
+    if config_c.get('SPLIT_HAND_MATRIX_GRID'):
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'main' in info_data['split']:
+            _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_MATRIX_GRID) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
+
+        info_data['split']['main'] = 'matrix_grid'
+        info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID'])
+
+    if config_c.get('EE_HANDS') is True:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'main' in info_data['split']:
+            _log_warning(info_data, 'Split main hand is specified in both config.h (EE_HANDS) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
+
+        info_data['split']['main'] = 'eeprom'
+
+    if config_c.get('MASTER_RIGHT') is True:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'main' in info_data['split']:
+            _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_RIGHT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
+
+        info_data['split']['main'] = 'right'
+
+    if config_c.get('MASTER_LEFT') is True:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'main' in info_data['split']:
+            _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_LEFT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main'])
+
+        info_data['split']['main'] = 'left'
+
+
+def _extract_split_transport(info_data, config_c):
+    # Figure out the transport method
+    if config_c.get('USE_I2C') is True:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'transport' not in info_data['split']:
+            info_data['split']['transport'] = {}
+
+        if 'protocol' in info_data['split']['transport']:
+            _log_warning(info_data, 'Split transport is specified in both config.h (USE_I2C) and info.json (split.transport.protocol) (Value: %s), the config.h value wins.' % info_data['split']['transport'])
+
+        info_data['split']['transport']['protocol'] = 'i2c'
+
+    elif 'protocol' not in info_data.get('split', {}).get('transport', {}):
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'transport' not in info_data['split']:
+            info_data['split']['transport'] = {}
+
+        info_data['split']['transport']['protocol'] = 'serial'
+
+
+def _extract_split_right_pins(info_data, config_c):
+    # Figure out the right half matrix pins
+    row_pins = config_c.get('MATRIX_ROW_PINS_RIGHT', '').replace('{', '').replace('}', '').strip()
+    col_pins = config_c.get('MATRIX_COL_PINS_RIGHT', '').replace('{', '').replace('}', '').strip()
+    unused_pin_text = config_c.get('UNUSED_PINS_RIGHT')
+    unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None
+    direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1]
+
+    if row_pins and col_pins:
+        if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data:
+            _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
+
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'matrix_pins' not in info_data['split']:
+            info_data['split']['matrix_pins'] = {}
+
+        if 'right' not in info_data['split']['matrix_pins']:
+            info_data['split']['matrix_pins']['right'] = {}
+
+        info_data['split']['matrix_pins']['right'] = {
+            'cols': _extract_pins(col_pins),
+            'rows': _extract_pins(row_pins),
+        }
+
+    if direct_pins:
+        if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}):
+            _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.')
+
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'matrix_pins' not in info_data['split']:
+            info_data['split']['matrix_pins'] = {}
+
+        if 'right' not in info_data['split']['matrix_pins']:
+            info_data['split']['matrix_pins']['right'] = {}
+
+        info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins)
+
+    if unused_pins:
+        if 'split' not in info_data:
+            info_data['split'] = {}
+
+        if 'matrix_pins' not in info_data['split']:
+            info_data['split']['matrix_pins'] = {}
+
+        if 'right' not in info_data['split']['matrix_pins']:
+            info_data['split']['matrix_pins']['right'] = {}
+
+        info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins)
+
+
 def _extract_matrix_info(info_data, config_c):
     """Populate the matrix information.
     """
     row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
     col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
+    unused_pin_text = config_c.get('UNUSED_PINS')
+    unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None
     direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
+    info_snippet = {}
 
     if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
         if 'matrix_size' in info_data:
@@ -205,19 +349,35 @@ def _extract_matrix_info(info_data, config_c):
         }
 
     if row_pins and col_pins:
-        if 'matrix_pins' in info_data:
+        if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
             _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
 
-        info_data['matrix_pins'] = {
-            'cols': _extract_pins(col_pins),
-            'rows': _extract_pins(row_pins),
-        }
+        info_snippet['cols'] = _extract_pins(col_pins)
+        info_snippet['rows'] = _extract_pins(row_pins)
 
     if direct_pins:
-        if 'matrix_pins' in info_data:
+        if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:
             _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
 
-        info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins)
+        info_snippet['direct'] = _extract_direct_matrix(direct_pins)
+
+    if unused_pins:
+        if 'matrix_pins' not in info_data:
+            info_data['matrix_pins'] = {}
+
+        info_snippet['unused'] = _extract_pins(unused_pins)
+
+    if config_c.get('CUSTOM_MATRIX', 'no') != 'no':
+        if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
+            _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.')
+
+        info_snippet['custom'] = True
+
+        if config_c['CUSTOM_MATRIX'] == 'lite':
+            info_snippet['custom_lite'] = True
+
+    if info_snippet:
+        info_data['matrix_pins'] = info_snippet
 
     return info_data
 
@@ -275,6 +435,10 @@ def _extract_config_h(info_data):
 
     # Pull data that easily can't be mapped in json
     _extract_matrix_info(info_data, config_c)
+    _extract_audio(info_data, config_c)
+    _extract_split_main(info_data, config_c)
+    _extract_split_transport(info_data, config_c)
+    _extract_split_right_pins(info_data, config_c)
 
     return info_data
 
diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py
index cbc5bff518..ffc7c6bcd1 100644
--- a/lib/python/qmk/json_schema.py
+++ b/lib/python/qmk/json_schema.py
@@ -2,6 +2,7 @@
 """
 import json
 from collections.abc import Mapping
+from functools import lru_cache
 from pathlib import Path
 
 import hjson
@@ -25,6 +26,7 @@ def json_load(json_file):
         exit(1)
 
 
+@lru_cache(maxsize=0)
 def load_jsonschema(schema_name):
     """Read a jsonschema file from disk.
     """
@@ -39,8 +41,9 @@ def load_jsonschema(schema_name):
     return json_load(schema_path)
 
 
-def create_validator(schema):
-    """Creates a validator for the given schema id.
+@lru_cache(maxsize=0)
+def compile_schema_store():
+    """Compile all our schemas into a schema store.
     """
     schema_store = {}
 
@@ -51,6 +54,14 @@ def create_validator(schema):
             continue
         schema_store[schema_data['$id']] = schema_data
 
+    return schema_store
+
+
+@lru_cache(maxsize=0)
+def create_validator(schema):
+    """Creates a validator for the given schema id.
+    """
+    schema_store = compile_schema_store()
     resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
 
     return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate