summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/.gitignore1
-rw-r--r--app/CMakeLists.txt40
-rw-r--r--app/Kconfig84
-rw-r--r--app/boards/Kconfig1
-rw-r--r--app/boards/arm/planck/CMakeLists.txt9
-rw-r--r--app/boards/arm/planck/Kconfig.board8
-rw-r--r--app/boards/arm/planck/Kconfig.defconfig39
-rw-r--r--app/boards/arm/planck/board.cmake6
-rw-r--r--app/boards/arm/planck/pinmux.c69
-rw-r--r--app/boards/arm/planck/planck_rev6.dts162
-rw-r--r--app/boards/arm/planck/planck_rev6.yaml19
-rw-r--r--app/boards/arm/planck/planck_rev6_defconfig50
-rw-r--r--app/boards/native_posix.conf9
-rw-r--r--app/boards/native_posix.overlay79
-rw-r--r--app/boards/shields/petejohanson_handwire/Kconfig.defconfig13
-rw-r--r--app/boards/shields/petejohanson_handwire/Kconfig.shield5
-rw-r--r--app/boards/shields/petejohanson_handwire/keymaps/default/include/keymap.h5
-rw-r--r--app/boards/shields/petejohanson_handwire/keymaps/default/keymap.c34
-rw-r--r--app/boards/shields/petejohanson_handwire/keymaps/default/keymap.overlay41
-rw-r--r--app/boards/shields/petejohanson_handwire/petejohanson_handwire.conf1
-rw-r--r--app/boards/shields/petejohanson_handwire/petejohanson_handwire.overlay44
-rw-r--r--app/cmake/keymap.cmake94
-rw-r--r--app/drivers/zephyr/CMakeLists.txt9
-rw-r--r--app/drivers/zephyr/Kconfig11
-rw-r--r--app/drivers/zephyr/dts/bindings/gpio-kscan.yaml25
-rw-r--r--app/drivers/zephyr/kscan_gpio.c256
-rw-r--r--app/drivers/zephyr/module.yaml3
-rw-r--r--app/dts/bindings/zmk,keymap.yaml13
-rw-r--r--app/dts/bindings/zmk,kscan-composite.yaml27
-rw-r--r--app/dts/bindings/zmk,kscan-mock.yaml17
-rw-r--r--app/dts/bindings/zmk,layers.yaml14
-rw-r--r--app/include/dt-bindings/zmk/keys.h142
-rw-r--r--app/include/zmk/ble.h7
-rw-r--r--app/include/zmk/endpoints.h8
-rw-r--r--app/include/zmk/handlers.h8
-rw-r--r--app/include/zmk/hid.h194
-rw-r--r--app/include/zmk/hog.h10
-rw-r--r--app/include/zmk/keymap.h28
-rw-r--r--app/include/zmk/keys.h17
-rw-r--r--app/include/zmk/kscan-mock.h8
-rw-r--r--app/include/zmk/kscan.h3
-rw-r--r--app/include/zmk/matrix.h12
-rw-r--r--app/include/zmk/usb_hid.h14
-rw-r--r--app/prj.conf2
-rw-r--r--app/src/ble.c193
-rw-r--r--app/src/endpoints.c105
-rw-r--r--app/src/handlers.c88
-rw-r--r--app/src/hid.c130
-rw-r--r--app/src/hog.c158
-rw-r--r--app/src/keymap.c74
-rw-r--r--app/src/kscan.c75
-rw-r--r--app/src/kscan_composite.c130
-rw-r--r--app/src/kscan_mock.c102
-rw-r--r--app/src/main.c39
-rw-r--r--app/src/usb_hid.c58
-rw-r--r--app/west.yml13
56 files changed, 2806 insertions, 0 deletions
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
new file mode 100644
index 0000000..c5868ee
--- /dev/null
+++ b/app/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Find Zephyr. This also loads Zephyr's build system.
+cmake_minimum_required(VERSION 3.13.1)
+
+list(APPEND BOARD_ROOT ${CMAKE_SOURCE_DIR})
+list(APPEND DTS_ROOT ${CMAKE_SOURCE_DIR})
+
+# Add our custom Zephyr module for drivers w/ syscalls, etc.
+list(APPEND DTS_ROOT ${CMAKE_SOURCE_DIR}/drivers/zephyr)
+
+list(APPEND ZEPHYR_EXTRA_MODULES
+ ${CMAKE_CURRENT_SOURCE_DIR}/drivers
+)
+list(APPEND SYSCALL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/drivers/zephyr)
+
+include(cmake/keymap.cmake)
+
+find_package(Zephyr)
+project(zmk)
+
+
+if(EXISTS ${KEYMAP_DIR}/keymap.c)
+ target_sources(app PRIVATE ${KEYMAP_DIR}/keymap.c)
+endif()
+
+
+# Add your source file to the "app" target. This must come after
+# find_package(Zephyr) which defines the target.
+target_include_directories(app PRIVATE include)
+target_sources(app PRIVATE src/kscan.c)
+target_sources(app PRIVATE src/keymap.c)
+target_sources(app PRIVATE src/hid.c)
+target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
+target_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER app PRIVATE src/kscan_mock.c)
+target_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER app PRIVATE src/kscan_composite.c)
+target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c)
+target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/hog.c)
+target_sources(app PRIVATE src/endpoints.c)
+target_sources(app PRIVATE src/main.c)
+target_sources(app PRIVATE src/handlers.c)
+
diff --git a/app/Kconfig b/app/Kconfig
new file mode 100644
index 0000000..7871b91
--- /dev/null
+++ b/app/Kconfig
@@ -0,0 +1,84 @@
+mainmenu "ZMK Firmware"
+
+config ZMK_KEYBOARD_NAME
+ string "Keyboard Name"
+
+config USB_DEVICE_PRODUCT
+ default ZMK_KEYBOARD_NAME
+
+config BT_DEVICE_NAME
+ default ZMK_KEYBOARD_NAME
+
+config ZMK_KSCAN_EVENT_QUEUE_SIZE
+ int "Size of the event queue for KSCAN events to buffer events"
+ default 4
+
+menu "HID Output Types"
+
+config ZMK_USB
+ bool "USB"
+ select USB
+ select USB_DEVICE_STACK
+ select USB_DEVICE_HID
+
+menuconfig ZMK_BLE
+ bool "BLE (HID over GATT)"
+ select BT
+ select BT_SMP
+ select BT_SMP_SC_PAIR_ONLY
+ select BT_PERIPHERAL
+ select BT_GATT_DIS
+ select BT_GATT_BAS
+
+if ZMK_BLE
+
+# HID GATT notifications sent this way are *not* picked up by Linux, and possibly others.
+config BT_GATT_NOTIFY_MULTIPLE
+ default n
+
+config BT_DEVICE_APPEARANCE
+ default 961
+
+config ZMK_BLE_PASSKEY_ENTRY
+ bool "Experimental: Requiring typing passkey from host to pair BLE connection"
+ default n
+
+# Incresed stack due to settings API usage
+# CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
+#
+# CONFIG_BT_SETTINGS=y
+# CONFIG_FLASH=y
+# CONFIG_FLASH_PAGE_LAYOUT=y
+# CONFIG_FLASH_MAP=y
+# CONFIG_NVS=y
+# CONFIG_SETTINGS=y
+
+endif
+
+endmenu
+
+config ZMK_KSCAN_MOCK_DRIVER
+ bool "Enable mock kscan driver to simulate key presses"
+ default n
+
+
+config ZMK_KSCAN_COMPOSITE_DRIVER
+ bool "Enable composite kscan driver to combine kscan devices"
+ default n
+
+menu "ZMK Actions"
+
+config ZMK_ACTION_MOD_TAP
+ bool "Enable the Mod-Tap Action"
+
+endmenu
+
+module = ZMK
+module-str = zmk
+source "subsys/logging/Kconfig.template.log_config"
+
+rsource "boards/Kconfig"
+rsource "boards/shields/*/Kconfig.defconfig"
+
+source "Kconfig.zephyr"
+
diff --git a/app/boards/Kconfig b/app/boards/Kconfig
new file mode 100644
index 0000000..1c645e6
--- /dev/null
+++ b/app/boards/Kconfig
@@ -0,0 +1 @@
+rsource "shields/*/Kconfig.shield"
diff --git a/app/boards/arm/planck/CMakeLists.txt b/app/boards/arm/planck/CMakeLists.txt
new file mode 100644
index 0000000..f26de66
--- /dev/null
+++ b/app/boards/arm/planck/CMakeLists.txt
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: Apache-2.0
+
+list(APPEND EXTRA_DTC_FLAGS "-qq")
+
+if(CONFIG_PINMUX)
+zephyr_library()
+zephyr_library_sources(pinmux.c)
+zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
+endif()
diff --git a/app/boards/arm/planck/Kconfig.board b/app/boards/arm/planck/Kconfig.board
new file mode 100644
index 0000000..280b48f
--- /dev/null
+++ b/app/boards/arm/planck/Kconfig.board
@@ -0,0 +1,8 @@
+# STM32F3DISCOVERY board configuration
+
+# Copyright (c) 2017 I-SENSE group of ICCS
+# SPDX-License-Identifier: Apache-2.0
+
+config BOARD_STM32F3_DISCO
+ bool "STM32F3DISCOVERY Development Board"
+ depends on SOC_STM32F303XC
diff --git a/app/boards/arm/planck/Kconfig.defconfig b/app/boards/arm/planck/Kconfig.defconfig
new file mode 100644
index 0000000..3ccd178
--- /dev/null
+++ b/app/boards/arm/planck/Kconfig.defconfig
@@ -0,0 +1,39 @@
+# Planck keyboard configuration
+
+# Copyright (c) 2017 I-SENSE group of ICCS
+# SPDX-License-Identifier: Apache-2.0
+
+if BOARD_PLANCK_REV6
+
+config BOARD
+ default "planck_rev6"
+
+config UART_1
+ default y
+ depends on UART_CONSOLE
+
+if I2C
+
+config I2C_1
+ default y
+
+config I2C_2
+ default y
+
+endif # I2C
+
+if SPI
+
+config SPI_1
+ default y
+
+config SPI_2
+ default y
+
+endif # SPI
+
+config CAN_1
+ default y
+ depends on CAN
+
+endif # BOARD_PLANCK_REV6
diff --git a/app/boards/arm/planck/board.cmake b/app/boards/arm/planck/board.cmake
new file mode 100644
index 0000000..e2bdf48
--- /dev/null
+++ b/app/boards/arm/planck/board.cmake
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0
+
+board_runner_args(jlink "--device=STM32F303VC" "--speed=4000")
+
+include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake)
+include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
diff --git a/app/boards/arm/planck/pinmux.c b/app/boards/arm/planck/pinmux.c
new file mode 100644
index 0000000..76bd6d1
--- /dev/null
+++ b/app/boards/arm/planck/pinmux.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017 I-SENSE group of ICCS
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <kernel.h>
+#include <device.h>
+#include <init.h>
+#include <drivers/pinmux.h>
+#include <sys/sys_io.h>
+
+#include <pinmux/stm32/pinmux_stm32.h>
+
+/* pin assignments for STM32F3DISCOVERY board */
+static const struct pin_config pinconf[] = {
+#ifdef CONFIG_UART_1
+ {STM32_PIN_PC4, STM32F3_PINMUX_FUNC_PC4_USART1_TX},
+ {STM32_PIN_PC5, STM32F3_PINMUX_FUNC_PC5_USART1_RX},
+#endif /* CONFIG_UART_1 */
+#ifdef CONFIG_UART_2
+ {STM32_PIN_PA2, STM32F3_PINMUX_FUNC_PA2_USART2_TX},
+ {STM32_PIN_PA3, STM32F3_PINMUX_FUNC_PA3_USART2_RX},
+#endif /* CONFIG_UART_2 */
+#ifdef CONFIG_I2C_1
+ {STM32_PIN_PB6, STM32F3_PINMUX_FUNC_PB6_I2C1_SCL},
+ {STM32_PIN_PB7, STM32F3_PINMUX_FUNC_PB7_I2C1_SDA},
+#endif /* CONFIG_I2C_1 */
+#ifdef CONFIG_I2C_2
+ {STM32_PIN_PA9, STM32F3_PINMUX_FUNC_PA9_I2C2_SCL},
+ {STM32_PIN_PA10, STM32F3_PINMUX_FUNC_PA10_I2C2_SDA},
+#endif /* CONFIG_I2C_2 */
+#ifdef CONFIG_SPI_1
+#ifdef CONFIG_SPI_STM32_USE_HW_SS
+ {STM32_PIN_PA4, STM32F3_PINMUX_FUNC_PA4_SPI1_NSS},
+#endif /* CONFIG_SPI_STM32_USE_HW_SS */
+ {STM32_PIN_PA5, STM32F3_PINMUX_FUNC_PA5_SPI1_SCK},
+ {STM32_PIN_PA6, STM32F3_PINMUX_FUNC_PA6_SPI1_MISO},
+ {STM32_PIN_PA7, STM32F3_PINMUX_FUNC_PA7_SPI1_MOSI},
+#endif /* CONFIG_SPI_1 */
+#ifdef CONFIG_SPI_2
+#ifdef CONFIG_SPI_STM32_USE_HW_SS
+ {STM32_PIN_PB12, STM32F3_PINMUX_FUNC_PB12_SPI2_NSS},
+#endif /* CONFIG_SPI_STM32_USE_HW_SS */
+ {STM32_PIN_PB13, STM32F3_PINMUX_FUNC_PB13_SPI2_SCK},
+ {STM32_PIN_PB14, STM32F3_PINMUX_FUNC_PB14_SPI2_MISO},
+ {STM32_PIN_PB15, STM32F3_PINMUX_FUNC_PB15_SPI2_MOSI},
+#endif /* CONFIG_SPI_2 */
+#ifdef CONFIG_USB_DC_STM32
+ {STM32_PIN_PA11, STM32F3_PINMUX_FUNC_PA11_USB_DM},
+ {STM32_PIN_PA12, STM32F3_PINMUX_FUNC_PA12_USB_DP},
+#endif /* CONFIG_USB_DC_STM32 */
+#ifdef CONFIG_CAN_1
+ {STM32_PIN_PD0, STM32F3_PINMUX_FUNC_PD0_CAN1_RX},
+ {STM32_PIN_PD1, STM32F3_PINMUX_FUNC_PD1_CAN1_TX},
+#endif /* CONFIG_CAN_1 */
+};
+
+static int pinmux_stm32_init(struct device *port)
+{
+ ARG_UNUSED(port);
+
+ stm32_setup_pins(pinconf, ARRAY_SIZE(pinconf));
+
+ return 0;
+}
+
+SYS_INIT(pinmux_stm32_init, PRE_KERNEL_1,
+ CONFIG_PINMUX_STM32_DEVICE_INITIALIZATION_PRIORITY);
diff --git a/app/boards/arm/planck/planck_rev6.dts b/app/boards/arm/planck/planck_rev6.dts
new file mode 100644
index 0000000..58de7a4
--- /dev/null
+++ b/app/boards/arm/planck/planck_rev6.dts
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2017 I-SENSE group of ICCS
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/dts-v1/;
+#include <st/f3/stm32f303.dtsi>
+
+/ {
+ model = "Plack PCD, rev6";
+ compatible = "planck,rev6", "st,stm32f303";
+
+ chosen {
+ zephyr,console = &usart1;
+ zephyr,shell-uart = &usart1;
+ zephyr,sram = &sram0;
+ zephyr,flash = &flash0;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+ red_led_3: led_3 {
+ gpios = <&gpioe 9 GPIO_ACTIVE_HIGH>;
+ label = "User LD3";
+ };
+ blue_led_4: led_4 {
+ gpios = <&gpioe 8 GPIO_ACTIVE_HIGH>;
+ label = "User LD4";
+ };
+ orange_led_5: led_5 {
+ gpios = <&gpioe 10 GPIO_ACTIVE_HIGH>;
+ label = "User LD5";
+ };
+ green_led_6: led_6 {
+ gpios = <&gpioe 15 GPIO_ACTIVE_HIGH>;
+ label = "User LD6";
+ };
+ green_led_7: led_7 {
+ gpios = <&gpioe 11 GPIO_ACTIVE_HIGH>;
+ label = "User LD7";
+ };
+ orange_led_8: led_8 {
+ gpios = <&gpioe 14 GPIO_ACTIVE_HIGH>;
+ label = "User LD8";
+ };
+ blue_led_9: led_9 {
+ gpios = <&gpioe 12 GPIO_ACTIVE_HIGH>;
+ label = "User LD9";
+ };
+ red_led_10: led_10 {
+ gpios = <&gpioe 13 GPIO_ACTIVE_HIGH>;
+ label = "User LD10";
+ };
+ };
+
+ kscan {
+ compatible = "gpio-kscan";
+ label = "Keyscan Matrix";
+ row-gpios = <&gpioa 10 GPIO_ACTIVE_HIGH>,
+ <&gpioa 9 GPIO_ACTIVE_HIGH>,
+ <&gpioa 8 GPIO_ACTIVE_HIGH>,
+ <&gpiob 15 GPIO_ACTIVE_HIGH>,
+ <&gpioc 13 GPIO_ACTIVE_HIGH>,
+ <&gpioc 14 GPIO_ACTIVE_HIGH>,
+ <&gpioc 15 GPIO_ACTIVE_HIGH>,
+ <&gpioa 2 GPIO_ACTIVE_HIGH>;
+ col-gpios = <&gpiob 11 GPIO_ACTIVE_HIGH>,
+ <&gpiob 10 GPIO_ACTIVE_HIGH>,
+ <&gpiob 2 GPIO_ACTIVE_HIGH>,
+ <&gpiob 1 GPIO_ACTIVE_HIGH>,
+ <&gpioa 7 GPIO_ACTIVE_HIGH>,
+ <&gpiob 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_keys {
+ compatible = "gpio-keys";
+ user_button: button {
+ label = "User";
+ gpios = <&gpioa 0 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ aliases {
+ led0 = &green_led_6;
+ led1 = &green_led_7;
+ sw0 = &user_button;
+ can-primary = &can1;
+ };
+};
+
+&usart1 {
+ current-speed = <115200>;
+ status = "okay";
+};
+
+&usart2 {
+ current-speed = <115200>;
+ status = "okay";
+};
+
+&i2c1 {
+ status = "okay";
+ clock-frequency = <I2C_BITRATE_FAST>;
+
+ lsm303dlhc-magn@1e {
+ compatible = "st,lsm303dlhc-magn";
+ reg = <0x1e>;
+ label = "LSM303DLHC-MAGN";
+ };
+
+ lsm303dlhc-accel@19 {
+ compatible = "st,lis2dh", "st,lsm303dlhc-accel";
+ reg = <0x19>;
+ irq-gpios = <&gpioe 4 GPIO_ACTIVE_HIGH>,
+ <&gpioe 5 GPIO_ACTIVE_HIGH>;
+ label = "LSM303DLHC-ACCEL";
+ };
+};
+
+&i2c2 {
+ status = "okay";
+ clock-frequency = <I2C_BITRATE_FAST>;
+};
+
+&spi1 {
+ status = "okay";
+};
+
+&spi2 {
+ status = "okay";
+};
+
+&usb {
+ status = "okay";
+};
+
+&rtc {
+ status = "okay";
+};
+
+&can1 {
+ status = "okay";
+};
+
+&flash0 {
+ /*
+ * For more information, see:
+ * http://docs.zephyrproject.org/latest/guides/dts/index.html#flash-partitions
+ */
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ /* Set 6Kb of storage at the end of the 256Kb of flash */
+ storage_partition: partition@3e800 {
+ label = "storage";
+ reg = <0x0003e800 0x00001800>;
+ };
+ };
+};
diff --git a/app/boards/arm/planck/planck_rev6.yaml b/app/boards/arm/planck/planck_rev6.yaml
new file mode 100644
index 0000000..9c5af3f
--- /dev/null
+++ b/app/boards/arm/planck/planck_rev6.yaml
@@ -0,0 +1,19 @@
+identifier: planck_rev6
+name: PLANKREV6
+type: keyboard
+arch: arm
+toolchain:
+ - zephyr
+ - gnuarmemb
+ - xtools
+ram: 40
+supported:
+ - gpio
+ - i2c
+ - counter
+ - spi
+ - usb_device
+ - lsm303dlhc
+ - nvs
+ - can
+ - kscan
diff --git a/app/boards/arm/planck/planck_rev6_defconfig b/app/boards/arm/planck/planck_rev6_defconfig
new file mode 100644
index 0000000..17330e2
--- /dev/null
+++ b/app/boards/arm/planck/planck_rev6_defconfig
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: Apache-2.0
+
+CONFIG_SOC_SERIES_STM32F3X=y
+CONFIG_SOC_STM32F303XC=y
+# 72MHz system clock
+CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=72000000
+
+# Floating Point Options
+CONFIG_FLOAT=y
+
+# enable uart driver
+CONFIG_SERIAL=y
+
+# enable console
+CONFIG_CONSOLE=y
+CONFIG_UART_CONSOLE=y
+
+#enable I2C
+CONFIG_I2C=y
+
+#enable SPI
+CONFIG_SPI=y
+
+# enable pinmux
+CONFIG_PINMUX=y
+
+# enable GPIO
+CONFIG_GPIO=y
+
+# clock configuration
+CONFIG_CLOCK_CONTROL=y
+
+# kscan matrix
+CONFIG_KSCAN=y
+CONFIG_KSCAN_GPIO=y
+
+# Clock configuration for Cube Clock control driver
+CONFIG_CLOCK_STM32_HSE_CLOCK=8000000
+CONFIG_CLOCK_STM32_SYSCLK_SRC_PLL=y
+# use HSE as PLL input
+CONFIG_CLOCK_STM32_PLL_SRC_HSE=y
+# however, the board does not have an external oscillator, so just use
+# the 8MHz clock signal coming from integrated STLink
+CONFIG_CLOCK_STM32_HSE_BYPASS=y
+# produce 72MHz clock at PLL output
+CONFIG_CLOCK_STM32_PLL_PREDIV=1
+CONFIG_CLOCK_STM32_PLL_MULTIPLIER=9
+CONFIG_CLOCK_STM32_AHB_PRESCALER=1
+CONFIG_CLOCK_STM32_APB1_PRESCALER=2
+CONFIG_CLOCK_STM32_APB2_PRESCALER=1
diff --git a/app/boards/native_posix.conf b/app/boards/native_posix.conf
new file mode 100644
index 0000000..5bfb366
--- /dev/null
+++ b/app/boards/native_posix.conf
@@ -0,0 +1,9 @@
+CONFIG_KSCAN=n
+CONFIG_ZMK_KSCAN_MOCK_DRIVER=y
+CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER=y
+CONFIG_ZMK_KSCAN_GPIO_DRIVER=n
+CONFIG_GPIO=n
+CONFIG_ZMK_BLE=n
+CONFIG_LOG=y
+CONFIG_KSCAN_LOG_LEVEL_DBG=y
+CONFIG_ZMK_LOG_LEVEL_DBG=y
diff --git a/app/boards/native_posix.overlay b/app/boards/native_posix.overlay
new file mode 100644
index 0000000..9d92bee
--- /dev/null
+++ b/app/boards/native_posix.overlay
@@ -0,0 +1,79 @@
+#include <dt-bindings/zmk/keys.h>
+#include <zmk/kscan-mock.h>
+
+/ {
+ chosen {
+ zmk,kscan = &kscan0;
+ zmk,keymap = &keymap0;
+ };
+
+ kscan0: kscan_0 {
+ compatible = "zmk,kscan-composite";
+ label = "KSCAN_COMP";
+ rows = <2>;
+ columns = <4>;
+
+ left: left {
+ kscan = <&left_hand>;
+ };
+
+ right: right {
+ kscan = <&right_hand>;
+ column-offset = <2>;
+ };
+ };
+
+ left_hand: kscan_1 {
+ compatible = "zmk,kscan-mock";
+ label = "KSCAN_LEFT";
+
+ rows = <2>;
+ columns = <2>;
+ events = <ZMK_MOCK_PRESS(0,1,300) ZMK_MOCK_PRESS(0,0,300) ZMK_MOCK_RELEASE(0,0,300) ZMK_MOCK_RELEASE(0,1,300)>;
+ // events = <ZMK_MOCK_PRESS(0,0,800) ZMK_MOCK_RELEASE(0,0,800) ZMK_MOCK_PRESS(0,1,800) ZMK_MOCK_RELEASE(0,1,800)>;
+ };
+
+ right_hand: kscan_2 {
+ compatible = "zmk,kscan-mock";
+ label = "KSCAN_RIGHT";
+
+ rows = <2>;
+ columns = <2>;
+ events = <ZMK_MOCK_PRESS(1,1,800) ZMK_MOCK_RELEASE(1,1,100) ZMK_MOCK_PRESS(0,1,800) ZMK_MOCK_RELEASE(0,1,100)>;
+ };
+
+ keymap0: keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+ layers = <&default &lower &raise>;
+ };
+
+ layers {
+ compatible = "zmk,layers";
+
+ default: layer_0 {
+ label = "DEFAULT";
+ keys =
+ <
+ KC_A MT(MOD_LSFT, KC_B) KC_C KC_D
+ KC_E KC_F KC_G KC_H
+ >;
+ };
+
+ lower: layer_1 {
+ label = "LOWER";
+ keys = <
+ KC_A KC_B KC_C KC_D
+ KC_E KC_F KC_G KC_H
+ >;
+ };
+
+ raise: layer_2 {
+ label = "RAISE";
+ keys = <
+ KC_E KC_F KC_G KC_H
+ KC_A KC_B KC_C KC_D
+ >;
+ };
+ };
+};
diff --git a/app/boards/shields/petejohanson_handwire/Kconfig.defconfig b/app/boards/shields/petejohanson_handwire/Kconfig.defconfig
new file mode 100644
index 0000000..7da09ac
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/Kconfig.defconfig
@@ -0,0 +1,13 @@
+
+if SHIELD_PETEJOHANSON_HANDWIRE
+
+config ZMK_KEYBOARD_NAME
+ default "Pete's Handwire Breadboard"
+
+config ZMK_BLE
+ default y
+
+config ZMK_ACTION_MOD_TAP
+ default y
+
+endif
diff --git a/app/boards/shields/petejohanson_handwire/Kconfig.shield b/app/boards/shields/petejohanson_handwire/Kconfig.shield
new file mode 100644
index 0000000..f6e8a3f
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/Kconfig.shield
@@ -0,0 +1,5 @@
+# Copyright (c) 2019 Linaro Limited
+# SPDX-License-Identifier: Apache-2.0
+
+config SHIELD_PETEJOHANSON_HANDWIRE
+ def_bool $(shields_list_contains,petejohanson_handwire)
diff --git a/app/boards/shields/petejohanson_handwire/keymaps/default/include/keymap.h b/app/boards/shields/petejohanson_handwire/keymaps/default/include/keymap.h
new file mode 100644
index 0000000..01cd9e5
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/keymaps/default/include/keymap.h
@@ -0,0 +1,5 @@
+
+#include <dt-bindings/zmk/keys.h>
+
+#define CC_RAIS ZC_CSTM(1)
+#define CC_LOWR ZC_CSTM(2) \ No newline at end of file
diff --git a/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.c b/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.c
new file mode 100644
index 0000000..067cd08
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.c
@@ -0,0 +1,34 @@
+
+#include <zmk/keys.h>
+#include <zmk/keymap.h>
+#include <keymap.h>
+
+bool zmk_handle_key_user(struct zmk_key_event *key_event)
+{
+ switch (key_event->key)
+ {
+ case CC_LOWR:
+ if (key_event->pressed)
+ {
+ zmk_keymap_layer_activate(1);
+ }
+ else
+ {
+ zmk_keymap_layer_deactivate(1);
+ }
+
+ return false;
+ case CC_RAIS:
+ if (key_event->pressed)
+ {
+ zmk_keymap_layer_activate(2);
+ }
+ else
+ {
+ zmk_keymap_layer_deactivate(2);
+ }
+ return false;
+ }
+
+ return true;
+};
diff --git a/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.overlay b/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.overlay
new file mode 100644
index 0000000..d87363f
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/keymaps/default/keymap.overlay
@@ -0,0 +1,41 @@
+#include <dt-bindings/zmk/keys.h>
+#include <keymap.h>
+
+/ {
+ chosen {
+ zmk,keymap = &keymap0;
+ };
+ keymap0: keymap {
+ compatible = "zmk,keymap";
+ label ="Default keymap";
+ layers = <&default &lower &raise>;
+ };
+
+ layers {
+ compatible = "zmk,layers";
+
+ default: layer_0 {
+ label = "DEFAULT";
+ keys =
+ <
+ KC_A MT(MOD_LSFT, KC_B) ZC_NO ZC_NO
+ CC_RAIS CC_LOWR ZC_NO ZC_NO
+ >;
+ };
+
+ lower: layer_1 {
+ label = "LOWER";
+ keys = <
+ KC_MPLY KC_MNXT ZC_NO ZC_NO
+ ZC_TRNS ZC_TRNS ZC_NO ZC_NO
+ >;
+ };
+
+ raise: layer_2 {
+ label = "RAISE";
+ keys = <
+ KC_C KC_D ZC_NO ZC_NO
+ ZC_TRNS ZC_TRNS ZC_NO ZC_NO>;
+ };
+ };
+};
diff --git a/app/boards/shields/petejohanson_handwire/petejohanson_handwire.conf b/app/boards/shields/petejohanson_handwire/petejohanson_handwire.conf
new file mode 100644
index 0000000..63829ba
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/petejohanson_handwire.conf
@@ -0,0 +1 @@
+CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER=y
diff --git a/app/boards/shields/petejohanson_handwire/petejohanson_handwire.overlay b/app/boards/shields/petejohanson_handwire/petejohanson_handwire.overlay
new file mode 100644
index 0000000..d65b3b1
--- /dev/null
+++ b/app/boards/shields/petejohanson_handwire/petejohanson_handwire.overlay
@@ -0,0 +1,44 @@
+
+/ {
+ chosen {
+ zmk,kscan = &kscan0;
+ };
+
+ kscan0: kscan_0 {
+ compatible = "zmk,kscan-composite";
+ label = "KSCAN_COMP";
+ rows = <2>;
+ columns = <4>;
+
+ left {
+ kscan = <&left_hand>;
+ };
+
+ right {
+ kscan = <&right_hand>;
+ column-offset = <2>;
+ };
+ };
+
+ left_hand: kscan_1 {
+ compatible = "gpio-kscan";
+ label = "KSCAN_LEFT";
+
+ diode-direction = "row2col";
+ row-gpios = <&arduino_header 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
+ <&arduino_header 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+ col-gpios = <&arduino_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
+ <&arduino_header 11 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+ };
+
+ right_hand: kscan_2 {
+ compatible = "gpio-kscan";
+ label = "KSCAN_RIGHT";
+
+ diode-direction = "row2col";
+ row-gpios = <&arduino_header 12 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
+ <&arduino_header 13 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+ col-gpios = <&arduino_header 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
+ <&arduino_header 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+ };
+};
diff --git a/app/cmake/keymap.cmake b/app/cmake/keymap.cmake
new file mode 100644
index 0000000..2d3c629
--- /dev/null
+++ b/app/cmake/keymap.cmake
@@ -0,0 +1,94 @@
+
+get_property(cached_keymap_value CACHE KEYMAP PROPERTY VALUE)
+
+# There are actually 4 sources, the three user input sources, and the
+# previously used value (CACHED_KEYMAP). The previously used value has
+# precedence, and if we detect that the user is trying to change the
+# value we give him a warning about needing to clean the build
+# directory to be able to change keymaps.
+
+set(keymap_cli_argument ${cached_keymap_value}) # Either new or old
+if(keymap_cli_argument STREQUAL CACHED_KEYMAP)
+ # We already have a CACHED_KEYMAP so there is no new input on the CLI
+ unset(keymap_cli_argument)
+endif()
+
+set(keymap_app_cmake_lists ${KEYMAP})
+if(cached_keymap_value STREQUAL KEYMAP)
+ # The app build scripts did not set a default, The KEYMAP we are
+ # reading is the cached value from the CLI
+ unset(keymap_app_cmake_lists)
+endif()
+
+if(CACHED_KEYMAP)
+ # Warn the user if it looks like he is trying to change the keymap
+ # without cleaning first
+ if(keymap_cli_argument)
+ if(NOT (CACHED_KEYMAP STREQUAL keymap_cli_argument))
+ message(WARNING "The build directory must be cleaned pristinely when changing keymaps")
+ # TODO: Support changing keymaps without requiring a clean build
+ endif()
+ endif()
+
+ set(KEYMAP ${CACHED_KEYMAP})
+elseif(keymap_cli_argument)
+ set(KEYMAP ${keymap_cli_argument})
+
+elseif(DEFINED ENV{KEYMAP})
+ set(KEYMAP $ENV{KEYMAP})
+
+elseif(keymap_app_cmake_lists)
+ set(KEYMAP ${keymap_app_cmake_lists})
+
+else()
+ set(KEYMAP default)
+ message(STATUS "KEYMAP defaulted to 'default'")
+endif()
+
+message(STATUS "Keymap: ${KEYMAP}")
+
+# Store the selected keymap in the cache
+set(CACHED_KEYMAP ${KEYMAP} CACHE STRING "Selected keymap")
+
+set(ZMK_APP_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+list(APPEND KEYMAP_DIRS ${ZMK_APP_DIR}/keymaps)
+
+foreach(root ${BOARD_ROOT})
+ find_path(BOARD_DIR
+ NAMES ${BOARD}_defconfig
+ PATHS ${root}/boards/*/*
+ NO_DEFAULT_PATH
+ )
+ if(BOARD_DIR)
+ list(APPEND KEYMAP_DIRS ${BOARD_DIR}/keymaps)
+ endif()
+
+ if(DEFINED SHIELD)
+ find_path(shields_refs_list
+ NAMES ${SHIELD}.overlay
+ PATHS ${root}/boards/shields/*
+ NO_DEFAULT_PATH
+ )
+ foreach(shield_path ${shields_refs_list})
+ list(APPEND KEYMAP_DIRS ${shield_path}/keymaps)
+ endforeach()
+ endif()
+endforeach()
+
+find_path(BASE_KEYMAPS_DIR
+ NAMES ${KEYMAP}/keymap.overlay
+ PATHS ${KEYMAP_DIRS}
+ NO_DEFAULT_PATH
+)
+
+if (BASE_KEYMAPS_DIR)
+ set(KEYMAP_DIR "${BASE_KEYMAPS_DIR}/${KEYMAP}" CACHE STRING "Selected keymap directory")
+ message(STATUS "Using keymap directory: ${KEYMAP_DIR}/")
+ # Used to let local imports of custom keycodes work as expected
+ list(APPEND DTS_ROOT ${KEYMAP_DIR})
+ if (EXISTS "${KEYMAP_DIR}/include")
+ include_directories("${KEYMAP_DIR}/include")
+ endif()
+ set(DTC_OVERLAY_FILE ${KEYMAP_DIR}/keymap.overlay)
+endif()
diff --git a/app/drivers/zephyr/CMakeLists.txt b/app/drivers/zephyr/CMakeLists.txt
new file mode 100644
index 0000000..03a97c6
--- /dev/null
+++ b/app/drivers/zephyr/CMakeLists.txt
@@ -0,0 +1,9 @@
+if(CONFIG_ZMK_KSCAN_GPIO_DRIVER)
+ # Add hello_world_driver.h to the set of global include paths.
+ zephyr_include_directories(.)
+
+ zephyr_library()
+ zephyr_library_sources(
+ kscan_gpio.c
+ )
+endif()
diff --git a/app/drivers/zephyr/Kconfig b/app/drivers/zephyr/Kconfig
new file mode 100644
index 0000000..2dcb3a8
--- /dev/null
+++ b/app/drivers/zephyr/Kconfig
@@ -0,0 +1,11 @@
+config ZMK_KSCAN_GPIO_DRIVER
+ bool "Enable GPIO kscan driver to simulate key presses"
+ default y
+ select GPIO
+
+config ZMK_KSCAN_INIT_PRIORITY
+ int "Keyboard scan driver init priority"
+ default 40
+ help
+ Keyboard scan device driver initialization priority.
+
diff --git a/app/drivers/zephyr/dts/bindings/gpio-kscan.yaml b/app/drivers/zephyr/dts/bindings/gpio-kscan.yaml
new file mode 100644
index 0000000..0698c3e
--- /dev/null
+++ b/app/drivers/zephyr/dts/bindings/gpio-kscan.yaml
@@ -0,0 +1,25 @@
+# Copyright (c) 2020, Pete Johanson
+# SPDX-License-Identifier: Apache-2.0
+
+description: GPIO keyboard matrix controller
+
+compatible: "gpio-kscan"
+
+include: kscan.yaml
+
+properties:
+ row-gpios:
+ type: phandle-array
+ required: true
+ col-gpios:
+ type: phandle-array
+ required: true
+ debounce-period:
+ type: int
+ default: 5
+ diode-direction:
+ type: string
+ default: row2col
+ enum:
+ - row2col
+ - col2row
diff --git a/app/drivers/zephyr/kscan_gpio.c b/app/drivers/zephyr/kscan_gpio.c
new file mode 100644
index 0000000..a53e2f9
--- /dev/null
+++ b/app/drivers/zephyr/kscan_gpio.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT gpio_kscan
+
+#include <device.h>
+#include <drivers/kscan.h>
+#include <drivers/gpio.h>
+#include <logging/log.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+struct kscan_gpio_item_config
+{
+ char *label;
+ gpio_pin_t pin;
+ gpio_flags_t flags;
+};
+
+#define _KSCAN_GPIO_ITEM_CFG_INIT(n, prop, idx) \
+ { \
+ .label = DT_INST_GPIO_LABEL_BY_IDX(n, prop, idx), \
+ .pin = DT_INST_GPIO_PIN_BY_IDX(n, prop, idx), \
+ .flags = DT_INST_GPIO_FLAGS_BY_IDX(n, prop, idx), \
+ },
+
+static int kscan_gpio_config_interrupts(struct device **devices,
+ const struct kscan_gpio_item_config *configs,
+ size_t len, gpio_flags_t flags)
+{
+ for (int i = 0; i < len; i++)
+ {
+ struct device *dev = devices[i];
+ const struct kscan_gpio_item_config *cfg = &configs[i];
+
+ int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);
+
+ if (err)
+ {
+ LOG_ERR("Unable to enable matrix GPIO interrupt");
+ return err;
+ }
+ }
+
+ return 0;
+}
+#define INST_MATRIX_ROWS(n) DT_INST_PROP_LEN(n, row_gpios)
+#define INST_MATRIX_COLS(n) DT_INST_PROP_LEN(n, col_gpios)
+#define INST_OUTPUT_LEN(n) COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_COLS(n)), (INST_MATRIX_ROWS(n)))
+#define INST_INPUT_LEN(n) COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_ROWS(n)), (INST_MATRIX_COLS(n)))
+
+#define GPIO_INST_INIT(n) \
+ struct kscan_gpio_irq_callback_##n \
+ { \
+ struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) * work; \
+ struct gpio_callback callback; \
+ }; \
+ static struct kscan_gpio_irq_callback_##n \
+ irq_callbacks_##n[INST_INPUT_LEN(n)]; \
+ struct kscan_gpio_config_##n \
+ { \
+ struct kscan_gpio_item_config rows[INST_MATRIX_ROWS(n)]; \
+ struct kscan_gpio_item_config cols[INST_MATRIX_COLS(n)]; \
+ }; \
+ struct kscan_gpio_data_##n \
+ { \
+ kscan_callback_t callback; \
+ struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) work; \
+ bool matrix_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)]; \
+ struct device *rows[INST_MATRIX_ROWS(n)]; \
+ struct device *cols[INST_MATRIX_COLS(n)]; \
+ struct device *dev; \
+ }; \
+ static struct device **kscan_gpio_input_devices_##n(struct device *dev) \
+ { \
+ struct kscan_gpio_data_##n *data = dev->driver_data; \
+ return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->cols), (data->rows))); \
+ } \
+ static const struct kscan_gpio_item_config *kscan_gpio_input_configs_##n(struct device *dev) \
+ { \
+ const struct kscan_gpio_config_##n *cfg = dev->config_info; \
+ return ((COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->cols), (cfg->rows)))); \
+ } \
+ static struct device **kscan_gpio_output_devices_##n(struct device *dev) \
+ { \
+ struct kscan_gpio_data_##n *data = dev->driver_data; \
+ return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->rows), (data->cols))); \
+ } \
+ static const struct kscan_gpio_item_config *kscan_gpio_output_configs_##n(struct device *dev) \
+ { \
+ const struct kscan_gpio_config_##n *cfg = dev->config_info; \
+ return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->rows), (cfg->cols))); \
+ } \
+ static int kscan_gpio_enable_interrupts_##n(struct device *dev) \
+ { \
+ return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), kscan_gpio_input_configs_##n(dev), INST_INPUT_LEN(n), \
+ GPIO_INT_DEBOUNCE | GPIO_INT_EDGE_BOTH); \
+ } \
+ static int kscan_gpio_disable_interrupts_##n(struct device *dev) \
+ { \
+ return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), kscan_gpio_input_configs_##n(dev), INST_INPUT_LEN(n), \
+ GPIO_INT_DISABLE); \
+ } \
+ static void kscan_gpio_set_output_state_##n(struct device *dev, int value) \
+ { \
+ for (int i = 0; i < INST_OUTPUT_LEN(n); i++) \
+ { \
+ struct device *in_dev = kscan_gpio_output_devices_##n(dev)[i]; \
+ const struct kscan_gpio_item_config *cfg = &kscan_gpio_output_configs_##n(dev)[i]; \
+ gpio_pin_set(in_dev, cfg->pin, value); \
+ } \
+ } \
+ static void kscan_gpio_set_matrix_state_##n(bool state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)], u32_t input_index, u32_t output_index, bool value) \
+ { \
+ state[COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (output_index), (input_index))][COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (input_index), (output_index))] = value; \
+ } \
+ static int kscan_gpio_read_##n(struct device *dev) \
+ { \
+ struct kscan_gpio_data_##n *data = dev->driver_data; \
+ static bool read_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)]; \
+ LOG_DBG("Scanning the matrix for updated state"); \
+ /* Disable our interrupts temporarily while we scan, to avoid */ \
+ /* re-entry while we iterate columns and set them active one by one */ \
+ /* to get pressed state for each matrix cell. */ \
+ kscan_gpio_disable_interrupts_##n(dev); \
+ kscan_gpio_set_output_state_##n(dev, 0); \
+ for (int o = 0; o < INST_OUTPUT_LEN(n); o++) \
+ { \
+ struct device *out_dev = kscan_gpio_output_devices_##n(dev)[o]; \
+ const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o]; \
+ gpio_pin_set(out_dev, out_cfg->pin, 1); \
+ for (int i = 0; i < INST_INPUT_LEN(n); i++) \
+ { \
+ struct device *in_dev = kscan_gpio_input_devices_##n(dev)[i]; \
+ const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs_##n(dev)[i]; \
+ kscan_gpio_set_matrix_state_##n(read_state, i, o, gpio_pin_get(in_dev, in_cfg->pin) > 0); \
+ } \
+ gpio_pin_set(out_dev, out_cfg->pin, 0); \
+ } \
+ /* Set all our outputs as active again, then re-enable interrupts, */ \
+ /* so we can trigger interrupts again for future press/release */ \
+ kscan_gpio_set_output_state_##n(dev, 1); \
+ kscan_gpio_enable_interrupts_##n(dev); \
+ for (int r = 0; r < INST_MATRIX_ROWS(n); r++) \
+ { \
+ for (int c = 0; c < INST_MATRIX_COLS(n); c++) \
+ { \
+ bool pressed = read_state[r][c]; \
+ if (pressed != data->matrix_state[r][c]) \
+ { \
+ LOG_DBG("Sending event at %d,%d state %s", \
+ r, c, (pressed ? "on" : "off")); \
+ data->matrix_state[r][c] = pressed; \
+ data->callback(dev, r, c, pressed); \
+ } \
+ } \
+ } \
+ return 0; \
+ } \
+ static void kscan_gpio_work_handler_##n(struct k_work *work) \
+ { \
+ struct kscan_gpio_data_##n *data = \
+ CONTAINER_OF(work, struct kscan_gpio_data_##n, work); \
+ kscan_gpio_read_##n(data->dev); \
+ } \
+ static void kscan_gpio_irq_callback_handler_##n(struct device *dev, \
+ struct gpio_callback *cb, gpio_port_pins_t pin) \
+ { \
+ struct kscan_gpio_irq_callback_##n *data = \
+ CONTAINER_OF(cb, struct kscan_gpio_irq_callback_##n, callback); \
+ COND_CODE_0(DT_INST_PROP(n, debounce_period), \
+ ({ k_work_submit(data->work); }), \
+ ({ \
+ k_delayed_work_cancel(data->work); \
+ k_delayed_work_submit(data->work, K_MSEC(DT_INST_PROP(n, debounce_period))); })) \
+ } \
+ static struct kscan_gpio_data_##n kscan_gpio_data_##n; \
+ static int kscan_gpio_configure_##n(struct device *dev, kscan_callback_t callback) \
+ { \
+ struct kscan_gpio_data_##n *data = dev->driver_data; \
+ if (!callback) \
+ { \
+ return -EINVAL; \
+ } \
+ data->callback = callback; \
+ return 0; \
+ } \
+ static int kscan_gpio_init_##n(struct device *dev) \
+ { \
+ struct kscan_gpio_data_##n *data = dev->driver_data; \
+ int err; \
+ struct device **input_devices = kscan_gpio_input_devices_##n(dev); \
+ for (int i = 0; i < INST_INPUT_LEN(n); i++) \
+ { \
+ const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs_##n(dev)[i]; \
+ input_devices[i] = device_get_binding(in_cfg->label); \
+ if (!input_devices[i]) \
+ { \
+ LOG_ERR("Unable to find input GPIO device"); \
+ return -EINVAL; \
+ } \
+ err = gpio_pin_configure(input_devices[i], in_cfg->pin, GPIO_INPUT | in_cfg->flags); \
+ if (err) \
+ { \
+ LOG_ERR("Unable to configure pin %d on %s for input", in_cfg->pin, in_cfg->label); \
+ return err; \
+ } \
+ irq_callbacks_##n[i].work = &data->work; \
+ gpio_init_callback(&irq_callbacks_##n[i].callback, kscan_gpio_irq_callback_handler_##n, BIT(in_cfg->pin)); \
+ err = gpio_add_callback(input_devices[i], &irq_callbacks_##n[i].callback); \
+ if (err) \
+ { \
+ LOG_ERR("Error adding the callback to the column device"); \
+ return err; \
+ } \
+ } \
+ struct device **output_devices = kscan_gpio_output_devices_##n(dev); \
+ for (int o = 0; o < INST_OUTPUT_LEN(n); o++) \
+ { \
+ const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o]; \
+ output_devices[o] = device_get_binding(out_cfg->label); \
+ if (!output_devices[o]) \
+ { \
+ LOG_ERR("Unable to find output GPIO device"); \
+ return -EINVAL; \
+ } \
+ err = gpio_pin_configure(output_devices[o], out_cfg->pin, GPIO_OUTPUT_ACTIVE | out_cfg->flags); \
+ if (err) \
+ { \
+ LOG_ERR("Unable to configure pin %d on %s for output", out_cfg->pin, out_cfg->label); \
+ return err; \
+ } \
+ } \
+ data->dev = dev; \
+ (COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work_init), (k_delayed_work_init)))(&data->work, kscan_gpio_work_handler_##n); \
+ return 0; \
+ } \
+ static const struct kscan_driver_api gpio_driver_api_##n = { \
+ .config = kscan_gpio_configure_##n, \
+ .enable_callback = kscan_gpio_enable_interrupts_##n, \
+ .disable_callback = kscan_gpio_disable_interrupts_##n, \
+ }; \
+ static const struct kscan_gpio_config_##n kscan_gpio_config_##n = { \
+ .rows = { \
+ IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, row_gpios, 0, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, 0))) \
+ IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, row_gpios, 1, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, 1)))}, \
+ .cols = {IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, col_gpios, 0, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, 0))) IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, col_gpios, 1, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, 1)))}}; \
+ DEVICE_AND_API_INIT(kscan_gpio_##n, DT_INST_LABEL(n), kscan_gpio_init_##n, \
+ &kscan_gpio_data_##n, &kscan_gpio_config_##n, \
+ POST_KERNEL, CONFIG_ZMK_KSCAN_INIT_PRIORITY, \
+ &gpio_driver_api_##n);
+
+DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)
diff --git a/app/drivers/zephyr/module.yaml b/app/drivers/zephyr/module.yaml
new file mode 100644
index 0000000..cbff6a1
--- /dev/null
+++ b/app/drivers/zephyr/module.yaml
@@ -0,0 +1,3 @@
+build:
+ cmake: zephyr
+ kconfig: zephyr/Kconfig
diff --git a/app/dts/bindings/zmk,keymap.yaml b/app/dts/bindings/zmk,keymap.yaml
new file mode 100644
index 0000000..8c56f93
--- /dev/null
+++ b/app/dts/bindings/zmk,keymap.yaml
@@ -0,0 +1,13 @@
+description: |
+ Allows defining a keymap composed of multiple layers
+
+compatible: "zmk,keymap"
+
+properties:
+ label:
+ type: string
+ required: true
+
+ layers:
+ type: phandles
+ required: true
diff --git a/app/dts/bindings/zmk,kscan-composite.yaml b/app/dts/bindings/zmk,kscan-composite.yaml
new file mode 100644
index 0000000..6126c30
--- /dev/null
+++ b/app/dts/bindings/zmk,kscan-composite.yaml
@@ -0,0 +1,27 @@
+description: |
+ Allows composing multiple KSCAN devices into one virtual device
+
+compatible: "zmk,kscan-composite"
+
+properties:
+ label:
+ type: string
+ rows:
+ type: int
+ columns:
+ type: int
+
+child-binding:
+ description: "Details of an included KSCAN devices"
+
+ properties:
+ label:
+ type: string
+ kscan:
+ type: phandle
+ row-offset:
+ type: int
+ default: 0
+ column-offset:
+ type: int
+ default: 0
diff --git a/app/dts/bindings/zmk,kscan-mock.yaml b/app/dts/bindings/zmk,kscan-mock.yaml
new file mode 100644
index 0000000..41dd5b8
--- /dev/null
+++ b/app/dts/bindings/zmk,kscan-mock.yaml
@@ -0,0 +1,17 @@
+description: |
+ Allows defining a mock keyboard scan driver that simulates periodic events.
+
+compatible: "zmk,kscan-mock"
+
+properties:
+ label:
+ type: string
+ event-period:
+ type: int
+ description: Milliseconds between each generated event
+ events:
+ type: array
+ rows:
+ type: int
+ columns:
+ type: int
diff --git a/app/dts/bindings/zmk,layers.yaml b/app/dts/bindings/zmk,layers.yaml
new file mode 100644
index 0000000..c9b462a
--- /dev/null
+++ b/app/dts/bindings/zmk,layers.yaml
@@ -0,0 +1,14 @@
+description: |
+ Allows defining the various keymap layers for use.
+
+compatible: "zmk,layers"
+
+child-binding:
+ description: "A layer to be used in a keymap"
+
+ properties:
+ label:
+ type: string
+ keys:
+ type: array
+
diff --git a/app/include/dt-bindings/zmk/keys.h b/app/include/dt-bindings/zmk/keys.h
new file mode 100644
index 0000000..c91d27b
--- /dev/null
+++ b/app/include/dt-bindings/zmk/keys.h
@@ -0,0 +1,142 @@
+
+#pragma once
+
+#define KC_A 0x04
+#define KC_B 0x05
+#define KC_C 0x06
+#define KC_D 0x07
+#define KC_E 0x08
+#define KC_F 0x09
+#define KC_G 0x0A
+#define KC_H 0x0B
+#define KC_I 0x0C
+#define KC_J 0x0D
+#define KC_K 0x0E
+#define KC_L 0x0F
+#define KC_M 0x10
+#define KC_N 0x11
+#define KC_O 0x12
+#define KC_P 0x13
+#define KC_Q 0x14
+#define KC_R 0x15
+#define KC_S 0x16
+#define KC_T 0x17
+#define KC_U 0x18
+#define KC_V 0x19
+#define KC_W 0x1A
+#define KC_X 0x1B
+#define KC_Y 0x1C
+#define KC_Z 0x1D
+#define KC_1 0x1E
+#define KC_2 0x1F
+#define KC_3 0x20
+#define KC_4 0x21
+#define KC_5 0x22
+#define KC_6 0x23
+#define KC_7 0x24
+#define KC_8 0x25
+#define KC_9 0x26
+#define KC_0 0x27
+#define KC_RET 0x28
+#define KC_ESC 0x29
+#define KC_DEL 0x2A
+#define KC_BKSP KC_DEL
+#define KC_TAB 0x2B
+#define KC_SPC 0x2C
+#define KC_MIN 0x2D
+#define KC_EQL 0x2E
+#define KC_LBKT 0x2F
+#define KC_RBKT 0x30
+#define KC_FSLH 0x31
+
+#define KC_SCLN 0x33
+#define KC_QUOT 0x34
+#define KC_GRAV 0x35
+#define KC_CMMA 0x36
+#define KC_DOT 0x37
+#define KC_BSLH 0x38
+#define KC_CLCK 0x39
+#define KC_F1 0x3A
+#define KC_F2 0x3B
+
+#define KC_RARW 0x4F
+#define KC_LARW 0x50
+#define KC_DARW 0x51
+#define KC_UARW 0x52
+
+#define KC_KDIV 0x54
+#define KC_KMLT 0x55
+#define KC_KMIN 0x56
+#define KC_KPLS 0x57
+
+#define KC_APP 0x65
+
+#define KC_CURU 0xB4
+
+#define KC_LPRN 0xB6
+#define KC_RPRN 0xB7
+#define KC_LCUR 0xB8
+#define KC_RCUR 0xB9
+
+#define KC_CRRT 0xC3
+#define KC_PRCT 0xC4
+#define KC_LABT 0xC5
+#define KC_RABT 0xC6
+#define KC_AMPS 0xC7
+#define KC_PIPE 0xC9
+#define KC_COLN 0xCB
+#define KC_HASH 0xCC
+#define KC_KSPC 0xCD
+#define KC_ATSN 0xCE
+#define KC_BANG 0xCF
+
+#define KC_LCTL 0xE0
+#define KC_LSFT 0xE1
+#define KC_LALT 0xE2
+#define KC_LGUI 0xE3
+#define KC_RCTL 0xE4
+#define KC_RSFT 0xE5
+#define KC_RALT 0xE6
+#define KC_RGUI 0xE7
+
+#define KC_VOLU 0x80
+#define KC_VOLD 0x81
+
+/* The following are select consumer page usages */
+
+#define KC_MNXT 0x100
+#define KC_MPRV 0x101
+#define KC_MSTP 0x102
+#define KC_MJCT 0x103
+#define KC_MPLY 0x104
+#define KC_MMUT 0x105
+#define KC_MVLU 0x106
+#define KC_MVLD 0x107
+
+#define ZC_TRNS (0xFFFF)
+#define ZC_NO (0xFFFF - 1)
+
+#define ZC_CSTM(n) (0xFFF + n)
+
+#define MOD_LCTL (1 << 0x00)
+#define MOD_LSFT (1 << 0x01)
+#define MOD_LALT (1 << 0x02)
+#define MOD_LGUI (1 << 0x03)
+#define MOD_RCTL (1 << 0x04)
+#define MOD_RSFT (1 << 0x05)
+#define MOD_RALT (1 << 0x06)
+#define MOD_RGUI (1 << 0x07)
+
+#define ZK_ACTION(k) (k >> 24)
+#define _ACTION(a) (a << 24)
+#define _ACTION_MODS(m) (m << 16)
+#define ZK_KEY(a) (a & 0xFFFF)
+#define ZK_MODS(a) ((a >> 16) & 0xFF)
+
+#define ZK_IS_CONSUMER(k) (ZK_KEY(k) >= 0x100)
+
+#define ZMK_ACTION_KEY 0x01
+#define ZMK_ACTION_MOD_TAP 0x01
+#define ZMK_ACTION_ONE_SHOT 0x02
+
+#define MT(mods, kc) (_ACTION(ZMK_ACTION_MOD_TAP) + _ACTION_MODS(mods) + kc)
diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h
new file mode 100644
index 0000000..a77237d
--- /dev/null
+++ b/app/include/zmk/ble.h
@@ -0,0 +1,7 @@
+
+#pragma once
+
+#include <zmk/keys.h>
+
+int zmk_ble_init();
+bool zmk_ble_handle_key_user(struct zmk_key_event *key_event);
diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h
new file mode 100644
index 0000000..255ca54
--- /dev/null
+++ b/app/include/zmk/endpoints.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <zmk/keys.h>
+#include <zmk/hid.h>
+
+int zmk_endpoints_init();
+int zmk_endpoints_send_report(enum zmk_hid_report_changes changes);
+int zmk_endpoints_send_key_event(struct zmk_key_event key_event);
diff --git a/app/include/zmk/handlers.h b/app/include/zmk/handlers.h
new file mode 100644
index 0000000..a10851f
--- /dev/null
+++ b/app/include/zmk/handlers.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <dt-bindings/zmk/keys.h>
+
+#include <zmk/keymap.h>
+#include <zmk/keys.h>
+
+void zmk_handle_key(struct zmk_key_event key_event);
diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h
new file mode 100644
index 0000000..7e3560d
--- /dev/null
+++ b/app/include/zmk/hid.h
@@ -0,0 +1,194 @@
+#pragma once
+
+#include <usb/usb_device.h>
+#include <usb/class/usb_hid.h>
+
+#include <dt-bindings/zmk/keys.h>
+
+#include <zmk/keys.h>
+
+#define COLLECTION_REPORT 0x03
+
+#define ZMK_HID_MAX_KEYCODE KC_APP
+
+static const u8_t zmk_hid_report_desc[] = {
+ /* USAGE_PAGE (Generic Desktop) */
+ HID_GI_USAGE_PAGE,
+ USAGE_GEN_DESKTOP,
+ /* USAGE (Keyboard) */
+ HID_LI_USAGE,
+ USAGE_GEN_DESKTOP_KEYBOARD,
+ /* COLLECTION (Application) */
+ HID_MI_COLLECTION,
+ COLLECTION_APPLICATION,
+ /* REPORT ID (1) */
+ HID_GI_REPORT_ID,
+ 0x01,
+ /* USAGE_PAGE (Keypad) */
+ HID_GI_USAGE_PAGE,
+ USAGE_GEN_DESKTOP_KEYPAD,
+ /* USAGE_MINIMUM (Keyboard LeftControl) */
+ HID_LI_USAGE_MIN(1),
+ 0xE0,
+ /* USAGE_MAXIMUM (Keyboard Right GUI) */
+ HID_LI_USAGE_MAX(1),
+ 0xE7,
+ /* LOGICAL_MINIMUM (0) */
+ HID_GI_LOGICAL_MIN(1),
+ 0x00,
+ /* LOGICAL_MAXIMUM (1) */
+ HID_GI_LOGICAL_MAX(1),
+ 0x01,
+
+ /* REPORT_SIZE (1) */
+ HID_GI_REPORT_SIZE,
+ 0x01,
+ /* REPORT_COUNT (8) */
+ HID_GI_REPORT_COUNT,
+ 0x08,
+ /* INPUT (Data,Var,Abs) */
+ HID_MI_INPUT,
+ 0x02,
+
+ /* USAGE_PAGE (Keypad) */
+ HID_GI_USAGE_PAGE,
+ USAGE_GEN_DESKTOP_KEYPAD,
+ /* LOGICAL_MINIMUM (0) */
+ HID_GI_LOGICAL_MIN(1),
+ 0x00,
+ /* LOGICAL_MAXIMUM (1) */
+ HID_GI_LOGICAL_MAX(1),
+ 0x01,
+ /* USAGE_MINIMUM (Reserved) */
+ HID_LI_USAGE_MIN(1),
+ 0x00,
+ /* USAGE_MAXIMUM (Keyboard Application) */
+ HID_LI_USAGE_MAX(1),
+ ZMK_HID_MAX_KEYCODE,
+ /* REPORT_SIZE (8) */
+ HID_GI_REPORT_SIZE,
+ 0x01,
+ /* REPORT_COUNT (6) */
+ HID_GI_REPORT_COUNT,
+ ZMK_HID_MAX_KEYCODE + 1,
+ /* INPUT (Data,Ary,Abs) */
+ HID_MI_INPUT,
+ 0x02,
+ /* USAGE_PAGE (Keypad) */
+ HID_GI_USAGE_PAGE,
+ USAGE_GEN_DESKTOP_KEYPAD,
+ /* REPORT_SIZE (8) */
+ HID_GI_REPORT_SIZE,
+ 0x02,
+ /* REPORT_COUNT (6) */
+ HID_GI_REPORT_COUNT,
+ 0x01,
+ /* INPUT (Cnst,Var,Abs) */
+ HID_MI_INPUT,
+ 0x03,
+ /* END_COLLECTION */
+ HID_MI_COLLECTION_END,
+ /* USAGE_PAGE (Consumer) */
+ HID_GI_USAGE_PAGE,
+ 0x0C,
+ /* USAGE (Consumer Control) */
+ HID_LI_USAGE,
+ 0x01,
+ /* Consumer Page */
+ HID_MI_COLLECTION,
+ COLLECTION_APPLICATION,
+ /* REPORT ID (1) */
+ HID_GI_REPORT_ID,
+ 0x02,
+ /* USAGE_PAGE (Consumer) */
+ HID_GI_USAGE_PAGE,
+ 0x0C,
+ /* LOGICAL_MINIMUM (0) */
+ HID_GI_LOGICAL_MIN(1),
+ 0x00,
+ /* LOGICAL_MAXIMUM (1) */
+ HID_GI_LOGICAL_MAX(1),
+ 0x01,
+ /* USAGE (Scan Next Track) */
+ HID_LI_USAGE,
+ 0xB5,
+ /* USAGE (Scan Previous Track) */
+ HID_LI_USAGE,
+ 0xB6,
+ /* USAGE (Stop) */
+ HID_LI_USAGE,
+ 0xB7,
+ /* USAGE (Eject) */
+ HID_LI_USAGE,
+ 0xB8,
+ /* USAGE (Media Play/Pause) */
+ HID_LI_USAGE,
+ 0xCD,
+ /* USAGE (Mute) */
+ HID_LI_USAGE,
+ 0xE2,
+ /* USAGE (Volume Increment) */
+ HID_LI_USAGE,
+ 0xE9,
+ /* USAGE (Volume Decrement) */
+ HID_LI_USAGE,
+ 0xEA,
+ /* INPUT (Data,Ary,Abs) */
+ /* REPORT_SIZE (1) */
+ HID_GI_REPORT_SIZE,
+ 0x01,
+ /* REPORT_COUNT (8) */
+ HID_GI_REPORT_COUNT,
+ 0x08,
+ HID_MI_INPUT,
+ 0x02,
+ /* END COLLECTION */
+ HID_MI_COLLECTION_END,
+};
+
+// struct zmk_hid_boot_report
+// {
+// u8_t modifiers;
+// u8_t _unused;
+// u8_t keys[6];
+// } __packed;
+
+struct zmk_hid_keypad_report_body
+{
+ zmk_mod_flags modifiers;
+ u8_t keys[13];
+} __packed;
+
+struct zmk_hid_keypad_report
+{
+ u8_t report_id;
+ struct zmk_hid_keypad_report_body body;
+} __packed;
+
+struct zmk_hid_consumer_report_body
+{
+ u8_t keys;
+} __packed;
+
+struct zmk_hid_consumer_report
+{
+ u8_t report_id;
+ struct zmk_hid_consumer_report_body body;
+} __packed;
+
+enum zmk_hid_report_changes
+{
+ None = 0x00,
+ Keypad = (0x01 << 0x00),
+ Consumer = (0x01 << 0x01)
+};
+
+int zmk_hid_register_mod(zmk_mod modifier);
+int zmk_hid_unregister_mod(zmk_mod modifier);
+int zmk_hid_register_mods(zmk_mod_flags modifiers);
+int zmk_hid_unregister_mods(zmk_mod_flags modifiers);
+enum zmk_hid_report_changes zmk_hid_press_key(zmk_key key);
+enum zmk_hid_report_changes zmk_hid_release_key(zmk_key key);
+
+struct zmk_hid_keypad_report *zmk_hid_get_keypad_report();
+struct zmk_hid_consumer_report *zmk_hid_get_consumer_report();
diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h
new file mode 100644
index 0000000..e2f976a
--- /dev/null
+++ b/app/include/zmk/hog.h
@@ -0,0 +1,10 @@
+
+#pragma once
+
+#include <zmk/keys.h>
+#include <zmk/hid.h>
+
+int zmk_hog_init();
+
+int zmk_hog_send_keypad_report(struct zmk_hid_keypad_report_body *body);
+int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body);
diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h
new file mode 100644
index 0000000..285b071
--- /dev/null
+++ b/app/include/zmk/keymap.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <devicetree.h>
+#include <usb/usb_device.h>
+#include <usb/class/usb_hid.h>
+#include <dt-bindings/zmk/keys.h>
+
+#include <zmk/matrix.h>
+#include <zmk/keys.h>
+
+#define ZMK_KEYMAP_NODE DT_CHOSEN(zmk_keymap)
+#define ZMK_KEYMAP_LAYERS_LEN DT_PROP_LEN(ZMK_KEYMAP_NODE, layers)
+
+/* TODO: Need to actually be able to get a NODELABEL from a node id
+#define _ZMK_KEYMAP_GENERATE_LAYER_CONST(node_id) \
+ DT_NODELABEL_FOR_NODE(node_id)_layer,
+
+enum zmk_keymap_layer
+{
+ DT_FOREACH_CHILD(DT_INST(0, zmk_layers), _ZMK_KEYMAP_GENERATE_LAYER_CONST)
+};
+*/
+
+bool zmk_keymap_layer_activate(u8_t layer);
+bool zmk_keymap_layer_deactivate(u8_t layer);
+
+zmk_key
+zmk_keymap_keycode_from_position(u32_t row, u32_t column);
diff --git a/app/include/zmk/keys.h b/app/include/zmk/keys.h
new file mode 100644
index 0000000..6966bba
--- /dev/null
+++ b/app/include/zmk/keys.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <zephyr.h>
+#include <dt-bindings/zmk/keys.h>
+
+typedef u32_t zmk_key;
+typedef u8_t zmk_action;
+typedef u8_t zmk_mod;
+typedef u8_t zmk_mod_flags;
+
+struct zmk_key_event
+{
+ u32_t column;
+ u32_t row;
+ zmk_key key;
+ bool pressed;
+}; \ No newline at end of file
diff --git a/app/include/zmk/kscan-mock.h b/app/include/zmk/kscan-mock.h
new file mode 100644
index 0000000..d481899
--- /dev/null
+++ b/app/include/zmk/kscan-mock.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#define ZMK_MOCK_IS_PRESS(v) ((v & (0x01 << 31)) != 0)
+#define ZMK_MOCK_PRESS(row, col, msec) (row + (col << 8) + (msec << 16) + (0x01 << 31))
+#define ZMK_MOCK_RELEASE(row, col, msec) (row + (col << 8) + (msec << 16))
+#define ZMK_MOCK_ROW(v) (v & 0xFF)
+#define ZMK_MOCK_COL(v) ((v >> 8) & 0xFF)
+#define ZMK_MOCK_MSEC(v) ((v & ~(0x01 << 31)) >> 16)
diff --git a/app/include/zmk/kscan.h b/app/include/zmk/kscan.h
new file mode 100644
index 0000000..ebffccd
--- /dev/null
+++ b/app/include/zmk/kscan.h
@@ -0,0 +1,3 @@
+#pragma once
+
+int zmk_kscan_init(char *name);
diff --git a/app/include/zmk/matrix.h b/app/include/zmk/matrix.h
new file mode 100644
index 0000000..783c98e
--- /dev/null
+++ b/app/include/zmk/matrix.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#define ZMK_MATRIX_NODE_ID DT_CHOSEN(zmk_kscan)
+
+#if DT_NODE_HAS_PROP(ZMK_MATRIX_NODE_ID,row_gpios)
+#define ZMK_MATRIX_ROWS DT_PROP_LEN(ZMK_MATRIX_NODE_ID,row_gpios)
+#define ZMK_MATRIX_COLS DT_PROP_LEN(ZMK_MATRIX_NODE_ID,col_gpios)
+#else
+#define ZMK_MATRIX_ROWS DT_PROP(ZMK_MATRIX_NODE_ID,rows)
+#define ZMK_MATRIX_COLS DT_PROP(ZMK_MATRIX_NODE_ID,columns)
+#endif
+
diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h
new file mode 100644
index 0000000..5d7c744
--- /dev/null
+++ b/app/include/zmk/usb_hid.h
@@ -0,0 +1,14 @@
+#ifndef ZMK_USB_HID
+#define ZMK_USB_HID
+
+#include <usb/usb_device.h>
+#include <usb/class/usb_hid.h>
+
+#include <zmk/keys.h>
+#include <zmk/hid.h>
+
+int zmk_usb_hid_init();
+
+int zmk_usb_hid_send_report(u8_t *report, size_t len);
+
+#endif
diff --git a/app/prj.conf b/app/prj.conf
new file mode 100644
index 0000000..c4d6d35
--- /dev/null
+++ b/app/prj.conf
@@ -0,0 +1,2 @@
+# CONFIG_LOG=y
+# CONFIG_ZMK_LOG_LEVEL_DBG=y
diff --git a/app/src/ble.c b/app/src/ble.c
new file mode 100644
index 0000000..51607c6
--- /dev/null
+++ b/app/src/ble.c
@@ -0,0 +1,193 @@
+
+#include <math.h>
+
+#include <settings/settings.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/conn.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/gatt.h>
+
+#include <zmk/keys.h>
+
+static struct bt_conn *auth_passkey_entry_conn;
+static u8_t passkey_entries[6] = {0, 0, 0, 0, 0, 0};
+static u8_t passkey_digit = 0;
+
+static void connected(struct bt_conn *conn, u8_t err)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ if (err)
+ {
+ printk("Failed to connect to %s (%u)\n", addr, err);
+ return;
+ }
+
+ printk("Connected %s\n", addr);
+
+ if (bt_conn_set_security(conn, BT_SECURITY_L2))
+ {
+ printk("Failed to set security\n");
+ }
+}
+
+static void disconnected(struct bt_conn *conn, u8_t reason)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ printk("Disconnected from %s (reason 0x%02x)\n", addr, reason);
+}
+
+static void security_changed(struct bt_conn *conn, bt_security_t level,
+ enum bt_security_err err)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ if (!err)
+ {
+ printk("Security changed: %s level %u\n", addr, level);
+ }
+ else
+ {
+ printk("Security failed: %s level %u err %d\n", addr, level,
+ err);
+ }
+}
+
+static struct bt_conn_cb conn_callbacks = {
+ .connected = connected,
+ .disconnected = disconnected,
+ .security_changed = security_changed,
+};
+
+static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ printk("Passkey for %s: %06u\n", addr, passkey);
+}
+
+#ifdef CONFIG_ZMK_BLE_PASSKEY_ENTRY
+
+static void auth_passkey_entry(struct bt_conn *conn)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ printk("Passkey entry requested for %s\n", addr);
+ auth_passkey_entry_conn = bt_conn_ref(conn);
+}
+
+#endif
+
+static void auth_cancel(struct bt_conn *conn)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ if (auth_passkey_entry_conn)
+ {
+ bt_conn_unref(auth_passkey_entry_conn);
+ auth_passkey_entry_conn = NULL;
+ }
+
+ passkey_digit = 0;
+
+ printk("Pairing cancelled: %s\n", addr);
+}
+
+static struct bt_conn_auth_cb zmk_ble_auth_cb_display = {
+// .passkey_display = auth_passkey_display,
+
+#ifdef CONFIG_ZMK_BLE_PASSKEY_ENTRY
+ .passkey_entry = auth_passkey_entry,
+#endif
+ .cancel = auth_cancel,
+};
+
+static const struct bt_data zmk_ble_ad[] = {
+ BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
+ BT_DATA_BYTES(BT_DATA_UUID16_ALL,
+ 0x12, 0x18, /* HID Service */
+ 0x0f, 0x18), /* Battery Service */
+};
+
+static void zmk_ble_ready(int err)
+{
+ if (err)
+ {
+ printk("Bluetooth init failed (err %d)\n", err);
+ return;
+ }
+
+ err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0);
+ if (err)
+ {
+ printk("Advertising failed to start (err %d)\n", err);
+ return;
+ }
+}
+
+int zmk_ble_init()
+{
+ if (IS_ENABLED(CONFIG_SETTINGS))
+ {
+ settings_load();
+ }
+ int err = bt_enable(zmk_ble_ready);
+
+ if (err)
+ {
+ printk("BLUETOOTH FAILED");
+ return err;
+ }
+
+ bt_conn_cb_register(&conn_callbacks);
+ bt_conn_auth_cb_register(&zmk_ble_auth_cb_display);
+
+ return 0;
+}
+
+bool zmk_ble_handle_key_user(struct zmk_key_event *key_event)
+{
+ zmk_key key = key_event->key;
+
+ if (!auth_passkey_entry_conn)
+ {
+ return true;
+ }
+
+ if (key < KC_1 || key > KC_0)
+ {
+ return true;
+ }
+
+ u32_t val = (key == KC_0) ? 0 : (key - KC_1 + 1);
+
+ passkey_entries[passkey_digit++] = val;
+
+ if (passkey_digit == 6)
+ {
+ u32_t passkey = 0;
+ for (int i = 5; i >= 0; i--)
+ {
+ passkey = (passkey * 10) + val;
+ }
+ bt_conn_auth_passkey_entry(auth_passkey_entry_conn, passkey);
+ bt_conn_unref(auth_passkey_entry_conn);
+ auth_passkey_entry_conn = NULL;
+ }
+
+ return false;
+}
diff --git a/app/src/endpoints.c b/app/src/endpoints.c
new file mode 100644
index 0000000..f46d42d
--- /dev/null
+++ b/app/src/endpoints.c
@@ -0,0 +1,105 @@
+
+#include <zmk/endpoints.h>
+#include <zmk/hid.h>
+#include <zmk/usb_hid.h>
+#include <zmk/hog.h>
+
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+int zmk_endpoints_init()
+{
+ int err;
+
+ LOG_DBG("");
+
+#ifdef CONFIG_ZMK_USB
+ err = zmk_usb_hid_init();
+ if (err)
+ {
+ LOG_ERR("USB HID Init Failed\n");
+ return err;
+ }
+#endif /* CONFIG_ZMK_USB */
+
+#ifdef CONFIG_ZMK_BLE
+ err = zmk_hog_init();
+ if (err)
+ {
+ LOG_ERR("HOG Init Failed\n");
+ return err;
+ }
+
+#endif /* CONFIG_ZMK_BLE */
+
+ return 0;
+}
+
+int zmk_endpoints_send_report(enum zmk_hid_report_changes report_type)
+{
+ int err;
+ struct zmk_hid_keypad_report *keypad_report;
+ struct zmk_hid_consumer_report *consumer_report;
+ switch (report_type)
+ {
+ case Keypad:
+ keypad_report = zmk_hid_get_keypad_report();
+#ifdef CONFIG_ZMK_USB
+ if (zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(struct zmk_hid_keypad_report)) != 0)
+ {
+ LOG_DBG("USB Send Failed");
+ }
+#endif /* CONFIG_ZMK_USB */
+
+#ifdef CONFIG_ZMK_BLE
+ err = zmk_hog_send_keypad_report(&keypad_report->body);
+ if (err)
+ {
+ LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
+ }
+#endif /* CONFIG_ZMK_BLE */
+
+ break;
+ case Consumer:
+ consumer_report = zmk_hid_get_consumer_report();
+#ifdef CONFIG_ZMK_USB
+ if (zmk_usb_hid_send_report((u8_t *)consumer_report, sizeof(struct zmk_hid_consumer_report)) != 0)
+ {
+ LOG_DBG("USB Send Failed");
+ }
+#endif /* CONFIG_ZMK_USB */
+
+#ifdef CONFIG_ZMK_BLE
+ err = zmk_hog_send_consumer_report(&consumer_report->body);
+ if (err)
+ {
+ LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
+ }
+#endif /* CONFIG_ZMK_BLE */
+
+ break;
+ default:
+ LOG_ERR("Unknown report change type %d", report_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int zmk_endpoints_send_key_event(struct zmk_key_event key_event)
+{
+ enum zmk_hid_report_changes changes;
+
+ LOG_DBG("key %d, state %d\n", key_event.key, key_event.pressed);
+
+ if (key_event.pressed)
+ {
+ changes = zmk_hid_press_key(key_event.key);
+ }
+ else
+ {
+ changes = zmk_hid_release_key(key_event.key);
+ }
+
+ return zmk_endpoints_send_report(changes);
+}
diff --git a/app/src/handlers.c b/app/src/handlers.c
new file mode 100644
index 0000000..464b1ab
--- /dev/null
+++ b/app/src/handlers.c
@@ -0,0 +1,88 @@
+
+#include <zmk/ble.h>
+#include <zmk/handlers.h>
+#include <zmk/endpoints.h>
+#include <zmk/hid.h>
+#include <zmk/matrix.h>
+
+#ifdef CONFIG_ZMK_ACTION_MOD_TAP
+u16_t action_effect_pending = 0;
+#endif
+
+__attribute__((weak)) bool zmk_handle_key_user(struct zmk_key_event *key_event)
+{
+ return true;
+};
+
+bool zmk_handle_action(zmk_action action, struct zmk_key_event *key_event)
+{
+ zmk_mod mods = ZK_MODS(key_event->key);
+ u8_t flattened_index = (key_event->row * ZMK_MATRIX_COLS) + key_event->column;
+ switch (action)
+ {
+#ifdef CONFIG_ZMK_ACTION_MOD_TAP
+ case ZMK_ACTION_MOD_TAP:
+ if (key_event->pressed)
+ {
+ WRITE_BIT(action_effect_pending, flattened_index, true);
+ zmk_hid_register_mods(mods);
+ }
+ else
+ {
+ zmk_hid_unregister_mods(mods);
+ if (action_effect_pending & BIT(flattened_index))
+ {
+ struct zmk_key_event non_mod_event =
+ {
+ .row = key_event->row,
+ .column = key_event->column,
+ .key = ZK_KEY(key_event->key),
+ .pressed = true};
+
+ zmk_handle_key(non_mod_event);
+ // A small sleep is needed to ensure device layer sends initial
+ // key, before we send the release.
+ k_msleep(10);
+ non_mod_event.pressed = false;
+ zmk_handle_key(non_mod_event);
+ }
+ else
+ {
+ // Since not sending a keycode, at least send the report w/ the mod removed
+ zmk_endpoints_send_report(Keypad);
+ }
+ }
+ break;
+#endif
+ }
+ return false;
+};
+
+void zmk_handle_key(struct zmk_key_event key_event)
+{
+ zmk_action action = ZK_ACTION(key_event.key);
+
+ if (!zmk_handle_key_user(&key_event))
+ {
+ return;
+ }
+
+ if (action && !zmk_handle_action(action, &key_event))
+ {
+ return;
+ }
+
+#ifdef CONFIG_ZMK_ACTION_MOD_TAP
+ action_effect_pending = 0;
+#endif
+
+#ifdef CONFIG_ZMK_BLE
+ /* Used for intercepting key presses when doing passkey verification */
+ if (!zmk_ble_handle_key_user(&key_event))
+ {
+ return;
+ }
+#endif /* CONFIG_ZMK_BLE */
+
+ zmk_endpoints_send_key_event(key_event);
+};
diff --git a/app/src/hid.c b/app/src/hid.c
new file mode 100644
index 0000000..b3d47cf
--- /dev/null
+++ b/app/src/hid.c
@@ -0,0 +1,130 @@
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/hid.h>
+
+static struct zmk_hid_keypad_report kp_report = {
+ .report_id = 1,
+ .body = {
+ .modifiers = 0,
+ .keys = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}};
+
+static struct zmk_hid_consumer_report consumer_report = {
+ .report_id = 2,
+ .body = {
+ .keys = 0x00}};
+
+#define _TOGGLE_MOD(mod, state) \
+ if (modifier > MOD_RGUI) \
+ { \
+ return -EINVAL; \
+ } \
+ WRITE_BIT(kp_report.body.modifiers, mod, state); \
+ return 0;
+
+int zmk_hid_register_mod(zmk_mod modifier)
+{
+ _TOGGLE_MOD(modifier, true);
+}
+int zmk_hid_unregister_mod(zmk_mod modifier)
+{
+ _TOGGLE_MOD(modifier, false);
+}
+
+int zmk_hid_register_mods(zmk_mod_flags modifiers)
+{
+ kp_report.body.modifiers |= modifiers;
+ return 0;
+}
+
+int zmk_hid_unregister_mods(zmk_mod_flags modifiers)
+{
+ kp_report.body.modifiers &= ~modifiers;
+ return 0;
+}
+
+#define KEY_OFFSET 0x02
+#define MAX_KEYS 6
+
+/*
+#define TOGGLE_BOOT_KEY(match, val) \
+ for (int idx = 0; idx < MAX_KEYS; idx++) \
+ { \
+ if (kp_report.boot.keys[idx + KEY_OFFSET] != match) \
+ { \
+ continue; \
+ } \
+ kp_report.boot.keys[idx + KEY_OFFSET] = val; \
+ break; \
+ }
+*/
+
+#define TOGGLE_KEY(code, val) WRITE_BIT(kp_report.body.keys[code / 8], code % 8, val)
+
+#define TOGGLE_CONSUMER(key, state) \
+ WRITE_BIT(consumer_report.body.keys, (key - 0x100), state);
+
+enum zmk_hid_report_changes zmk_hid_press_key(zmk_key code)
+{
+ if (code >= KC_LCTL && code <= KC_RGUI)
+ {
+ return zmk_hid_register_mod(code - KC_LCTL);
+ }
+
+ if (ZK_IS_CONSUMER(code))
+ {
+ LOG_DBG("Toggling a consumer key!");
+ TOGGLE_CONSUMER(code, true);
+ return Consumer;
+ }
+ else
+ {
+ if (code > ZMK_HID_MAX_KEYCODE)
+ {
+ return -EINVAL;
+ }
+
+ // TOGGLE_BOOT_KEY(0U, code);
+
+ TOGGLE_KEY(code, true);
+
+ return Keypad;
+ }
+};
+
+enum zmk_hid_report_changes zmk_hid_release_key(zmk_key code)
+{
+ if (code >= KC_LCTL && code <= KC_RGUI)
+ {
+ return zmk_hid_unregister_mod(code - KC_LCTL);
+ }
+
+ if (ZK_IS_CONSUMER(code))
+ {
+ TOGGLE_CONSUMER(code, false);
+ return Consumer;
+ }
+ else
+ {
+ if (code > ZMK_HID_MAX_KEYCODE)
+ {
+ return -EINVAL;
+ }
+
+ // TOGGLE_BOOT_KEY(0U, code);
+
+ TOGGLE_KEY(code, false);
+
+ return Keypad;
+ }
+};
+
+struct zmk_hid_keypad_report *zmk_hid_get_keypad_report()
+{
+ return &kp_report;
+}
+
+struct zmk_hid_consumer_report *zmk_hid_get_consumer_report()
+{
+ return &consumer_report;
+}
diff --git a/app/src/hog.c b/app/src/hog.c
new file mode 100644
index 0000000..087af42
--- /dev/null
+++ b/app/src/hog.c
@@ -0,0 +1,158 @@
+#include <settings/settings.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/gatt.h>
+
+#include <zmk/ble.h>
+#include <zmk/hog.h>
+#include <zmk/hid.h>
+
+int zmk_hog_init()
+{
+ return zmk_ble_init();
+}
+
+enum
+{
+ HIDS_REMOTE_WAKE = BIT(0),
+ HIDS_NORMALLY_CONNECTABLE = BIT(1),
+};
+
+struct hids_info
+{
+ u16_t version; /* version number of base USB HID Specification */
+ u8_t code; /* country HID Device hardware is localized for. */
+ u8_t flags;
+} __packed;
+
+struct hids_report
+{
+ u8_t id; /* report id */
+ u8_t type; /* report type */
+} __packed;
+
+static struct hids_info info = {
+ .version = 0x0000,
+ .code = 0x00,
+ .flags = HIDS_NORMALLY_CONNECTABLE & HIDS_REMOTE_WAKE,
+};
+
+enum
+{
+ HIDS_INPUT = 0x01,
+ HIDS_OUTPUT = 0x02,
+ HIDS_FEATURE = 0x03,
+};
+
+static struct hids_report input = {
+ .id = 0x01,
+ .type = HIDS_INPUT,
+};
+
+static struct hids_report consumer_input = {
+ .id = 0x02,
+ .type = HIDS_INPUT,
+};
+
+static bool host_requests_notification = false;
+static u8_t ctrl_point;
+// static u8_t proto_mode;
+
+static ssize_t read_hids_info(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, u16_t len, u16_t offset)
+{
+ return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, sizeof(struct hids_info));
+}
+
+static ssize_t read_hids_report_ref(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, u16_t len, u16_t offset)
+{
+ return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, sizeof(struct hids_report));
+}
+
+static ssize_t read_hids_report_map(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, u16_t len, u16_t offset)
+{
+ return bt_gatt_attr_read(conn, attr, buf, len, offset, zmk_hid_report_desc, sizeof(zmk_hid_report_desc));
+}
+
+static ssize_t read_hids_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, u16_t len, u16_t offset)
+{
+ struct zmk_hid_keypad_report_body *report_body = &zmk_hid_get_keypad_report()->body;
+ return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, sizeof(struct zmk_hid_keypad_report_body));
+}
+
+static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, u16_t len, u16_t offset)
+{
+ struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body;
+ return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, sizeof(struct zmk_hid_consumer_report_body));
+}
+
+// static ssize_t write_proto_mode(struct bt_conn *conn,
+// const struct bt_gatt_attr *attr,
+// const void *buf, u16_t len, u16_t offset,
+// u8_t flags)
+// {
+// printk("PROTO CHANGED\n");
+// return 0;
+// }
+
+static void input_ccc_changed(const struct bt_gatt_attr *attr, u16_t value)
+{
+ host_requests_notification = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0;
+}
+
+static ssize_t write_ctrl_point(struct bt_conn *conn,
+ const struct bt_gatt_attr *attr,
+ const void *buf, u16_t len, u16_t offset,
+ u8_t flags)
+{
+ u8_t *value = attr->user_data;
+
+ if (offset + len > sizeof(ctrl_point))
+ {
+ return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
+ }
+
+ memcpy(value + offset, buf, len);
+
+ return len;
+}
+
+/* HID Service Declaration */
+BT_GATT_SERVICE_DEFINE(hog_svc,
+ BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS),
+ // BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_PROTOCOL_MODE, BT_GATT_CHRC_WRITE_WITHOUT_RESP,
+ // BT_GATT_PERM_WRITE, NULL, write_proto_mode, &proto_mode),
+ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ,
+ BT_GATT_PERM_READ, read_hids_info, NULL, &info),
+ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ,
+ BT_GATT_PERM_READ, read_hids_report_map, NULL, NULL),
+
+ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
+ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
+ BT_GATT_PERM_READ_ENCRYPT,
+ read_hids_input_report, NULL, NULL),
+ BT_GATT_CCC(input_ccc_changed,
+ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+ BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ,
+ read_hids_report_ref, NULL, &input),
+ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
+ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
+ BT_GATT_PERM_READ_ENCRYPT,
+ read_hids_consumer_input_report, NULL, NULL),
+ BT_GATT_CCC(input_ccc_changed,
+ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+ BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ,
+ read_hids_report_ref, NULL, &consumer_input),
+ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT,
+ BT_GATT_CHRC_WRITE_WITHOUT_RESP,
+ BT_GATT_PERM_WRITE,
+ NULL, write_ctrl_point, &ctrl_point));
+
+int zmk_hog_send_keypad_report(struct zmk_hid_keypad_report_body *report)
+{
+ return bt_gatt_notify(NULL, &hog_svc.attrs[5], report, sizeof(struct zmk_hid_keypad_report_body));
+};
+
+int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report)
+{
+ return bt_gatt_notify(NULL, &hog_svc.attrs[10], report, sizeof(struct zmk_hid_consumer_report_body));
+};
diff --git a/app/src/keymap.c b/app/src/keymap.c
new file mode 100644
index 0000000..569a2cc
--- /dev/null
+++ b/app/src/keymap.c
@@ -0,0 +1,74 @@
+
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+#include <zmk/keymap.h>
+
+static u32_t zmk_keymap_layer_state = 0;
+static u8_t zmk_keymap_layer_default = 0;
+
+static zmk_key zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_MATRIX_ROWS * ZMK_MATRIX_COLS] = {
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 0)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 0, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 1)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 1, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 2)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 2, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 3)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 3, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 4)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 4, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 5)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 5, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 6)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 6, keys),
+#endif
+#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 7)
+ DT_PROP_BY_PHANDLE_IDX(ZMK_KEYMAP_NODE, layers, 7, keys),
+#endif
+};
+
+#define SET_LAYER_STATE(layer, state) \
+ if (layer >= 32) \
+ { \
+ return false; \
+ } \
+ WRITE_BIT(zmk_keymap_layer_state, layer, state); \
+ return true;
+
+bool zmk_keymap_layer_activate(u8_t layer)
+{
+ SET_LAYER_STATE(layer, true);
+};
+
+bool zmk_keymap_layer_deactivate(u8_t layer)
+{
+ SET_LAYER_STATE(layer, false);
+};
+
+zmk_key zmk_keymap_keycode_from_position(u32_t row, u32_t column)
+{
+ for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= zmk_keymap_layer_default; layer--)
+ {
+ if ((zmk_keymap_layer_state & BIT(layer)) == BIT(layer) || layer == zmk_keymap_layer_default)
+ {
+ u8_t key_index = (row * ZMK_MATRIX_COLS) + column;
+ LOG_DBG("Getting key at index %d", key_index);
+
+ zmk_key key = zmk_keymap[layer][key_index];
+ if (key == ZC_TRNS)
+ {
+ continue;
+ }
+
+ return key;
+ }
+ }
+
+ return ZC_NO;
+}
diff --git a/app/src/kscan.c b/app/src/kscan.c
new file mode 100644
index 0000000..1f54a14
--- /dev/null
+++ b/app/src/kscan.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020 Peter Johanson
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr.h>
+#include <device.h>
+#include <drivers/kscan.h>
+#include <logging/log.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/keymap.h>
+#include <zmk/handlers.h>
+
+#define ZMK_KSCAN_EVENT_STATE_PRESSED 0
+#define ZMK_KSCAN_EVENT_STATE_RELEASED 1
+
+struct zmk_kscan_event
+{
+ u32_t row;
+ u32_t column;
+ u32_t state;
+};
+
+struct zmk_kscan_msg_processor
+{
+ struct k_work work;
+} msg_processor;
+
+K_MSGQ_DEFINE(zmk_kscan_msgq, sizeof(struct zmk_kscan_event), CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4);
+
+static void zmk_kscan_callback(struct device *dev, u32_t row, u32_t column, bool pressed)
+{
+ struct zmk_kscan_event ev = {
+ .row = row,
+ .column = column,
+ .state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)};
+
+ k_msgq_put(&zmk_kscan_msgq, &ev, K_NO_WAIT);
+ k_work_submit(&msg_processor.work);
+}
+
+void zmk_kscan_process_msgq(struct k_work *item)
+{
+ struct zmk_kscan_event ev;
+
+ while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0)
+ {
+ bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
+ zmk_key key = zmk_keymap_keycode_from_position(ev.row, ev.column);
+ struct zmk_key_event kev = (struct zmk_key_event){.row = ev.row, .column = ev.column, .key = key, .pressed = pressed};
+
+ LOG_DBG("Row: %d, col: %d, key: %d, pressed: %s\n", ev.row, ev.column, key, (pressed ? "true" : "false"));
+ zmk_handle_key(kev);
+ }
+}
+
+int zmk_kscan_init(char *name)
+{
+ struct device *dev = device_get_binding(name);
+ if (dev == NULL)
+ {
+ LOG_ERR("Failed to get the KSCAN device");
+ return -EINVAL;
+ }
+
+ k_work_init(&msg_processor.work, zmk_kscan_process_msgq);
+
+ kscan_config(dev, zmk_kscan_callback);
+ kscan_enable_callback(dev);
+
+ return 0;
+}
diff --git a/app/src/kscan_composite.c b/app/src/kscan_composite.c
new file mode 100644
index 0000000..d46484b
--- /dev/null
+++ b/app/src/kscan_composite.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_kscan_composite
+
+#include <device.h>
+#include <drivers/kscan.h>
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#define MATRIX_NODE_ID DT_DRV_INST(0)
+#define MATRIX_ROWS DT_PROP(MATRIX_NODE_ID, rows)
+#define MATRIX_COLS DT_PROP(MATRIX_NODE_ID, columns)
+
+struct kscan_composite_child_config
+{
+ char *label;
+ u8_t row_offset;
+ u8_t column_offset;
+};
+
+#define CHILD_CONFIG(inst) \
+ { \
+ .label = DT_LABEL(DT_PHANDLE(inst, kscan)), \
+ .row_offset = DT_PROP(inst, row_offset), \
+ .column_offset = DT_PROP(inst, column_offset)},
+
+const struct kscan_composite_child_config kscan_composite_children[] = {
+ DT_FOREACH_CHILD(MATRIX_NODE_ID, CHILD_CONFIG)};
+
+struct kscan_composite_config
+{
+};
+
+struct kscan_composite_data
+{
+ kscan_callback_t callback;
+
+ struct device *dev;
+};
+
+static int kscan_composite_enable_callback(struct device *dev)
+{
+ for (int i = 0; i < sizeof(kscan_composite_children) / sizeof(kscan_composite_children[0]); i++)
+ {
+ const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
+
+ kscan_enable_callback(device_get_binding(cfg->label));
+ }
+ return 0;
+}
+
+static int kscan_composite_disable_callback(struct device *dev)
+{
+ for (int i = 0; i < sizeof(kscan_composite_children) / sizeof(kscan_composite_children[0]); i++)
+ {
+ const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
+
+ kscan_disable_callback(device_get_binding(cfg->label));
+ }
+ return 0;
+}
+
+static void kscan_composite_child_callback(struct device *child_dev, u32_t row, u32_t column, bool pressed)
+{
+ // TODO: Ideally we can get this passed into our callback!
+ struct device *dev = device_get_binding(DT_INST_LABEL(0));
+ struct kscan_composite_data *data = dev->driver_data;
+
+ for (int i = 0; i < sizeof(kscan_composite_children) / sizeof(kscan_composite_children[0]); i++)
+ {
+ const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
+
+ if (device_get_binding(cfg->label) != child_dev)
+ {
+ continue;
+ }
+
+ data->callback(dev, row + cfg->row_offset, column + cfg->column_offset, pressed);
+ }
+}
+
+static int kscan_composite_configure(struct device *dev, kscan_callback_t callback)
+{
+ struct kscan_composite_data *data = dev->driver_data;
+
+ if (!callback)
+ {
+ return -EINVAL;
+ }
+
+ for (int i = 0; i < sizeof(kscan_composite_children) / sizeof(kscan_composite_children[0]); i++)
+ {
+ const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
+
+ kscan_config(device_get_binding(cfg->label), &kscan_composite_child_callback);
+ }
+
+ data->callback = callback;
+
+ return 0;
+}
+
+static int kscan_composite_init(struct device *dev)
+{
+ struct kscan_composite_data *data = dev->driver_data;
+
+ data->dev = dev;
+
+ return 0;
+}
+
+static const struct kscan_driver_api mock_driver_api = {
+ .config = kscan_composite_configure,
+ .enable_callback = kscan_composite_enable_callback,
+ .disable_callback = kscan_composite_disable_callback,
+};
+
+static const struct kscan_composite_config kscan_composite_config = {};
+
+static struct kscan_composite_data kscan_composite_data;
+
+DEVICE_AND_API_INIT(kscan_composite, DT_INST_LABEL(0), kscan_composite_init,
+ &kscan_composite_data,
+ &kscan_composite_config,
+ APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
+ &mock_driver_api);
diff --git a/app/src/kscan_mock.c b/app/src/kscan_mock.c
new file mode 100644
index 0000000..7d2d24d
--- /dev/null
+++ b/app/src/kscan_mock.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_kscan_mock
+
+#include <device.h>
+#include <drivers/kscan.h>
+#include <logging/log.h>
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/kscan-mock.h>
+
+struct kscan_mock_data
+{
+ kscan_callback_t callback;
+
+ u8_t event_index;
+ struct k_delayed_work work;
+ struct device *dev;
+};
+
+static int kscan_mock_disable_callback(struct device *dev)
+{
+ struct kscan_mock_data *data = dev->driver_data;
+
+ k_delayed_work_cancel(&data->work);
+ return 0;
+}
+
+static int kscan_mock_configure(struct device *dev, kscan_callback_t callback)
+{
+ struct kscan_mock_data *data = dev->driver_data;
+
+ if (!callback)
+ {
+ return -EINVAL;
+ }
+
+ data->event_index = 0;
+ data->callback = callback;
+
+ return 0;
+}
+
+#define MOCK_INST_INIT(n) \
+ struct kscan_mock_config_##n \
+ { \
+ u32_t events[DT_INST_PROP_LEN(n, events)]; \
+ }; \
+ static void kscan_mock_schedule_next_event_##n(struct device *dev) \
+ { \
+ struct kscan_mock_data *data = dev->driver_data; \
+ const struct kscan_mock_config_##n *cfg = dev->config_info; \
+ if (data->event_index < DT_INST_PROP_LEN(n, events)) \
+ { \
+ u32_t ev = cfg->events[data->event_index]; \
+ LOG_DBG("delaying next keypress: %d", ZMK_MOCK_MSEC(ev)); \
+ k_delayed_work_submit(&data->work, K_MSEC(ZMK_MOCK_MSEC(ev))); \
+ } \
+ } \
+ static void kscan_mock_work_handler_##n(struct k_work *work) \
+ { \
+ struct kscan_mock_data *data = \
+ CONTAINER_OF(work, struct kscan_mock_data, work); \
+ const struct kscan_mock_config_##n *cfg = data->dev->config_info; \
+ u32_t ev = cfg->events[data->event_index++]; \
+ LOG_DBG("ev %u row %d column %d state %d\n", ev, \
+ ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), ZMK_MOCK_IS_PRESS(ev)); \
+ data->callback(data->dev, \
+ ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), ZMK_MOCK_IS_PRESS(ev)); \
+ kscan_mock_schedule_next_event_##n(data->dev); \
+ } \
+ static int kscan_mock_init_##n(struct device *dev) \
+ { \
+ struct kscan_mock_data *data = dev->driver_data; \
+ data->dev = dev; \
+ k_delayed_work_init(&data->work, kscan_mock_work_handler_##n); \
+ return 0; \
+ } \
+ static int kscan_mock_enable_callback_##n(struct device *dev) \
+ { \
+ kscan_mock_schedule_next_event_##n(dev); \
+ return 0; \
+ } \
+ static const struct kscan_driver_api mock_driver_api_##n = { \
+ .config = kscan_mock_configure, \
+ .enable_callback = kscan_mock_enable_callback_##n, \
+ .disable_callback = kscan_mock_disable_callback, \
+ }; \
+ static struct kscan_mock_data kscan_mock_data_##n; \
+ static const struct kscan_mock_config_##n kscan_mock_config_##n = { \
+ .events = DT_INST_PROP(n, events)}; \
+ DEVICE_AND_API_INIT(kscan_mock_##n, DT_INST_LABEL(n), kscan_mock_init_##n, \
+ &kscan_mock_data_##n, \
+ &kscan_mock_config_##n, \
+ APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
+ &mock_driver_api_##n);
+
+DT_INST_FOREACH_STATUS_OKAY(MOCK_INST_INIT) \ No newline at end of file
diff --git a/app/src/main.c b/app/src/main.c
new file mode 100644
index 0000000..1ced310
--- /dev/null
+++ b/app/src/main.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 Peter Johanson
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr.h>
+#include <device.h>
+#include <devicetree.h>
+#include <settings/settings.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/matrix.h>
+#include <zmk/kscan.h>
+#include <zmk/endpoints.h>
+
+#define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID)
+
+void main(void)
+{
+ printk("Welcome to ZMK!\n");
+
+ if (zmk_kscan_init(ZMK_KSCAN_DEV) != 0)
+ {
+ return;
+ }
+
+ if (zmk_endpoints_init())
+ {
+ printk("ENDPOINT INIT FAILED\n");
+ return;
+ }
+
+#ifdef CONFIG_SETTINGS
+ settings_load();
+#endif
+}
diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c
new file mode 100644
index 0000000..2862d56
--- /dev/null
+++ b/app/src/usb_hid.c
@@ -0,0 +1,58 @@
+
+#include <device.h>
+
+#include <usb/usb_device.h>
+#include <usb/class/usb_hid.h>
+#include <dt-bindings/zmk/keys.h>
+
+#include <zmk/hid.h>
+#include <zmk/keymap.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+static enum usb_dc_status_code usb_status;
+
+static struct device *hid_dev;
+
+int zmk_usb_hid_send_report(const u8_t *report, size_t len)
+{
+ if (usb_status == USB_DC_SUSPEND)
+ {
+ return usb_wakeup_request();
+ }
+
+ return hid_int_ep_write(hid_dev, report, len, NULL);
+}
+
+void usb_hid_status_cb(enum usb_dc_status_code status, const u8_t *params)
+{
+ usb_status = status;
+};
+
+int zmk_usb_hid_init()
+{
+ int usb_enable_ret;
+
+ hid_dev = device_get_binding("HID_0");
+ if (hid_dev == NULL)
+ {
+ LOG_ERR("Unable to locate HID device");
+ return -EINVAL;
+ }
+
+ usb_hid_register_device(hid_dev,
+ zmk_hid_report_desc, sizeof(zmk_hid_report_desc),
+ NULL);
+
+ usb_hid_init(hid_dev);
+
+ usb_enable_ret = usb_enable(usb_hid_status_cb);
+
+ if (usb_enable_ret != 0)
+ {
+ LOG_ERR("Unable to enable USB");
+ return -EINVAL;
+ }
+
+ return 0;
+}
diff --git a/app/west.yml b/app/west.yml
new file mode 100644
index 0000000..156bc85
--- /dev/null
+++ b/app/west.yml
@@ -0,0 +1,13 @@
+manifest:
+ remotes:
+ - name: zephyrproject-rtos
+ url-base: https://github.com/zephyrproject-rtos
+ - name: petejohanson
+ url-base: https://github.com/petejohanson
+ projects:
+ - name: zephyr
+ remote: petejohanson
+ revision: kconfig/external-sheilds-shields-as-list-fix
+ import: true
+ self:
+ path: zmk