diff options
Diffstat (limited to 'app')
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 |