Merge branch 'conditional_mod_tap' into mercury

This commit is contained in:
jmding@gmail.com 2021-09-12 16:51:33 +00:00
commit db5a4b952a
121 changed files with 12113 additions and 7005 deletions

View file

@ -1,4 +1,4 @@
FROM zmkfirmware/zmk-dev-arm:2.4
FROM zmkfirmware/zmk-dev-arm:2.5
COPY .bashrc tmp
RUN mv /tmp/.bashrc ~/.bashrc

View file

@ -14,12 +14,13 @@ jobs:
build:
runs-on: ubuntu-latest
container:
image: zmkfirmware/zmk-build-arm:2.4
image: zmkfirmware/zmk-build-arm:2.5
strategy:
matrix:
board:
- bluemicro840_v1
- nice_nano
- nice_nano_v2
- nrfmicro_13
- proton_c
shield:
@ -69,11 +70,11 @@ jobs:
- board: planck_rev6
- board: proton_c
shield: clueboard_california
- board: nice_nano
- board: nice_nano_v2
shield: kyria_left
cmake-args: -DCONFIG_ZMK_DISPLAY=y
skip-archive: true
- board: nice_nano
- board: nice_nano_v2
shield: kyria_right
cmake-args: -DCONFIG_ZMK_DISPLAY=y
skip-archive: true

View file

@ -16,7 +16,7 @@ jobs:
integration_test:
runs-on: ubuntu-latest
container:
image: zmkfirmware/zmk-build-arm:2.4
image: zmkfirmware/zmk-build-arm:2.5
steps:
- name: Checkout
uses: actions/checkout@v2

View file

@ -91,7 +91,7 @@ You can setup git to run prettier automatically when you commit by installing th
### Development Setup
To get your development environment setup going, start at the
[basic setup](https://zmk.dev/docs/dev-setup) docs, and make sure you can build and flash
[basic setup](https://zmk.dev/docs/development/setup/) docs, and make sure you can build and flash
your own locally built firmware.
### Formatting

View file

@ -36,6 +36,7 @@ target_sources(app PRIVATE src/events/position_state_changed.c)
target_sources(app PRIVATE src/events/layer_state_changed.c)
target_sources(app PRIVATE src/events/keycode_state_changed.c)
target_sources(app PRIVATE src/events/modifiers_state_changed.c)
target_sources(app PRIVATE src/events/endpoint_selection_changed.c)
target_sources(app PRIVATE src/events/sensor_event.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)

View file

@ -263,14 +263,11 @@ config ZMK_SLEEP
if ZMK_SLEEP
config SYS_POWER_DEEP_SLEEP_STATES
default y
choice SYS_PM_POLICY
default SYS_PM_POLICY_APP
default PM_POLICY_APP
endchoice
config DEVICE_POWER_MANAGEMENT
config PM_DEVICE
default y
config ZMK_IDLE_SLEEP_TIMEOUT
@ -422,6 +419,11 @@ config ZMK_WPM
config SENSOR
default y
choice CBPRINTF_IMPLEMENTATION
default CBPRINTF_NANO
endchoice
module = ZMK
module-str = zmk
source "subsys/logging/Kconfig.template.log_config"

View file

@ -11,6 +11,9 @@ CONFIG_FPU=y
# enable GPIO
CONFIG_GPIO=y
# Enable pinmux
CONFIG_PINMUX=y
# Needed to reduce this to size that will fit on F072
CONFIG_HEAP_MEM_POOL_SIZE=1024

View file

@ -4,4 +4,4 @@ config BOARD_ENABLE_DCDC
bool "Enable DCDC mode"
select SOC_DCDC_NRF52X
default y
depends on BOARD_NICE_NANO
depends on (BOARD_NICE_NANO || BOARD_NICE_NANO_V2)

View file

@ -7,3 +7,7 @@ config BOARD_NICE_NANO
bool "nice!nano"
depends on SOC_NRF52840_QIAA
config BOARD_NICE_NANO_V2
bool "nice!nano v2"
depends on SOC_NRF52840_QIAA

View file

@ -1,7 +1,7 @@
# Copyright (c) 2020 Pete Johanson
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
if BOARD_NICE_NANO
if BOARD_NICE_NANO || BOARD_NICE_NANO_V2
config BOARD
default "nice_nano"
@ -25,7 +25,18 @@ config ZMK_BLE
config ZMK_USB
default y
endif # BOARD_NICE_NANO || BOARD_NICE_NANO_V2
if BOARD_NICE_NANO
config ZMK_BATTERY_VOLTAGE_DIVIDER
default y
endif # BOARD_NICE_NANO
if BOARD_NICE_NANO_V2
config ZMK_BATTERY_NRF_VDDH
default y
endif # BOARD_NICE_NANO_V2

View file

@ -1,31 +1,13 @@
/*
* Copyright (c) 2020 Pete Johanson
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "arduino_pro_micro_pins.dtsi"
#include "nice_nano.dtsi"
/ {
model = "nice!nano";
compatible = "nice,nano";
chosen {
zephyr,code-partition = &code_partition;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
leds {
compatible = "gpio-leds";
blue_led: led_0 {
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
label = "Blue LED";
};
};
ext-power {
compatible = "zmk,ext-power-generic";
label = "EXT_POWER";
@ -40,76 +22,3 @@
full-ohms = <(2000000 + 806000)>;
};
};
&adc {
status = "okay";
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&i2c0 {
compatible = "nordic,nrf-twi";
sda-pin = <17>;
scl-pin = <20>;
};
&uart0 {
compatible = "nordic,nrf-uarte";
tx-pin = <6>;
rx-pin = <8>;
};
&usbd {
status = "okay";
};
&flash0 {
/*
* For more information, see:
* http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html
*/
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
sd_partition: partition@0 {
label = "softdevice";
reg = <0x00000000 0x00026000>;
};
code_partition: partition@26000 {
label = "code_partition";
reg = <0x00026000 0x000c6000>;
};
/*
* The flash starting at 0x000ec000 and ending at
* 0x000f3fff is reserved for use by the application.
*/
/*
* Storage partition will be used by FCB/LittleFS/NVS
* if enabled.
*/
storage_partition: partition@ec000 {
label = "storage";
reg = <0x000ec000 0x00008000>;
};
boot_partition: partition@f4000 {
label = "adafruit_boot";
reg = <0x000f4000 0x0000c000>;
};
};
};

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <nordic/nrf52840_qiaa.dtsi>
#include "arduino_pro_micro_pins.dtsi"
/ {
model = "nice!nano";
compatible = "nice,nano";
chosen {
zephyr,code-partition = &code_partition;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
leds {
compatible = "gpio-leds";
blue_led: led_0 {
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
label = "Blue LED";
};
};
};
&adc {
status = "okay";
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&i2c0 {
compatible = "nordic,nrf-twi";
sda-pin = <17>;
scl-pin = <20>;
};
&uart0 {
compatible = "nordic,nrf-uarte";
tx-pin = <6>;
rx-pin = <8>;
};
&usbd {
status = "okay";
};
&flash0 {
/*
* For more information, see:
* http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html
*/
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
sd_partition: partition@0 {
label = "softdevice";
reg = <0x00000000 0x00026000>;
};
code_partition: partition@26000 {
label = "code_partition";
reg = <0x00026000 0x000c6000>;
};
/*
* The flash starting at 0x000ec000 and ending at
* 0x000f3fff is reserved for use by the application.
*/
/*
* Storage partition will be used by FCB/LittleFS/NVS
* if enabled.
*/
storage_partition: partition@ec000 {
label = "storage";
reg = <0x000ec000 0x00008000>;
};
boot_partition: partition@f4000 {
label = "adafruit_boot";
reg = <0x000f4000 0x0000c000>;
};
};
};

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/dts-v1/;
#include "nice_nano.dtsi"
/ {
ext-power {
compatible = "zmk,ext-power-generic";
label = "EXT_POWER";
control-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
init-delay-ms = <10>;
};
vbatt {
compatible = "zmk,battery-nrf-vddh";
label = "BATTERY";
};
};

View file

@ -0,0 +1,15 @@
identifier: nice_nano_v2
name: nice!nano v2
type: mcu
arch: arm
toolchain:
- zephyr
- gnuarmemb
- xtools
supported:
- adc
- usb_device
- ble
- ieee802154
- pwm
- watchdog

View file

@ -0,0 +1,20 @@
# SPDX-License-Identifier: MIT
CONFIG_SOC_SERIES_NRF52X=y
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_NICE_NANO_V2=y
# Enable MPU
CONFIG_ARM_MPU=y
# enable GPIO
CONFIG_GPIO=y
CONFIG_USE_DT_CODE_PARTITION=y
CONFIG_MPU_ALLOW_FLASH_WRITE=y
CONFIG_NVS=y
CONFIG_SETTINGS_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y

View file

@ -6,6 +6,7 @@
/dts-v1/;
#include <st/f3/stm32f303Xc.dtsi>
#include <dt-bindings/zmk/matrix_transform.h>
/ {
model = "Plack PCD, rev6";
@ -15,11 +16,13 @@
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zmk,kscan = &kscan0;
zmk,matrix_transform = &layout_grid_transform;
};
kscan0: kscan {
compatible = "zmk,kscan-gpio-matrix";
label = "KSCAN";
diode-direction = "col2row";
row-gpios
= <&gpioa 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&gpioa 9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
@ -40,6 +43,42 @@
;
};
layout_grid_transform:
keymap_transform_0 {
compatible = "zmk,matrix-transform";
columns = <6>;
rows = <8>;
map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(5,5)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(6,0) RC(6,1) RC(6,2) RC(6,3) RC(6,4) RC(6,5)
RC(3,0) RC(3,1) RC(3,2) RC(7,3) RC(7,4) RC(7,5) RC(7,0) RC(7,1) RC(7,2) RC(3,3) RC(3,4) RC(3,5)
>;
};
layout_mit_transform:
keymap_transform_1 {
compatible = "zmk,matrix-transform";
columns = <6>;
rows = <8>;
map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(5,5)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(6,0) RC(6,1) RC(6,2) RC(6,3) RC(6,4) RC(6,5)
RC(3,0) RC(3,1) RC(3,2) RC(7,3) RC(7,4) RC(7,0) RC(7,1) RC(7,2) RC(3,3) RC(3,4) RC(3,5)
>;
};
layout_2x2u_transform:
keymap_transform_2 {
compatible = "zmk,matrix-transform";
columns = <6>;
rows = <8>;
map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(5,5)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(6,0) RC(6,1) RC(6,2) RC(6,3) RC(6,4) RC(6,5)
RC(3,0) RC(3,1) RC(3,2) RC(7,3) RC(7,5) RC(7,1) RC(7,2) RC(3,3) RC(3,4) RC(3,5)
>;
};
};
&usb {

View file

@ -1,7 +0,0 @@
# SPDX-License-Identifier: MIT
if(CONFIG_PINMUX)
zephyr_library()
zephyr_library_sources(pinmux.c)
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
endif()

View file

@ -48,5 +48,5 @@
};
pro_micro_i2c: &i2c1 {};
pro_micro_spi: &spi1 {};
pro_micro_spi: &spi2 {};
pro_micro_serial: &usart1 {};

View file

@ -1,67 +0,0 @@
/*
* Copyright (c) 2017 I-SENSE group of ICCS
*
* SPDX-License-Identifier: MIT
*/
#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[] = {
#if DT_NODE_HAS_STATUS(DT_NODELABEL(usart1), okay) && CONFIG_SERIAL
{STM32_PIN_PC4, STM32F3_PINMUX_FUNC_PC4_USART1_TX},
{STM32_PIN_PC5, STM32F3_PINMUX_FUNC_PC5_USART1_RX},
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(usart2), okay) && CONFIG_SERIAL
{STM32_PIN_PA2, STM32F3_PINMUX_FUNC_PA2_USART2_TX},
{STM32_PIN_PA3, STM32F3_PINMUX_FUNC_PA3_USART2_RX},
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2c1), okay) && CONFIG_I2C
{STM32_PIN_PB6, STM32F3_PINMUX_FUNC_PB6_I2C1_SCL},
{STM32_PIN_PB7, STM32F3_PINMUX_FUNC_PB7_I2C1_SDA},
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2c2), okay) && CONFIG_I2C
{STM32_PIN_PA9, STM32F3_PINMUX_FUNC_PA9_I2C2_SCL},
{STM32_PIN_PA10, STM32F3_PINMUX_FUNC_PA10_I2C2_SDA},
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(spi1), okay) && CONFIG_SPI
#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
#if DT_NODE_HAS_STATUS(DT_NODELABEL(spi2), okay) && CONFIG_SPI
#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
#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 */
#if DT_NODE_HAS_STATUS(DT_NODELABEL(can1), okay) && CONFIG_CAN
{STM32_PIN_PD0, STM32F3_PINMUX_FUNC_PD0_CAN1_RX},
{STM32_PIN_PD1, STM32F3_PINMUX_FUNC_PD1_CAN1_TX},
#endif
};
static int pinmux_stm32_init(const 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);

View file

@ -6,6 +6,7 @@
/dts-v1/;
#include <st/f3/stm32f303Xc.dtsi>
#include <st/f3/stm32f303c(b-c)tx-pinctrl.dtsi>
#include "arduino_pro_micro_pins.dtsi"
/ {
@ -26,6 +27,18 @@
};
};
&usart1 {
pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>;
};
&spi2 {
pinctrl-0 = <&spi2_sck_pb13 &spi2_miso_pb14 &spi2_mosi_pb15>;
};
&i2c1 {
pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>;
};
&usb {
status = "okay";
};

View file

@ -16,23 +16,23 @@
// -----------------------------------------------------------------------------------------
// | TAB | Q | W | E | R | T | | Y | U | I | O | P | BKSP |
// | CTRL | A | S | D | F | G | | H | J | K | L | ; | ' |
// | SHFT | Z | X | C | V | B | | N | M | , | . | / | SHFT |
// | SHFT | Z | X | C | V | B | | N | M | , | . | / | ESC |
// | GUI | LWR | SPC | | ENT | RSE | ALT |
bindings = <
&kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSPC
&kp LCTRL &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT
&kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RSHFT
&kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp ESC
&kp LGUI &mo 1 &kp SPACE &kp RET &mo 2 &kp RALT
>;
};
lower_layer {
// -----------------------------------------------------------------------------------------
// | ESC | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | BKSP |
// | TAB | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | BKSP |
// | BTCLR| BT1 | BT2 | BT3 | BT4 | BT5 | | LFT | DWN | UP | RGT | | |
// | SHFT | | | | | | | | | | | | |
// | GUI | | SPC | | ENT | | ALT |
bindings = <
&kp ESC &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp BSPC
&kp TAB &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp BSPC
&bt BT_CLR &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &kp LEFT &kp DOWN &kp UP &kp RIGHT &trans &trans
&kp LSHFT &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans
&kp LGUI &trans &kp SPACE &kp RET &trans &kp RALT
@ -41,14 +41,14 @@
raise_layer {
// -----------------------------------------------------------------------------------------
// | ESC | ! | @ | # | $ | % | | ^ | & | * | ( | ) | BKSP |
// | CTRL | | | | | | | - | = | { | } | "|" | ` |
// | SHFT | | | | | | | _ | + | [ | ] | \ | ~ | // TODO: Fix this row when &mkp is committed
// | TAB | ! | @ | # | $ | % | | ^ | & | * | ( | ) | BKSP |
// | CTRL | | | | | | | - | = | [ | ] | \ | ` |
// | SHFT | | | | | | | _ | + | { | } | "|" | ~ |
// | GUI | | SPC | | ENT | | ALT |
bindings = <
&kp ESC &kp EXCL &kp AT &kp HASH &kp DLLR &kp PRCNT &kp CARET &kp AMPS &kp KP_MULTIPLY &kp LPAR &kp RPAR &kp BSPC
&kp LCTRL &trans &trans &trans &trans &trans &kp MINUS &kp EQUAL &kp LBKT &kp RBKT &kp PIPE &kp GRAVE
&kp LSHFT &trans &trans &trans &trans &trans &trans &trans &trans &trans &kp BSLH &kp TILDE
&kp TAB &kp EXCL &kp AT &kp HASH &kp DLLR &kp PRCNT &kp CARET &kp AMPS &kp KP_MULTIPLY &kp LPAR &kp RPAR &kp BSPC
&kp LCTRL &trans &trans &trans &trans &trans &kp MINUS &kp EQUAL &kp LBKT &kp RBKT &kp BSLH &kp GRAVE
&kp LSHFT &trans &trans &trans &trans &trans &kp UNDER &kp PLUS &kp LBRC &kp RBRC &kp PIPE &kp TILDE
&kp LGUI &trans &kp SPACE &kp RET &trans &kp RALT
>;
};

View file

@ -1 +1,35 @@
# Cradio
Cradio is a firmware for a few 34 key keyboards, including Cradio, Hypergolic and Sweep.
## Pin arrangement
Some revisions of the aforementioned PCBs have slightly different pin arrangements compared to what's defined in [`cradio.dtsi`](./cradio.dtsi). If you need to swap a few keys for your particular PCB, you can easily reorder the `input-gpio` definition in your own keymap file (i.e. in `zmk-config/config/cradio.keymap`):
```dts
/* Adjusted Cradio pin arrangement */
/* The position of Q and B keys have been swapped */
&kscan0 {
input-gpios
= <&pro_micro_d 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_a 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_a 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_a 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_a 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro_d 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
;
};
```
This `&kscan0` block must be placed outside of any blocks surrounded by curly braces (`{...}`).

View file

@ -3,3 +3,4 @@
add_subdirectory(kscan)
add_subdirectory(sensor)
add_subdirectory(display)

View file

@ -3,3 +3,4 @@
rsource "kscan/Kconfig"
rsource "sensor/Kconfig"
rsource "display/Kconfig"

View file

@ -0,0 +1,4 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_sources_ifdef(CONFIG_IL0323 il0323.c)

View file

@ -0,0 +1,4 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
rsource "Kconfig.il0323"

View file

@ -0,0 +1,11 @@
# Copyright (c) 2020 Phytec Messtechnik GmbH, Peter Johanson
# SPDX-License-Identifier: Apache-2.0
# IL0323 display controller configuration options
config IL0323
bool "IL0323 compatible display controller driver"
depends on SPI
depends on HEAP_MEM_POOL_SIZE != 0
help
Enable driver for IL0323 compatible controller.

View file

@ -0,0 +1,432 @@
/*
* Copyright (c) 2020 PHYTEC Messtechnik GmbHH, Peter Johanson
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT gooddisplay_il0323
#include <string.h>
#include <device.h>
#include <init.h>
#include <drivers/display.h>
#include <drivers/gpio.h>
#include <drivers/spi.h>
#include <sys/byteorder.h>
#include "il0323_regs.h"
#include <logging/log.h>
LOG_MODULE_REGISTER(il0323, CONFIG_DISPLAY_LOG_LEVEL);
/**
* IL0323 compatible EPD controller driver.
*
*/
#define IL0323_SPI_FREQ DT_INST_PROP(0, spi_max_frequency)
#define IL0323_BUS_NAME DT_INST_BUS_LABEL(0)
#define IL0323_DC_PIN DT_INST_GPIO_PIN(0, dc_gpios)
#define IL0323_DC_FLAGS DT_INST_GPIO_FLAGS(0, dc_gpios)
#define IL0323_DC_CNTRL DT_INST_GPIO_LABEL(0, dc_gpios)
#define IL0323_CS_PIN DT_INST_SPI_DEV_CS_GPIOS_PIN(0)
#define IL0323_CS_FLAGS DT_INST_SPI_DEV_CS_GPIOS_FLAGS(0)
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
#define IL0323_CS_CNTRL DT_INST_SPI_DEV_CS_GPIOS_LABEL(0)
#endif
#define IL0323_BUSY_PIN DT_INST_GPIO_PIN(0, busy_gpios)
#define IL0323_BUSY_CNTRL DT_INST_GPIO_LABEL(0, busy_gpios)
#define IL0323_BUSY_FLAGS DT_INST_GPIO_FLAGS(0, busy_gpios)
#define IL0323_RESET_PIN DT_INST_GPIO_PIN(0, reset_gpios)
#define IL0323_RESET_CNTRL DT_INST_GPIO_LABEL(0, reset_gpios)
#define IL0323_RESET_FLAGS DT_INST_GPIO_FLAGS(0, reset_gpios)
#define EPD_PANEL_WIDTH DT_INST_PROP(0, width)
#define EPD_PANEL_HEIGHT DT_INST_PROP(0, height)
#define IL0323_PIXELS_PER_BYTE 8U
/* Horizontally aligned page! */
#define IL0323_NUMOF_PAGES (EPD_PANEL_WIDTH / IL0323_PIXELS_PER_BYTE)
#define IL0323_PANEL_FIRST_GATE 0U
#define IL0323_PANEL_LAST_GATE (EPD_PANEL_HEIGHT - 1)
#define IL0323_PANEL_FIRST_PAGE 0U
#define IL0323_PANEL_LAST_PAGE (IL0323_NUMOF_PAGES - 1)
#define IL0323_BUFFER_SIZE 1280
struct il0323_data {
const struct device *reset;
const struct device *dc;
const struct device *busy;
const struct device *spi_dev;
struct spi_config spi_config;
#if defined(IL0323_CS_CNTRL)
struct spi_cs_control cs_ctrl;
#endif
};
static uint8_t il0323_pwr[] = DT_INST_PROP(0, pwr);
static uint8_t last_buffer[IL0323_BUFFER_SIZE];
static bool blanking_on = true;
static inline int il0323_write_cmd(struct il0323_data *driver, uint8_t cmd, uint8_t *data,
size_t len) {
struct spi_buf buf = {.buf = &cmd, .len = sizeof(cmd)};
struct spi_buf_set buf_set = {.buffers = &buf, .count = 1};
gpio_pin_set(driver->dc, IL0323_DC_PIN, 1);
if (spi_write(driver->spi_dev, &driver->spi_config, &buf_set)) {
return -EIO;
}
if (data != NULL) {
buf.buf = data;
buf.len = len;
gpio_pin_set(driver->dc, IL0323_DC_PIN, 0);
if (spi_write(driver->spi_dev, &driver->spi_config, &buf_set)) {
return -EIO;
}
}
return 0;
}
static inline void il0323_busy_wait(struct il0323_data *driver) {
int pin = gpio_pin_get(driver->busy, IL0323_BUSY_PIN);
while (pin > 0) {
__ASSERT(pin >= 0, "Failed to get pin level");
// LOG_DBG("wait %u", pin);
k_msleep(IL0323_BUSY_DELAY);
pin = gpio_pin_get(driver->busy, IL0323_BUSY_PIN);
}
}
static int il0323_update_display(const struct device *dev) {
struct il0323_data *driver = dev->data;
LOG_DBG("Trigger update sequence");
if (il0323_write_cmd(driver, IL0323_CMD_DRF, NULL, 0)) {
return -EIO;
}
k_msleep(IL0323_BUSY_DELAY);
return 0;
}
static int il0323_blanking_off(const struct device *dev) {
struct il0323_data *driver = dev->data;
if (blanking_on) {
/* Update EPD pannel in normal mode */
il0323_busy_wait(driver);
if (il0323_update_display(dev)) {
return -EIO;
}
}
blanking_on = false;
return 0;
}
static int il0323_blanking_on(const struct device *dev) {
blanking_on = true;
return 0;
}
static int il0323_write(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, const void *buf) {
struct il0323_data *driver = dev->data;
uint16_t x_end_idx = x + desc->width - 1;
uint16_t y_end_idx = y + desc->height - 1;
uint8_t ptl[IL0323_PTL_REG_LENGTH] = {0};
size_t buf_len;
LOG_DBG("x %u, y %u, height %u, width %u, pitch %u", x, y, desc->height, desc->width,
desc->pitch);
buf_len = MIN(desc->buf_size, desc->height * desc->width / IL0323_PIXELS_PER_BYTE);
__ASSERT(desc->width <= desc->pitch, "Pitch is smaller then width");
__ASSERT(buf != NULL, "Buffer is not available");
__ASSERT(buf_len != 0U, "Buffer of length zero");
__ASSERT(!(desc->width % IL0323_PIXELS_PER_BYTE), "Buffer width not multiple of %d",
IL0323_PIXELS_PER_BYTE);
LOG_DBG("buf_len %d", buf_len);
if ((y_end_idx > (EPD_PANEL_HEIGHT - 1)) || (x_end_idx > (EPD_PANEL_WIDTH - 1))) {
LOG_ERR("Position out of bounds");
return -EINVAL;
}
/* Setup Partial Window and enable Partial Mode */
ptl[IL0323_PTL_HRST_IDX] = x;
ptl[IL0323_PTL_HRED_IDX] = x_end_idx;
ptl[IL0323_PTL_VRST_IDX] = y;
ptl[IL0323_PTL_VRED_IDX] = y_end_idx;
ptl[sizeof(ptl) - 1] = IL0323_PTL_PT_SCAN;
LOG_HEXDUMP_DBG(ptl, sizeof(ptl), "ptl");
il0323_busy_wait(driver);
if (il0323_write_cmd(driver, IL0323_CMD_PIN, NULL, 0)) {
return -EIO;
}
if (il0323_write_cmd(driver, IL0323_CMD_PTL, ptl, sizeof(ptl))) {
return -EIO;
}
if (il0323_write_cmd(driver, IL0323_CMD_DTM1, last_buffer, IL0323_BUFFER_SIZE)) {
return -EIO;
}
if (il0323_write_cmd(driver, IL0323_CMD_DTM2, (uint8_t *)buf, buf_len)) {
return -EIO;
}
memcpy(last_buffer, (uint8_t *)buf, IL0323_BUFFER_SIZE);
/* Update partial window and disable Partial Mode */
if (blanking_on == false) {
if (il0323_update_display(dev)) {
return -EIO;
}
}
if (il0323_write_cmd(driver, IL0323_CMD_POUT, NULL, 0)) {
return -EIO;
}
return 0;
}
static int il0323_read(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, void *buf) {
LOG_ERR("not supported");
return -ENOTSUP;
}
static void *il0323_get_framebuffer(const struct device *dev) {
LOG_ERR("not supported");
return NULL;
}
static int il0323_set_brightness(const struct device *dev, const uint8_t brightness) {
LOG_WRN("not supported");
return -ENOTSUP;
}
static int il0323_set_contrast(const struct device *dev, uint8_t contrast) {
LOG_WRN("not supported");
return -ENOTSUP;
}
static void il0323_get_capabilities(const struct device *dev, struct display_capabilities *caps) {
memset(caps, 0, sizeof(struct display_capabilities));
caps->x_resolution = EPD_PANEL_WIDTH;
caps->y_resolution = EPD_PANEL_HEIGHT;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST | SCREEN_INFO_EPD;
}
static int il0323_set_orientation(const struct device *dev,
const enum display_orientation orientation) {
LOG_ERR("Unsupported");
return -ENOTSUP;
}
static int il0323_set_pixel_format(const struct device *dev, const enum display_pixel_format pf) {
if (pf == PIXEL_FORMAT_MONO10) {
return 0;
}
LOG_ERR("not supported");
return -ENOTSUP;
}
static int il0323_clear_and_write_buffer(const struct device *dev, uint8_t pattern, bool update) {
struct display_buffer_descriptor desc = {
.buf_size = IL0323_NUMOF_PAGES,
.width = EPD_PANEL_WIDTH,
.height = 1,
.pitch = EPD_PANEL_WIDTH,
};
uint8_t *line;
line = k_malloc(IL0323_NUMOF_PAGES);
if (line == NULL) {
return -ENOMEM;
}
memset(line, pattern, IL0323_NUMOF_PAGES);
for (int i = 0; i < EPD_PANEL_HEIGHT; i++) {
il0323_write(dev, 0, i, &desc, line);
}
k_free(line);
if (update == true) {
if (il0323_update_display(dev)) {
return -EIO;
}
}
return 0;
}
static int il0323_controller_init(const struct device *dev) {
struct il0323_data *driver = dev->data;
uint8_t tmp[IL0323_TRES_REG_LENGTH];
LOG_DBG("");
gpio_pin_set(driver->reset, IL0323_RESET_PIN, 1);
k_msleep(IL0323_RESET_DELAY);
gpio_pin_set(driver->reset, IL0323_RESET_PIN, 0);
k_msleep(IL0323_RESET_DELAY);
il0323_busy_wait(driver);
LOG_DBG("Initialize IL0323 controller");
if (il0323_write_cmd(driver, IL0323_CMD_PWR, il0323_pwr, sizeof(il0323_pwr))) {
return -EIO;
}
/* Turn on: booster, controller, regulators, and sensor. */
if (il0323_write_cmd(driver, IL0323_CMD_PON, NULL, 0)) {
return -EIO;
}
k_msleep(IL0323_PON_DELAY);
il0323_busy_wait(driver);
/* Pannel settings, KW mode */
tmp[0] = IL0323_PSR_UD | IL0323_PSR_SHL | IL0323_PSR_SHD | IL0323_PSR_RST;
#if EPD_PANEL_WIDTH == 80
#if EPD_PANEL_HEIGHT == 128
tmp[0] |= IL0323_PSR_RES_HEIGHT;
#endif /* panel height */
#else
tmp[0] |= IL0323_PSR_RES_WIDTH;
#if EPD_PANEL_HEIGHT == 96
tmp[0] |= IL0323_PSR_RES_HEIGHT;
#else
#endif /* panel height */
#endif /* panel width */
LOG_HEXDUMP_DBG(tmp, 1, "PSR");
if (il0323_write_cmd(driver, IL0323_CMD_PSR, tmp, 1)) {
return -EIO;
}
/* Set panel resolution */
tmp[IL0323_TRES_HRES_IDX] = EPD_PANEL_WIDTH;
tmp[IL0323_TRES_VRES_IDX] = EPD_PANEL_HEIGHT;
LOG_HEXDUMP_DBG(tmp, IL0323_TRES_REG_LENGTH, "TRES");
if (il0323_write_cmd(driver, IL0323_CMD_TRES, tmp, IL0323_TRES_REG_LENGTH)) {
return -EIO;
}
tmp[IL0323_CDI_CDI_IDX] = DT_INST_PROP(0, cdi);
LOG_HEXDUMP_DBG(tmp, IL0323_CDI_REG_LENGTH, "CDI");
if (il0323_write_cmd(driver, IL0323_CMD_CDI, tmp, IL0323_CDI_REG_LENGTH)) {
return -EIO;
}
tmp[0] = DT_INST_PROP(0, tcon);
if (il0323_write_cmd(driver, IL0323_CMD_TCON, tmp, 1)) {
return -EIO;
}
/* Enable Auto Sequence */
tmp[0] = IL0323_AUTO_PON_DRF_POF;
if (il0323_write_cmd(driver, IL0323_CMD_AUTO, tmp, 1)) {
return -EIO;
}
if (il0323_clear_and_write_buffer(dev, 0xff, false)) {
return -1;
}
return 0;
}
static int il0323_init(const struct device *dev) {
struct il0323_data *driver = dev->data;
LOG_DBG("");
driver->spi_dev = device_get_binding(IL0323_BUS_NAME);
if (driver->spi_dev == NULL) {
LOG_ERR("Could not get SPI device for IL0323");
return -EIO;
}
driver->spi_config.frequency = IL0323_SPI_FREQ;
driver->spi_config.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8);
driver->spi_config.slave = DT_INST_REG_ADDR(0);
driver->spi_config.cs = NULL;
driver->reset = device_get_binding(IL0323_RESET_CNTRL);
if (driver->reset == NULL) {
LOG_ERR("Could not get GPIO port for IL0323 reset");
return -EIO;
}
gpio_pin_configure(driver->reset, IL0323_RESET_PIN, GPIO_OUTPUT_INACTIVE | IL0323_RESET_FLAGS);
driver->dc = device_get_binding(IL0323_DC_CNTRL);
if (driver->dc == NULL) {
LOG_ERR("Could not get GPIO port for IL0323 DC signal");
return -EIO;
}
gpio_pin_configure(driver->dc, IL0323_DC_PIN, GPIO_OUTPUT_INACTIVE | IL0323_DC_FLAGS);
driver->busy = device_get_binding(IL0323_BUSY_CNTRL);
if (driver->busy == NULL) {
LOG_ERR("Could not get GPIO port for IL0323 busy signal");
return -EIO;
}
gpio_pin_configure(driver->busy, IL0323_BUSY_PIN, GPIO_INPUT | IL0323_BUSY_FLAGS);
#if defined(IL0323_CS_CNTRL)
driver->cs_ctrl.gpio_dev = device_get_binding(IL0323_CS_CNTRL);
if (!driver->cs_ctrl.gpio_dev) {
LOG_ERR("Unable to get SPI GPIO CS device");
return -EIO;
}
driver->cs_ctrl.gpio_pin = IL0323_CS_PIN;
driver->cs_ctrl.gpio_dt_flags = IL0323_CS_FLAGS;
driver->cs_ctrl.delay = 0U;
driver->spi_config.cs = &driver->cs_ctrl;
#endif
return il0323_controller_init(dev);
}
static struct il0323_data il0323_driver;
static struct display_driver_api il0323_driver_api = {
.blanking_on = il0323_blanking_on,
.blanking_off = il0323_blanking_off,
.write = il0323_write,
.read = il0323_read,
.get_framebuffer = il0323_get_framebuffer,
.set_brightness = il0323_set_brightness,
.set_contrast = il0323_set_contrast,
.get_capabilities = il0323_get_capabilities,
.set_pixel_format = il0323_set_pixel_format,
.set_orientation = il0323_set_orientation,
};
DEVICE_DT_INST_DEFINE(0, il0323_init, device_pm_control_nop, &il0323_driver, NULL, POST_KERNEL,
CONFIG_APPLICATION_INIT_PRIORITY, &il0323_driver_api);

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2020 PHYTEC Messtechnik GmbH, Peter Johanson
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_
#define ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_
#define IL0323_CMD_PSR 0x00
#define IL0323_CMD_PWR 0x01
#define IL0323_CMD_POF 0x02
#define IL0323_CMD_PFS 0x03
#define IL0323_CMD_PON 0x04
#define IL0323_CMD_PMES 0x05
#define IL0323_CMD_CPSET 0x06
#define IL0323_CMD_DSLP 0x07
#define IL0323_CMD_DTM1 0x10
#define IL0323_CMD_DSP 0x11
#define IL0323_CMD_DRF 0x12
#define IL0323_CMD_DTM2 0x13
#define IL0323_CMD_AUTO 0x17
#define IL0323_CMD_LUTOPT 0x2A
#define IL0323_CMD_PLL 0x30
#define IL0323_CMD_TSC 0x40
#define IL0323_CMD_TSE 0x41
#define IL0323_CMD_PBC 0x44
#define IL0323_CMD_CDI 0x50
#define IL0323_CMD_LPD 0x51
#define IL0323_CMD_TCON 0x60
#define IL0323_CMD_TRES 0x61
#define IL0323_CMD_GSST 0x65
#define IL0323_CMD_REV 0x70
#define IL0323_CMD_FLG 0x71
#define IL0323_CMD_CRC 0x72
#define IL0323_CMD_AMV 0x80
#define IL0323_CMD_VV 0x81
#define IL0323_CMD_VDCS 0x82
#define IL0323_CMD_PTL 0x90
#define IL0323_CMD_PIN 0x91
#define IL0323_CMD_POUT 0x92
#define IL0323_CMD_PGM 0xA0
#define IL0323_CMD_APG 0xA1
#define IL0323_CMD_ROTP 0xA2
#define IL0323_CMD_CCSET 0xE0
#define IL0323_CMD_PWS 0xE3
#define IL0323_CMD_LVSEL 0xE4
#define IL0323_CMD_TSSET 0xE5
#define IL0323_PSR_RES_WIDTH BIT(7)
#define IL0323_PSR_RES_HEIGHT BIT(6)
#define IL0323_PSR_LUT_REG BIT(5)
#define IL0323_PSR_LUT_OTP BIT(4)
#define IL0323_PSR_UD BIT(3)
#define IL0323_PSR_SHL BIT(2)
#define IL0323_PSR_SHD BIT(1)
#define IL0323_PSR_RST BIT(0)
#define IL0323_AUTO_PON_DRF_POF 0xA5
#define IL0323_AUTO_PON_DRF_POF_DSLP 0xA7
#define IL0323_CDI_REG_LENGTH 1U
#define IL0323_CDI_CDI_IDX 0
#define IL0323_TRES_REG_LENGTH 2U
#define IL0323_TRES_HRES_IDX 0
#define IL0323_TRES_VRES_IDX 1
#define IL0323_PTL_REG_LENGTH 5U
#define IL0323_PTL_HRST_IDX 0
#define IL0323_PTL_HRED_IDX 1
#define IL0323_PTL_VRST_IDX 2
#define IL0323_PTL_VRED_IDX 3
#define IL0323_PTL_PT_SCAN BIT(0)
/* Time constants in ms */
#define IL0323_RESET_DELAY 10U
#define IL0323_PON_DELAY 100U
#define IL0323_BUSY_DELAY 1U
#endif /* ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_ */

View file

@ -108,6 +108,6 @@ 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,
DEVICE_DT_INST_DEFINE(0, kscan_composite_init, device_pm_control_nop, &kscan_composite_data,
&kscan_composite_config, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&mock_driver_api);

View file

@ -248,9 +248,9 @@ struct kscan_gpio_item_config {
.cols = {UTIL_LISTIFY(INST_DEMUX_GPIOS(n), _KSCAN_GPIO_OUTPUT_CFG_INIT, n)}, \
}; \
\
DEVICE_AND_API_INIT(kscan_gpio_##n, DT_INST_LABEL(n), kscan_gpio_init_##n, \
&kscan_gpio_data_##n, &kscan_gpio_config_##n, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &gpio_driver_api_##n);
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, device_pm_control_nop, &kscan_gpio_data_##n, \
&kscan_gpio_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&gpio_driver_api_##n);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)

View file

@ -238,9 +238,9 @@ static const struct kscan_driver_api gpio_driver_api = {
.inputs = {UTIL_LISTIFY(INST_INPUT_LEN(n), KSCAN_DIRECT_INPUT_ITEM, n)}, \
.num_of_inputs = INST_INPUT_LEN(n), \
.debounce_period = DT_INST_PROP(n, debounce_period)}; \
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);
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, device_pm_control_nop, &kscan_gpio_data_##n, \
&kscan_gpio_config_##n, POST_KERNEL, CONFIG_ZMK_KSCAN_INIT_PRIORITY, \
&gpio_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)

View file

@ -1,48 +1,158 @@
/*
* Copyright (c) 2020 The ZMK Contributors
* Copyright (c) 2020-2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
#include <device.h>
#include <drivers/kscan.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/kscan.h>
#include <logging/log.h>
#include <sys/__assert.h>
#include <sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct kscan_gpio_item_config {
char *label;
#define INST_DIODE_DIR(n) DT_ENUM_IDX(DT_DRV_INST(n), diode_direction)
#define COND_DIODE_DIR(n, row2col_code, col2row_code) \
COND_CODE_0(INST_DIODE_DIR(n), row2col_code, col2row_code)
#define INST_ROWS_LEN(n) DT_INST_PROP_LEN(n, row_gpios)
#define INST_COLS_LEN(n) DT_INST_PROP_LEN(n, col_gpios)
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
#define USE_INTERRUPTS (!USE_POLLING)
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), code)
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
// TODO (Zephr 2.6): replace the following
// kscan_gpio_dt_spec -> gpio_dt_spec
// KSCAN_GPIO_DT_SPEC_GET_BY_IDX -> GPIO_DT_SPEC_GET_BY_IDX
// gpio_pin_get -> gpio_pin_get_dt
// gpio_pin_set -> gpio_pin_set_dt
// gpio_pin_interrupt_configure -> gpio_pin_interrupt_configure_dt
struct kscan_gpio_dt_spec {
const struct device *port;
gpio_pin_t pin;
gpio_flags_t flags;
gpio_dt_flags_t dt_flags;
};
#define _KSCAN_GPIO_ITEM_CFG_INIT(n, prop, idx) \
#define KSCAN_GPIO_DT_SPEC_GET_BY_IDX(node_id, 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), \
},
.port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \
.pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx), \
.dt_flags = DT_GPIO_FLAGS_BY_IDX(node_id, prop, idx), \
}
#define _KSCAN_GPIO_ROW_CFG_INIT(idx, n) _KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, idx)
#define _KSCAN_GPIO_COL_CFG_INIT(idx, n) _KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, idx)
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx),
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx),
#if !defined(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
static int kscan_gpio_config_interrupts(const struct device **devices,
const struct kscan_gpio_item_config *configs, size_t len,
gpio_flags_t flags) {
for (int i = 0; i < len; i++) {
const struct device *dev = devices[i];
const struct kscan_gpio_item_config *cfg = &configs[i];
enum kscan_diode_direction {
KSCAN_ROW2COL,
KSCAN_COL2ROW,
};
int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);
struct kscan_matrix_irq_callback {
const struct device *dev;
struct gpio_callback callback;
struct k_delayed_work *work;
};
struct kscan_matrix_data {
const struct device *dev;
kscan_callback_t callback;
struct k_delayed_work work;
#if USE_POLLING
struct k_timer poll_timer;
#else
/** Array of length config->inputs.len */
struct kscan_matrix_irq_callback *irqs;
#endif
/**
* Current state of the matrix as a flattened 2D array of length
* (config->rows.len * config->cols.len)
*/
bool *current_state;
/** Buffer for reading in the next matrix state. Parallel array to current_state. */
bool *next_state;
};
struct kscan_gpio_list {
const struct kscan_gpio_dt_spec *gpios;
size_t len;
};
/** Define a kscan_gpio_list from a compile-time GPIO array. */
#define KSCAN_GPIO_LIST(gpio_array) \
((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)})
struct kscan_matrix_config {
struct kscan_gpio_list rows;
struct kscan_gpio_list cols;
struct kscan_gpio_list inputs;
struct kscan_gpio_list outputs;
int32_t debounce_period_ms;
int32_t poll_period_ms;
enum kscan_diode_direction diode_direction;
};
/**
* Get the index into a matrix state array from a row and column.
*/
static int state_index_rc(const struct kscan_matrix_config *config, const int row, const int col) {
__ASSERT(row < config->rows.len, "Invalid row %i", row);
__ASSERT(col < config->cols.len, "Invalid column %i", col);
return (col * config->rows.len) + row;
}
/**
* Get the index into a matrix state array from input/output pin indices.
*/
static int state_index_io(const struct kscan_matrix_config *config, const int input_idx,
const int output_idx) {
return (config->diode_direction == KSCAN_ROW2COL)
? state_index_rc(config, output_idx, input_idx)
: state_index_rc(config, input_idx, output_idx);
}
static int kscan_matrix_set_all_outputs(const struct device *dev, const int value) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->outputs.gpios[i];
int err = gpio_pin_set(gpio->port, gpio->pin, value);
if (err) {
LOG_ERR("Unable to enable matrix GPIO interrupt");
LOG_ERR("Failed to set output %i to %i: %i", i, value, err);
return err;
}
}
return 0;
}
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->inputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = gpio_pin_interrupt_configure(gpio->port, gpio->pin, flags);
if (err) {
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err;
}
}
@ -51,257 +161,299 @@ static int kscan_gpio_config_interrupts(const struct device **devices,
}
#endif
#define COND_POLLING(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (code), ())
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), (code))
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_enable(const struct device *dev) {
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
if (err) {
return err;
}
#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_ROWS(n)), \
(INST_MATRIX_COLS(n)))
#define INST_INPUT_LEN(n) \
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_COLS(n)), \
(INST_MATRIX_ROWS(n)))
// While interrupts are enabled, set all outputs active so a pressed key
// will trigger an interrupt.
return kscan_matrix_set_all_outputs(dev, 1);
}
#endif
#define GPIO_INST_INIT(n) \
COND_INTERRUPTS( \
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; \
const struct device *dev; \
}; \
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; \
COND_POLLING(struct k_timer poll_timer;) \
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)]; \
const struct device *rows[INST_MATRIX_ROWS(n)]; \
const struct device *cols[INST_MATRIX_COLS(n)]; \
const struct device *dev; \
}; \
static const struct device **kscan_gpio_input_devices_##n(const struct device *dev) { \
struct kscan_gpio_data_##n *data = dev->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( \
const struct device *dev) { \
const struct kscan_gpio_config_##n *cfg = dev->config; \
return (( \
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->cols), (cfg->rows)))); \
} \
static const struct device **kscan_gpio_output_devices_##n(const struct device *dev) { \
struct kscan_gpio_data_##n *data = dev->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( \
const struct device *dev) { \
const struct kscan_gpio_config_##n *cfg = dev->config; \
return ( \
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->rows), (cfg->cols))); \
} \
COND_INTERRUPTS( \
static int kscan_gpio_enable_interrupts_##n(const 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_LEVEL_ACTIVE); \
} static int kscan_gpio_disable_interrupts_##n(const 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(const struct device *dev, int value) { \
int err; \
for (int i = 0; i < INST_OUTPUT_LEN(n); i++) { \
const 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]; \
if ((err = gpio_pin_set(in_dev, cfg->pin, value))) { \
LOG_DBG("FAILED TO SET OUTPUT %d to %d", cfg->pin, err); \
} \
} \
} \
static void kscan_gpio_set_matrix_state_##n( \
bool state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)], uint32_t input_index, \
uint32_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(const struct device *dev) { \
COND_INTERRUPTS(bool submit_follow_up_read = false;) \
struct kscan_gpio_data_##n *data = dev->data; \
static bool read_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)]; \
int err; \
/* 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. */ \
COND_INTERRUPTS(kscan_gpio_set_output_state_##n(dev, 0);) \
for (int o = 0; o < INST_OUTPUT_LEN(n); o++) { \
const 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]; \
err = gpio_pin_set(out_dev, out_cfg->pin, 1); \
if (err) { \
LOG_ERR("Failed to set output active (err %d)", err); \
return err; \
} \
for (int i = 0; i < INST_INPUT_LEN(n); i++) { \
const 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); \
} \
err = gpio_pin_set(out_dev, out_cfg->pin, 0); \
if (err) { \
LOG_ERR("Failed to set output inactive (err %d)", err); \
return err; \
} \
} \
/* Set all our outputs as active again. */ \
COND_INTERRUPTS(kscan_gpio_set_output_state_##n(dev, 1);) \
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]; \
/* Follow up reads needed because further interrupts won't fire on already tripped \
* input GPIO pins */ \
COND_INTERRUPTS(submit_follow_up_read = (submit_follow_up_read || pressed);) \
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); \
} \
} \
} \
COND_INTERRUPTS( \
if (submit_follow_up_read) { \
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(5)); \
})) \
} else { kscan_gpio_enable_interrupts_##n(dev); }) \
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); \
} \
COND_INTERRUPTS(static void kscan_gpio_irq_callback_handler_##n( \
const 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); \
kscan_gpio_disable_interrupts_##n(data->dev); \
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))); \
})) \
}) \
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_disable(const struct device *dev) {
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_DISABLE);
if (err) {
return err;
}
// While interrupts are disabled, set all outputs inactive so
// kscan_matrix_read() can scan them one by one.
return kscan_matrix_set_all_outputs(dev, 0);
}
#endif
#if USE_INTERRUPTS
static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
const gpio_port_pins_t pin) {
struct kscan_matrix_irq_callback *data =
CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
const struct kscan_matrix_config *config = data->dev->config;
// Disable our interrupts temporarily to avoid re-entry while we scan.
kscan_matrix_interrupt_disable(data->dev);
// TODO (Zephyr 2.6): use k_work_reschedule()
k_delayed_work_cancel(data->work);
k_delayed_work_submit(data->work, K_MSEC(config->debounce_period_ms));
}
#endif
static int kscan_matrix_read(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
const struct kscan_matrix_config *config = dev->config;
// Scan the matrix.
for (int o = 0; o < config->outputs.len; o++) {
const struct kscan_gpio_dt_spec *out_gpio = &config->outputs.gpios[o];
int err = gpio_pin_set(out_gpio->port, out_gpio->pin, 1);
if (err) {
LOG_ERR("Failed to set output %i active: %i", o, err);
return err;
}
for (int i = 0; i < config->inputs.len; i++) {
const struct kscan_gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
const int index = state_index_io(config, i, o);
data->next_state[index] = gpio_pin_get(in_gpio->port, in_gpio->pin);
}
err = gpio_pin_set(out_gpio->port, out_gpio->pin, 0);
if (err) {
LOG_ERR("Failed to set output %i inactive: %i", o, err);
return err;
}
}
// Process the new state.
#if USE_INTERRUPTS
bool submit_followup_read = false;
#endif
for (int r = 0; r < config->rows.len; r++) {
for (int c = 0; c < config->cols.len; c++) {
const int index = state_index_rc(config, r, c);
const bool pressed = data->next_state[index];
// Follow up reads are needed if any key is pressed because further
// interrupts won't fire on already tripped GPIO pins.
#if USE_INTERRUPTS
submit_followup_read = submit_followup_read || pressed;
#endif
if (pressed != data->current_state[index]) {
LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
data->current_state[index] = pressed;
data->callback(dev, r, c, pressed);
}
}
}
#if USE_INTERRUPTS
if (submit_followup_read) {
// At least one key is pressed. Poll until everything is released.
// TODO (Zephyr 2.6): use k_work_reschedule()
k_delayed_work_cancel(&data->work);
k_delayed_work_submit(&data->work, K_MSEC(config->debounce_period_ms));
} else {
// All keys are released. Return to waiting for an interrupt.
kscan_matrix_interrupt_enable(dev);
}
#endif
return 0;
}
#if USE_POLLING
static void kscan_matrix_timer_handler(struct k_timer *timer) {
struct kscan_matrix_data *data = CONTAINER_OF(timer, struct kscan_matrix_data, poll_timer);
k_delayed_work_submit(&data->work, K_NO_WAIT);
}
#endif
static void kscan_matrix_work_handler(struct k_work *work) {
struct k_delayed_work *dwork = CONTAINER_OF(work, struct k_delayed_work, work);
struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
kscan_matrix_read(data->dev);
}
static int kscan_matrix_configure(const struct device *dev, const kscan_callback_t callback) {
struct kscan_matrix_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_matrix_enable(const struct device *dev) {
#if USE_POLLING
struct kscan_matrix_data *data = dev->data;
const struct kscan_matrix_config *config = dev->config;
k_timer_start(&data->poll_timer, K_MSEC(config->poll_period_ms),
K_MSEC(config->poll_period_ms));
return 0;
#else
// Read will automatically enable interrupts once done.
return kscan_matrix_read(dev);
#endif
}
static int kscan_matrix_disable(const struct device *dev) {
#if USE_POLLING
struct kscan_matrix_data *data = dev->data;
k_timer_stop(&data->poll_timer);
return 0;
#else
return kscan_matrix_interrupt_disable(dev);
#endif
}
static int kscan_matrix_init_input_inst(const struct device *dev,
const struct kscan_gpio_dt_spec *gpio, const int index) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure(gpio->port, gpio->pin, GPIO_INPUT | gpio->dt_flags);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
#if USE_INTERRUPTS
struct kscan_matrix_data *data = dev->data;
struct kscan_matrix_irq_callback *irq = &data->irqs[index];
irq->dev = dev;
irq->work = &data->work;
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin));
err = gpio_add_callback(gpio->port, &irq->callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
return err;
}
#endif
return 0;
}
static int kscan_matrix_init_inputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->inputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = kscan_matrix_init_input_inst(dev, gpio, i);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init_output_inst(const struct device *dev,
const struct kscan_gpio_dt_spec *gpio) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure(gpio->port, gpio->pin, GPIO_OUTPUT | gpio->dt_flags);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for output", gpio->pin, gpio->port->name);
return 0;
}
static int kscan_matrix_init_outputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->outputs.gpios[i];
int err = kscan_matrix_init_output_inst(dev, gpio);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
data->dev = dev;
kscan_matrix_init_inputs(dev);
kscan_matrix_init_outputs(dev);
kscan_matrix_set_all_outputs(dev, 0);
k_delayed_work_init(&data->work, kscan_matrix_work_handler);
#if USE_POLLING
k_timer_init(&data->poll_timer, kscan_matrix_timer_handler, NULL);
#endif
return 0;
}
static const struct kscan_driver_api kscan_matrix_api = {
.config = kscan_matrix_configure,
.enable_callback = kscan_matrix_enable,
.disable_callback = kscan_matrix_disable,
};
#define KSCAN_MATRIX_INIT(index) \
static const struct kscan_gpio_dt_spec kscan_matrix_rows_##index[] = { \
UTIL_LISTIFY(INST_ROWS_LEN(index), KSCAN_GPIO_ROW_CFG_INIT, index)}; \
\
static struct kscan_gpio_data_##n kscan_gpio_data_##n = { \
.rows = {[INST_MATRIX_ROWS(n) - 1] = NULL}, .cols = {[INST_MATRIX_COLS(n) - 1] = NULL}}; \
static int kscan_gpio_configure_##n(const struct device *dev, kscan_callback_t callback) { \
struct kscan_gpio_data_##n *data = dev->data; \
if (!callback) { \
return -EINVAL; \
} \
data->callback = callback; \
LOG_DBG("Configured GPIO %d", n); \
return 0; \
static const struct kscan_gpio_dt_spec kscan_matrix_cols_##index[] = { \
UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
\
static bool kscan_current_state_##index[INST_MATRIX_LEN(index)]; \
static bool kscan_next_state_##index[INST_MATRIX_LEN(index)]; \
\
COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
\
static struct kscan_matrix_data kscan_matrix_data_##index = { \
.current_state = kscan_current_state_##index, \
.next_state = kscan_next_state_##index, \
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
\
static struct kscan_matrix_config kscan_matrix_config_##index = { \
.rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##index), \
.cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##index), \
.inputs = KSCAN_GPIO_LIST( \
COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
.outputs = KSCAN_GPIO_LIST( \
COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
.debounce_period_ms = DT_INST_PROP(index, debounce_period), \
.poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
.diode_direction = INST_DIODE_DIR(index), \
}; \
static int kscan_gpio_enable_##n(const struct device *dev) { \
COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \
k_timer_start(&data->poll_timer, K_MSEC(10), K_MSEC(10)); \
return 0;), \
(int err = kscan_gpio_enable_interrupts_##n(dev); \
if (err) { return err; } return kscan_gpio_read_##n(dev);)) \
}; \
static int kscan_gpio_disable_##n(const struct device *dev) { \
COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \
k_timer_stop(&data->poll_timer); return 0;), \
(return kscan_gpio_disable_interrupts_##n(dev);)) \
}; \
COND_POLLING(static void kscan_gpio_timer_handler_##n(struct k_timer *timer) { \
struct kscan_gpio_data_##n *data = \
CONTAINER_OF(timer, struct kscan_gpio_data_##n, poll_timer); \
k_work_submit(&data->work.work); \
}) \
static int kscan_gpio_init_##n(const struct device *dev) { \
struct kscan_gpio_data_##n *data = dev->data; \
int err; \
const 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; \
} else { \
LOG_DBG("Configured pin %d on %s for input", in_cfg->pin, in_cfg->label); \
} \
COND_INTERRUPTS( \
irq_callbacks_##n[i].work = &data->work; irq_callbacks_##n[i].dev = dev; \
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 input device"); \
return err; \
}) \
} \
const 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 | 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); \
COND_POLL_OR_INTERRUPTS( \
(k_timer_init(&data->poll_timer, kscan_gpio_timer_handler_##n, NULL); \
kscan_gpio_set_output_state_##n(dev, 0);), \
(kscan_gpio_set_output_state_##n(dev, 1);)) \
return 0; \
} \
static const struct kscan_driver_api gpio_driver_api_##n = { \
.config = kscan_gpio_configure_##n, \
.enable_callback = kscan_gpio_enable_##n, \
.disable_callback = kscan_gpio_disable_##n, \
}; \
static const struct kscan_gpio_config_##n kscan_gpio_config_##n = { \
.rows = {UTIL_LISTIFY(INST_MATRIX_ROWS(n), _KSCAN_GPIO_ROW_CFG_INIT, n)}, \
.cols = {UTIL_LISTIFY(INST_MATRIX_COLS(n), _KSCAN_GPIO_COL_CFG_INIT, n)}, \
}; \
DEVICE_AND_API_INIT(kscan_gpio_##n, DT_INST_LABEL(n), kscan_gpio_init_##n, \
&kscan_gpio_data_##n, &kscan_gpio_config_##n, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &gpio_driver_api_##n);
\
DEVICE_DT_INST_DEFINE(index, &kscan_matrix_init, device_pm_control_nop, \
&kscan_matrix_data_##index, &kscan_matrix_config_##index, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &kscan_matrix_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)
DT_INST_FOREACH_STATUS_OKAY(KSCAN_MATRIX_INIT);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
#endif // DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

View file

@ -88,8 +88,8 @@ static int kscan_mock_configure(const struct device *dev, kscan_callback_t callb
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), .exit_after = DT_INST_PROP(n, exit_after)}; \
DEVICE_AND_API_INIT(kscan_mock_##n, DT_INST_LABEL(n), kscan_mock_init_##n, \
&kscan_mock_data_##n, &kscan_mock_config_##n, APPLICATION, \
DEVICE_DT_INST_DEFINE(n, kscan_mock_init_##n, device_pm_control_nop, &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)

View file

@ -1,5 +1,5 @@
# Copyright (c) 2020 The ZMK Contributors
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
add_subdirectory_ifdef(CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER battery_voltage_divider)
add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery)
add_subdirectory_ifdef(CONFIG_EC11 ec11)

View file

@ -1,5 +1,5 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
rsource "battery_voltage_divider/Kconfig"
rsource "battery/Kconfig"
rsource "ec11/Kconfig"

View file

@ -0,0 +1,10 @@
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_include_directories(.)
zephyr_library()
zephyr_library_sources(battery_common.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_NRF_VDDH battery_nrf_vddh.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER battery_voltage_divider.c)

View file

@ -0,0 +1,21 @@
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
config ZMK_BATTERY
bool "ZMK battery monitoring"
help
Enable battery monitoring
config ZMK_BATTERY_NRF_VDDH
bool "ZMK nRF VDDH battery monitoring"
select ADC
select ZMK_BATTERY
help
Enable ZMK nRF VDDH voltage driver for battery monitoring.
config ZMK_BATTERY_VOLTAGE_DIVIDER
bool "ZMK battery voltage divider"
select ADC
select ZMK_BATTERY
help
Enable ZMK battery voltage divider driver for battery monitoring.

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <drivers/sensor.h>
#include "battery_common.h"
int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
struct sensor_value *val_out) {
switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
val_out->val1 = value->millivolts / 1000;
val_out->val2 = (value->millivolts % 1000) * 1000U;
break;
case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
val_out->val1 = value->state_of_charge;
val_out->val2 = 0;
break;
default:
return -ENOTSUP;
}
return 0;
}
uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) {
// Simple linear approximation of a battery based off adafruit's discharge graph:
// https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
if (bat_mv >= 4200) {
return 100;
} else if (bat_mv <= 3450) {
return 0;
}
return bat_mv * 2 / 15 - 459;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <drivers/sensor.h>
#include <stdint.h>
struct battery_value {
uint16_t adc_raw;
uint16_t millivolts;
uint8_t state_of_charge;
};
int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
struct sensor_value *val_out);
uint8_t lithium_ion_mv_to_pct(int16_t bat_mv);

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*
* This is a simplified version of battery_voltage_divider.c which always reads
* the VDDHDIV5 channel of the &adc node and multiplies it by 5.
*/
#define DT_DRV_COMPAT zmk_battery_nrf_vddh
#include <device.h>
#include <devicetree.h>
#include <drivers/adc.h>
#include <drivers/sensor.h>
#include <logging/log.h>
#include "battery_common.h"
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define VDDHDIV (5)
static const struct device *adc = DEVICE_DT_GET(DT_NODELABEL(adc));
struct vddh_data {
struct adc_channel_cfg acc;
struct adc_sequence as;
struct battery_value value;
};
static int vddh_sample_fetch(const struct device *dev, enum sensor_channel chan) {
// Make sure selected channel is supported
if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE &&
chan != SENSOR_CHAN_ALL) {
LOG_DBG("Selected channel is not supported: %d.", chan);
return -ENOTSUP;
}
struct vddh_data *drv_data = dev->data;
struct adc_sequence *as = &drv_data->as;
int rc = adc_read(adc, as);
as->calibrate = false;
if (rc != 0) {
LOG_ERR("Failed to read ADC: %d", rc);
return rc;
}
int32_t val = drv_data->value.adc_raw;
rc = adc_raw_to_millivolts(adc_ref_internal(adc), drv_data->acc.gain, as->resolution, &val);
if (rc != 0) {
LOG_ERR("Failed to convert raw ADC to mV: %d", rc);
return rc;
}
drv_data->value.millivolts = val * VDDHDIV;
drv_data->value.state_of_charge = lithium_ion_mv_to_pct(drv_data->value.millivolts);
LOG_DBG("ADC raw %d ~ %d mV => %d%%", drv_data->value.adc_raw, drv_data->value.millivolts,
drv_data->value.state_of_charge);
return rc;
}
static int vddh_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
struct vddh_data const *drv_data = dev->data;
return battery_channel_get(&drv_data->value, chan, val);
}
static const struct sensor_driver_api vddh_api = {
.sample_fetch = vddh_sample_fetch,
.channel_get = vddh_channel_get,
};
static int vddh_init(const struct device *dev) {
struct vddh_data *drv_data = dev->data;
if (!device_is_ready(adc)) {
LOG_ERR("ADC device is not ready %s", adc->name);
return -ENODEV;
}
drv_data->as = (struct adc_sequence){
.channels = BIT(0),
.buffer = &drv_data->value.adc_raw,
.buffer_size = sizeof(drv_data->value.adc_raw),
.oversampling = 4,
.calibrate = true,
};
#ifdef CONFIG_ADC_NRFX_SAADC
drv_data->acc = (struct adc_channel_cfg){
.gain = ADC_GAIN_1_5,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
.input_positive = SAADC_CH_PSELN_PSELN_VDDHDIV5,
};
drv_data->as.resolution = 12;
#else
#error Unsupported ADC
#endif
const int rc = adc_channel_setup(adc, &drv_data->acc);
LOG_DBG("VDDHDIV5 setup returned %d", rc);
return rc;
}
static struct vddh_data vddh_data;
DEVICE_DT_INST_DEFINE(0, &vddh_init, device_pm_control_nop, &vddh_data, NULL, POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY, &vddh_api);

View file

@ -7,11 +7,14 @@
#define DT_DRV_COMPAT zmk_battery_voltage_divider
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/adc.h>
#include <drivers/sensor.h>
#include <logging/log.h>
#include "battery_common.h"
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct io_channel_config {
@ -37,24 +40,9 @@ struct bvd_data {
const struct device *gpio;
struct adc_channel_cfg acc;
struct adc_sequence as;
uint16_t adc_raw;
uint16_t voltage;
uint8_t state_of_charge;
struct battery_value value;
};
static uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) {
// Simple linear approximation of a battery based off adafruit's discharge graph:
// https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
if (bat_mv >= 4200) {
return 100;
} else if (bat_mv <= 3450) {
return 0;
}
return bat_mv * 2 / 15 - 459;
}
static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) {
struct bvd_data *drv_data = dev->data;
const struct bvd_config *drv_cfg = dev->config;
@ -87,18 +75,18 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan)
as->calibrate = false;
if (rc == 0) {
int32_t val = drv_data->adc_raw;
int32_t val = drv_data->value.adc_raw;
adc_raw_to_millivolts(adc_ref_internal(drv_data->adc), drv_data->acc.gain, as->resolution,
&val);
uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm;
LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->adc_raw, val, millivolts);
LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->value.adc_raw, val, millivolts);
uint8_t percent = lithium_ion_mv_to_pct(millivolts);
LOG_DBG("Percent: %d", percent);
drv_data->voltage = millivolts;
drv_data->state_of_charge = percent;
drv_data->value.millivolts = millivolts;
drv_data->value.state_of_charge = percent;
} else {
LOG_DBG("Failed to read ADC: %d", rc);
}
@ -119,23 +107,7 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan)
static int bvd_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
struct bvd_data *drv_data = dev->data;
switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
val->val1 = drv_data->voltage / 1000;
val->val2 = (drv_data->voltage % 1000) * 1000U;
break;
case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
val->val1 = drv_data->state_of_charge;
val->val2 = 0;
break;
default:
return -ENOTSUP;
}
return 0;
return battery_channel_get(&drv_data->value, chan, val);
}
static const struct sensor_driver_api bvd_api = {
@ -173,8 +145,8 @@ static int bvd_init(const struct device *dev) {
drv_data->as = (struct adc_sequence){
.channels = BIT(0),
.buffer = &drv_data->adc_raw,
.buffer_size = sizeof(drv_data->adc_raw),
.buffer = &drv_data->value.adc_raw,
.buffer_size = sizeof(drv_data->value.adc_raw),
.oversampling = 4,
.calibrate = true,
};
@ -217,5 +189,5 @@ static const struct bvd_config bvd_cfg = {
.full_ohm = DT_INST_PROP(0, full_ohms),
};
DEVICE_AND_API_INIT(bvd_dev, DT_INST_LABEL(0), &bvd_init, &bvd_data, &bvd_cfg, POST_KERNEL,
DEVICE_DT_INST_DEFINE(0, &bvd_init, device_pm_control_nop, &bvd_data, &bvd_cfg, POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY, &bvd_api);

View file

@ -1,6 +0,0 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_library()
zephyr_library_sources(battery_voltage_divider.c)

View file

@ -1,8 +0,0 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
config ZMK_BATTERY_VOLTAGE_DIVIDER
bool "ZMK battery voltage divider"
select ADC
help
Enable ZMK battery voltage divider driver for battery monitoring.

View file

@ -142,7 +142,7 @@ int ec11_init(const struct device *dev) {
.b_flags = DT_INST_GPIO_FLAGS(n, b_gpios), \
COND_CODE_0(DT_INST_NODE_HAS_PROP(n, resolution), (1), (DT_INST_PROP(n, resolution))), \
}; \
DEVICE_AND_API_INIT(ec11_##n, DT_INST_LABEL(n), ec11_init, &ec11_data_##n, &ec11_cfg_##n, \
DEVICE_DT_INST_DEFINE(n, ec11_init, device_pm_control_nop, &ec11_data_##n, &ec11_cfg_##n, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &ec11_driver_api);
DT_INST_FOREACH_STATUS_OKAY(EC11_INST)

View file

@ -17,6 +17,11 @@ properties:
debounce-period:
type: int
default: 5
description: Debounce time in milliseconds
poll-period-ms:
type: int
default: 10
description: Time between reads in milliseconds when polling is enabled
diode-direction:
type: string
default: row2col

View file

@ -0,0 +1,11 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Battery SoC monitoring using nRF VDDH
compatible: "zmk,battery-nrf-vddh"
properties:
label:
required: true
type: string

View file

@ -0,0 +1,61 @@
# Copyright (c) 2020, Phytec Messtechnik GmbH, Peter Johanson
# SPDX-License-Identifier: Apache-2.0
description: IL0323 EPD display controller
compatible: "gooddisplay,il0323"
include: spi-device.yaml
properties:
height:
type: int
required: true
description: Height in pixel of the panel driven by the controller
width:
type: int
required: true
description: Width in pixel of the panel driven by the controller
reset-gpios:
type: phandle-array
required: true
description: RESET pin.
The RESET pin of GD7965 is active low.
If connected directly the MCU pin should be configured
as active low.
dc-gpios:
type: phandle-array
required: true
description: DC pin.
The DC pin of GD7965 is active low (transmission command byte).
If connected directly the MCU pin should be configured
as active low.
busy-gpios:
type: phandle-array
required: true
description: BUSY pin.
The BUSY pin of GD7965 is active low.
If connected directly the MCU pin should be configured
as active low.
pwr:
type: uint8-array
required: true
description: Power Setting (PWR) values
cdi:
type: int
required: true
description: VCOM and data interval value
tcon:
type: int
required: true
description: TCON setting value

View file

@ -6,13 +6,7 @@
#pragma once
#include <zmk/keys.h>
#include <zmk/hid.h>
enum zmk_endpoint {
ZMK_ENDPOINT_USB,
ZMK_ENDPOINT_BLE,
};
#include <zmk/endpoints_types.h>
int zmk_endpoints_select(enum zmk_endpoint endpoint);
int zmk_endpoints_toggle();

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
enum zmk_endpoint {
ZMK_ENDPOINT_USB,
ZMK_ENDPOINT_BLE,
};

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr.h>
#include <zmk/endpoints_types.h>
#include <zmk/event_manager.h>
struct zmk_endpoint_selection_changed {
enum zmk_endpoint endpoint;
};
ZMK_EVENT_DECLARE(zmk_endpoint_selection_changed);

View file

@ -7,6 +7,7 @@
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <power/power.h>
#include <logging/log.h>
@ -55,6 +56,8 @@ void activity_work_handler(struct k_work *work) {
int32_t inactive_time = current - activity_last_uptime;
#if IS_ENABLED(CONFIG_ZMK_SLEEP)
if (inactive_time > MAX_SLEEP_MS) {
// Put devices in low power mode before sleeping
pm_power_state_force((struct pm_state_info){PM_STATE_STANDBY, 0, 0});
set_state(ZMK_ACTIVITY_SLEEP);
} else
#endif /* IS_ENABLED(CONFIG_ZMK_SLEEP) */

View file

@ -49,7 +49,7 @@ static const struct behavior_driver_api behavior_bt_driver_api = {
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_bt, DT_INST_LABEL(0), behavior_bt_init, NULL, NULL, APPLICATION,
DEVICE_DT_INST_DEFINE(0, behavior_bt_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_bt_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -73,7 +73,7 @@ static const struct behavior_driver_api behavior_ext_power_driver_api = {
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_ext_power, DT_INST_LABEL(0), behavior_ext_power_init, NULL, NULL,
APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, &behavior_ext_power_driver_api);
DEVICE_DT_INST_DEFINE(0, behavior_ext_power_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_APPLICATION_INIT_PRIORITY, &behavior_ext_power_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -51,6 +51,11 @@ enum decision_moment {
HT_QUICK_TAP,
};
struct decision_trigger_event {
enum decision_moment decision_moment;
int32_t other_key_position;
};
struct behavior_hold_tap_config {
int tapping_term_ms;
char *hold_behavior_dev;
@ -72,6 +77,9 @@ struct active_hold_tap {
const struct behavior_hold_tap_config *config;
struct k_delayed_work work;
bool work_is_cancelled;
// initialized to -1, meaning no other key has been pressed yet
int32_t position_of_first_other_key_pressed;
};
// The undecided hold tap is the hold tap that needs to be decided before
@ -210,6 +218,7 @@ static struct active_hold_tap *store_hold_tap(uint32_t position, uint32_t param_
active_hold_taps[i].param_hold = param_hold;
active_hold_taps[i].param_tap = param_tap;
active_hold_taps[i].timestamp = timestamp;
active_hold_taps[i].position_of_first_other_key_pressed = -1;
return &active_hold_taps[i];
}
return NULL;
@ -222,33 +231,62 @@ static void clear_hold_tap(struct active_hold_tap *hold_tap) {
}
static bool
are_any_hold_enabler_keys_pressed(const struct behavior_hold_tap_config *hold_tap_config) {
struct behavior_hold_tap_config config = *hold_tap_config;
if (hold_tap_config->hold_enabler_keys_len == 0) {
does_position_of_first_other_keypress_permit_hold_behavior(struct active_hold_tap *hold_tap) {
if (hold_tap->config->hold_enabler_keys_len == 0) {
return true;
}
for (int i = 0; i < hold_tap_config->hold_enabler_keys_len; i++) {
if (is_key_position_pressed[config.hold_enabler_keys[i]]) {
for (int i = 0; i < hold_tap->config->hold_enabler_keys_len; i++) {
if (hold_tap->config->hold_enabler_keys[i] ==
hold_tap->position_of_first_other_key_pressed) {
return true;
}
}
return false;
}
static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_moment event) {
switch (event) {
static void store_position_if_is_first_other_key_pressed(
struct active_hold_tap *active_hold_tap,
struct decision_trigger_event *decision_trigger_event) {
// The value of position_of_first_other_key_pressed is initialized to -1 when
// on_hold_tap_binding_pressed calls store_hold_tap.
// If position_of_first_other_key_pressed is >= 0, then this is not the first
// other key to have been pressed.
if (active_hold_tap->position_of_first_other_key_pressed >= 0) {
return;
}
// If the decision_moment is not an other-key-down event, nothing to do.
if (decision_trigger_event->decision_moment != HT_OTHER_KEY_DOWN) {
return;
}
// If the position of the key being pressed is the hold-tap key, something went wrong.
if (active_hold_tap->position == decision_trigger_event->other_key_position) {
LOG_DBG("ERROR hold-tap key cannot be pressed while still active");
return;
}
// All checks passed. Store the position of the other key in the active_hold_tap.
active_hold_tap->position_of_first_other_key_pressed =
decision_trigger_event->other_key_position;
return;
}
static void decide_balanced(struct active_hold_tap *hold_tap,
struct decision_trigger_event *decision_trigger_event) {
switch (decision_trigger_event->decision_moment) {
case HT_KEY_UP:
hold_tap->status = STATUS_TAP;
return;
case HT_OTHER_KEY_UP:
if (are_any_hold_enabler_keys_pressed(hold_tap->config)) {
if (does_position_of_first_other_keypress_permit_hold_behavior(hold_tap)) {
hold_tap->status = STATUS_HOLD_INTERRUPT;
} else {
hold_tap->status = STATUS_TAP;
}
return;
case HT_TIMER_EVENT:
if (are_any_hold_enabler_keys_pressed(hold_tap->config)) {
if (does_position_of_first_other_keypress_permit_hold_behavior(hold_tap)) {
hold_tap->status = STATUS_HOLD_TIMER;
} else {
hold_tap->status = STATUS_TAP;
@ -262,13 +300,14 @@ static void decide_balanced(struct active_hold_tap *hold_tap, enum decision_mome
}
}
static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision_moment event) {
switch (event) {
static void decide_tap_preferred(struct active_hold_tap *hold_tap,
struct decision_trigger_event *decision_trigger_event) {
switch (decision_trigger_event->decision_moment) {
case HT_KEY_UP:
hold_tap->status = STATUS_TAP;
return;
case HT_TIMER_EVENT:
if (are_any_hold_enabler_keys_pressed(hold_tap->config)) {
if (does_position_of_first_other_keypress_permit_hold_behavior(hold_tap)) {
hold_tap->status = STATUS_HOLD_TIMER;
} else {
hold_tap->status = STATUS_TAP;
@ -282,20 +321,21 @@ static void decide_tap_preferred(struct active_hold_tap *hold_tap, enum decision
}
}
static void decide_hold_preferred(struct active_hold_tap *hold_tap, enum decision_moment event) {
switch (event) {
static void decide_hold_preferred(struct active_hold_tap *hold_tap,
struct decision_trigger_event *decision_trigger_event) {
switch (decision_trigger_event->decision_moment) {
case HT_KEY_UP:
hold_tap->status = STATUS_TAP;
return;
case HT_OTHER_KEY_DOWN:
if (are_any_hold_enabler_keys_pressed(hold_tap->config)) {
if (does_position_of_first_other_keypress_permit_hold_behavior(hold_tap)) {
hold_tap->status = STATUS_HOLD_INTERRUPT;
} else {
hold_tap->status = STATUS_TAP;
}
return;
case HT_TIMER_EVENT:
if (are_any_hold_enabler_keys_pressed(hold_tap->config)) {
if (does_position_of_first_other_keypress_permit_hold_behavior(hold_tap)) {
hold_tap->status = STATUS_HOLD_TIMER;
} else {
hold_tap->status = STATUS_TAP;
@ -398,7 +438,7 @@ static int release_binding(struct active_hold_tap *hold_tap) {
}
static void decide_hold_tap(struct active_hold_tap *hold_tap,
enum decision_moment decision_moment) {
struct decision_trigger_event *decision_trigger_event) {
if (hold_tap->status != STATUS_UNDECIDED) {
return;
}
@ -408,13 +448,14 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap,
return;
}
store_position_if_is_first_other_key_pressed(hold_tap, decision_trigger_event);
switch (hold_tap->config->flavor) {
case FLAVOR_HOLD_PREFERRED:
decide_hold_preferred(hold_tap, decision_moment);
decide_hold_preferred(hold_tap, decision_trigger_event);
case FLAVOR_BALANCED:
decide_balanced(hold_tap, decision_moment);
decide_balanced(hold_tap, decision_trigger_event);
case FLAVOR_TAP_PREFERRED:
decide_tap_preferred(hold_tap, decision_moment);
decide_tap_preferred(hold_tap, decision_trigger_event);
}
if (hold_tap->status == STATUS_UNDECIDED) {
@ -423,7 +464,7 @@ static void decide_hold_tap(struct active_hold_tap *hold_tap,
LOG_DBG("%d decided %s (%s decision moment %s)", hold_tap->position,
status_str(hold_tap->status), flavor_str(hold_tap->config->flavor),
decision_moment_str(decision_moment));
decision_moment_str(decision_trigger_event->decision_moment));
undecided_hold_tap = NULL;
press_binding(hold_tap);
release_captured_events();
@ -480,18 +521,16 @@ static int on_hold_tap_binding_pressed(struct zmk_behavior_binding *binding,
LOG_DBG("%d new undecided hold_tap", event.position);
undecided_hold_tap = hold_tap;
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = HT_QUICK_TAP;
if (is_quick_tap(hold_tap)) {
decide_hold_tap(hold_tap, HT_QUICK_TAP);
decide_hold_tap(hold_tap, &decision_trigger_event);
}
// if this behavior was queued we have to adjust the timer to only
// wait for the remaining time.
int32_t tapping_term_ms_left = (hold_tap->timestamp + cfg->tapping_term_ms) - k_uptime_get();
if (tapping_term_ms_left > 0) {
k_delayed_work_submit(&hold_tap->work, K_MSEC(tapping_term_ms_left));
} else {
decide_hold_tap(hold_tap, HT_TIMER_EVENT);
}
return ZMK_BEHAVIOR_OPAQUE;
}
@ -508,10 +547,14 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding,
// We insert a timer event before the TH_KEY_UP event to verify.
int work_cancel_result = k_delayed_work_cancel(&hold_tap->work);
if (event.timestamp > (hold_tap->timestamp + hold_tap->config->tapping_term_ms)) {
decide_hold_tap(hold_tap, HT_TIMER_EVENT);
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = HT_TIMER_EVENT;
decide_hold_tap(hold_tap, &decision_trigger_event);
}
decide_hold_tap(hold_tap, HT_KEY_UP);
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = HT_KEY_UP;
decide_hold_tap(hold_tap, &decision_trigger_event);
decide_retro_tap(hold_tap);
release_binding(hold_tap);
@ -561,7 +604,9 @@ static int position_state_changed_listener(const zmk_event_t *eh) {
// have run out.
if (ev->timestamp >
(undecided_hold_tap->timestamp + undecided_hold_tap->config->tapping_term_ms)) {
decide_hold_tap(undecided_hold_tap, HT_TIMER_EVENT);
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = HT_TIMER_EVENT;
decide_hold_tap(undecided_hold_tap, &decision_trigger_event);
}
if (!ev->state && find_captured_keydown_event(ev->position) == NULL) {
@ -575,7 +620,10 @@ static int position_state_changed_listener(const zmk_event_t *eh) {
LOG_DBG("%d capturing %d %s event", undecided_hold_tap->position, ev->position,
ev->state ? "down" : "up");
capture_event(eh);
decide_hold_tap(undecided_hold_tap, ev->state ? HT_OTHER_KEY_DOWN : HT_OTHER_KEY_UP);
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = ev->state ? HT_OTHER_KEY_DOWN : HT_OTHER_KEY_UP;
decision_trigger_event.other_key_position = ev->position;
decide_hold_tap(undecided_hold_tap, &decision_trigger_event);
return ZMK_EV_EVENT_CAPTURED;
}
@ -621,7 +669,9 @@ void behavior_hold_tap_timer_work_handler(struct k_work *item) {
if (hold_tap->work_is_cancelled) {
clear_hold_tap(hold_tap);
} else {
decide_hold_tap(hold_tap, HT_TIMER_EVENT);
struct decision_trigger_event decision_trigger_event;
decision_trigger_event.decision_moment = HT_TIMER_EVENT;
decide_hold_tap(hold_tap, &decision_trigger_event);
}
}
@ -652,7 +702,7 @@ static struct behavior_hold_tap_data behavior_hold_tap_data;
.hold_enabler_keys = DT_INST_PROP(n, hold_enabler_keys), \
.hold_enabler_keys_len = DT_INST_PROP_LEN(n, hold_enabler_keys), \
}; \
DEVICE_AND_API_INIT(behavior_hold_tap_##n, DT_INST_LABEL(n), behavior_hold_tap_init, \
DEVICE_DT_INST_DEFINE(n, behavior_hold_tap_init, device_pm_control_nop, \
&behavior_hold_tap_data, &behavior_hold_tap_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_hold_tap_driver_api);

View file

@ -36,8 +36,8 @@ static const struct behavior_driver_api behavior_key_press_driver_api = {
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
#define KP_INST(n) \
DEVICE_AND_API_INIT(behavior_key_press_##n, DT_INST_LABEL(n), behavior_key_press_init, NULL, \
NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
DEVICE_DT_INST_DEFINE(n, behavior_key_press_init, device_pm_control_nop, NULL, NULL, \
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_key_press_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)

View file

@ -90,9 +90,10 @@ static int behavior_mod_morph_init(const struct device *dev) { return 0; }
.mods = DT_INST_PROP(n, mods), \
}; \
static struct behavior_mod_morph_data behavior_mod_morph_data_##n = {}; \
DEVICE_AND_API_INIT(behavior_mod_morph_##n, DT_INST_LABEL(n), behavior_mod_morph_init, \
&behavior_mod_morph_data_##n, &behavior_mod_morph_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mod_morph_driver_api);
DEVICE_DT_INST_DEFINE(n, behavior_mod_morph_init, device_pm_control_nop, \
&behavior_mod_morph_data_##n, &behavior_mod_morph_config_##n, \
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_mod_morph_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)

View file

@ -39,6 +39,6 @@ static const struct behavior_mo_config behavior_mo_config = {};
static struct behavior_mo_data behavior_mo_data;
DEVICE_AND_API_INIT(behavior_mo, DT_INST_LABEL(0), behavior_mo_init, &behavior_mo_data,
DEVICE_DT_INST_DEFINE(0, behavior_mo_init, device_pm_control_nop, &behavior_mo_data,
&behavior_mo_config, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_mo_driver_api);

View file

@ -34,7 +34,7 @@ static const struct behavior_driver_api behavior_none_driver_api = {
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_none, DT_INST_LABEL(0), behavior_none_init, NULL, NULL, APPLICATION,
DEVICE_DT_INST_DEFINE(0, behavior_none_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_none_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -42,7 +42,7 @@ static const struct behavior_driver_api behavior_outputs_driver_api = {
.binding_pressed = on_keymap_binding_pressed,
};
DEVICE_AND_API_INIT(behavior_out, DT_INST_LABEL(0), behavior_out_init, NULL, NULL, APPLICATION,
DEVICE_DT_INST_DEFINE(0, behavior_out_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_outputs_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -41,7 +41,7 @@ static const struct behavior_driver_api behavior_reset_driver_api = {
#define RST_INST(n) \
static const struct behavior_reset_config behavior_reset_config_##n = { \
.type = DT_INST_PROP(n, type)}; \
DEVICE_AND_API_INIT(behavior_reset_##n, DT_INST_LABEL(n), behavior_reset_init, NULL, \
DEVICE_DT_INST_DEFINE(n, behavior_reset_init, device_pm_control_nop, NULL, \
&behavior_reset_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_reset_driver_api);

View file

@ -136,8 +136,8 @@ static const struct behavior_driver_api behavior_rgb_underglow_driver_api = {
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_rgb_underglow, DT_INST_LABEL(0), behavior_rgb_underglow_init, NULL,
NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
DEVICE_DT_INST_DEFINE(0, behavior_rgb_underglow_init, device_pm_control_nop, NULL, NULL,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_rgb_underglow_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -59,9 +59,8 @@ static const struct behavior_driver_api behavior_sensor_rotate_key_press_driver_
.sensor_binding_triggered = on_sensor_binding_triggered};
#define KP_INST(n) \
DEVICE_AND_API_INIT(behavior_sensor_rotate_key_press_##n, DT_INST_LABEL(n), \
behavior_sensor_rotate_key_press_init, NULL, NULL, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
DEVICE_DT_INST_DEFINE(n, behavior_sensor_rotate_key_press_init, device_pm_control_nop, NULL, \
NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_sensor_rotate_key_press_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)

View file

@ -272,7 +272,7 @@ static struct behavior_sticky_key_data behavior_sticky_key_data;
.release_after_ms = DT_INST_PROP(n, release_after_ms), \
.quick_release = DT_INST_PROP(n, quick_release), \
}; \
DEVICE_AND_API_INIT(behavior_sticky_key_##n, DT_INST_LABEL(n), behavior_sticky_key_init, \
DEVICE_DT_INST_DEFINE(n, behavior_sticky_key_init, device_pm_control_nop, \
&behavior_sticky_key_data, &behavior_sticky_key_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_sticky_key_driver_api);

View file

@ -37,7 +37,7 @@ static const struct behavior_driver_api behavior_to_driver_api = {
.binding_released = to_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_to, DT_INST_LABEL(0), behavior_to_init, NULL, NULL, APPLICATION,
DEVICE_DT_INST_DEFINE(0, behavior_to_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_to_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -43,7 +43,7 @@ static const struct behavior_tog_config behavior_tog_config = {};
static struct behavior_tog_data behavior_tog_data;
DEVICE_AND_API_INIT(behavior_tog, DT_INST_LABEL(0), behavior_tog_init, &behavior_tog_data,
DEVICE_DT_INST_DEFINE(0, behavior_tog_init, device_pm_control_nop, &behavior_tog_data,
&behavior_tog_config, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_tog_driver_api);

View file

@ -34,8 +34,7 @@ static const struct behavior_driver_api behavior_transparent_driver_api = {
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_transparent, DT_INST_LABEL(0), behavior_transparent_init, NULL, NULL,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_transparent_driver_api);
DEVICE_DT_INST_DEFINE(0, behavior_transparent_init, device_pm_control_nop, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_transparent_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -26,14 +26,16 @@ static lv_obj_t *screen;
__attribute__((weak)) lv_obj_t *zmk_display_status_screen() { return NULL; }
void display_tick_cb(struct k_work *work) {
lv_tick_inc(10);
lv_task_handler();
}
void display_tick_cb(struct k_work *work) { lv_task_handler(); }
#define TICK_MS 10
K_WORK_DEFINE(display_tick_work, display_tick_cb);
void display_timer_cb() { k_work_submit(&display_tick_work); }
void display_timer_cb() {
lv_tick_inc(TICK_MS);
k_work_submit(&display_tick_work);
}
K_TIMER_DEFINE(display_timer, display_timer_cb, NULL);
@ -44,7 +46,7 @@ static void start_display_updates() {
display_blanking_off(display);
k_timer_start(&display_timer, K_MSEC(10), K_MSEC(10));
k_timer_start(&display_timer, K_MSEC(TICK_MS), K_MSEC(TICK_MS));
}
static void stop_display_updates() {
@ -75,8 +77,6 @@ int zmk_display_init() {
lv_scr_load(screen);
lv_task_handler();
start_display_updates();
LOG_DBG("");

View file

@ -7,6 +7,7 @@ config ZMK_WIDGET_LAYER_STATUS
bool "Widget for highest, active layer using small icons"
default y
depends on !ZMK_SPLIT || ZMK_SPLIT_BLE_ROLE_CENTRAL
select LVGL_USE_LABEL
select LVGL_FONT_MONTSERRAT_12
config ZMK_WIDGET_BATTERY_STATUS

View file

@ -13,6 +13,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/endpoint_selection_changed.h>
#include <zmk/usb.h>
#include <zmk/ble.h>
#include <zmk/endpoints.h>
@ -86,6 +87,8 @@ int output_status_listener(const zmk_event_t *eh) {
}
ZMK_LISTENER(widget_output_status, output_status_listener)
ZMK_SUBSCRIPTION(widget_output_status, zmk_endpoint_selection_changed);
#if defined(CONFIG_USB)
ZMK_SUBSCRIPTION(widget_output_status, zmk_usb_conn_state_changed);
#endif

View file

@ -16,6 +16,7 @@
#include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/events/endpoint_selection_changed.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -242,6 +243,9 @@ static void update_current_endpoint() {
current_endpoint = new_endpoint;
LOG_INF("Endpoint changed: %d", current_endpoint);
ZMK_EVENT_RAISE(new_zmk_endpoint_selection_changed(
(struct zmk_endpoint_selection_changed){.endpoint = current_endpoint}));
}
}

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <kernel.h>
#include <zmk/events/endpoint_selection_changed.h>
ZMK_EVENT_IMPL(zmk_endpoint_selection_changed);

View file

@ -32,7 +32,7 @@ struct ext_power_generic_data {
#if IS_ENABLED(CONFIG_SETTINGS)
bool settings_init;
#endif
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#ifdef CONFIG_PM_DEVICE
uint32_t pm_state;
#endif
};
@ -143,7 +143,7 @@ static int ext_power_generic_init(const struct device *dev) {
return -EIO;
}
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#ifdef CONFIG_PM_DEVICE
data->pm_state = DEVICE_PM_ACTIVE_STATE;
#endif
@ -179,7 +179,7 @@ static int ext_power_generic_init(const struct device *dev) {
return 0;
}
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#ifdef CONFIG_PM_DEVICE
static int ext_power_generic_pm_control(const struct device *dev, uint32_t ctrl_command,
void *context, device_pm_cb cb, void *arg) {
int rc;
@ -210,7 +210,7 @@ static int ext_power_generic_pm_control(const struct device *dev, uint32_t ctrl_
return rc;
}
#endif /* CONFIG_DEVICE_POWER_MANAGEMENT */
#endif /* CONFIG_PM_DEVICE */
static const struct ext_power_generic_config config = {
.label = DT_INST_GPIO_LABEL(0, control_gpios),
@ -231,13 +231,7 @@ static const struct ext_power_api api = {.enable = ext_power_generic_enable,
#define ZMK_EXT_POWER_INIT_PRIORITY 81
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
DEVICE_DEFINE(ext_power_generic, DT_INST_LABEL(0), ext_power_generic_init,
&ext_power_generic_pm_control, &data, &config, POST_KERNEL,
ZMK_EXT_POWER_INIT_PRIORITY, &api);
#else
DEVICE_AND_API_INIT(ext_power_generic, DT_INST_LABEL(0), ext_power_generic_init, &data, &config,
DEVICE_DT_INST_DEFINE(0, ext_power_generic_init, &ext_power_generic_pm_control, &data, &config,
POST_KERNEL, ZMK_EXT_POWER_INIT_PRIORITY, &api);
#endif /* CONFIG_DEVICE_POWER_MANAGEMENT */
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View file

@ -23,18 +23,12 @@ bool is_usb_power_present() {
#endif /* CONFIG_USB */
}
enum power_states sys_pm_policy_next_state(int32_t ticks) {
#ifdef CONFIG_SYS_POWER_DEEP_SLEEP_STATES
#ifdef CONFIG_HAS_SYS_POWER_STATE_DEEP_SLEEP_1
struct pm_state_info pm_policy_next_state(int32_t ticks) {
if (zmk_activity_get_state() == ZMK_ACTIVITY_SLEEP && !is_usb_power_present()) {
return SYS_POWER_STATE_DEEP_SLEEP_1;
}
#endif /* CONFIG_HAS_SYS_POWER_STATE_DEEP_SLEEP_1 */
#endif /* CONFIG_SYS_POWER_DEEP_SLEEP_STATES */
return SYS_POWER_STATE_ACTIVE;
return (struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0};
}
bool sys_pm_policy_low_power_devices(enum power_states pm_state) {
return sys_pm_is_sleep_state(pm_state);
return (struct pm_state_info){PM_STATE_ACTIVE, 0, 0};
}
__weak bool pm_policy_low_power_devices(enum pm_state state) { return pm_is_sleep_state(state); }

View file

@ -19,6 +19,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static enum usb_dc_status_code usb_status = USB_DC_UNKNOWN;
static void raise_usb_status_changed_event(struct k_work *_work) {
ZMK_EVENT_RAISE(new_zmk_usb_conn_state_changed(
(struct zmk_usb_conn_state_changed){.conn_state = zmk_usb_get_conn_state()}));
}
K_WORK_DEFINE(usb_status_notifier_work, raise_usb_status_changed_event);
#ifdef CONFIG_ZMK_USB
static const struct device *hid_dev;
@ -54,14 +61,10 @@ int zmk_usb_hid_send_report(const uint8_t *report, size_t len) {
#endif /* CONFIG_ZMK_USB */
static void raise_usb_status_changed_event() {
ZMK_EVENT_RAISE(new_zmk_usb_conn_state_changed(
(struct zmk_usb_conn_state_changed){.conn_state = zmk_usb_get_conn_state()}));
}
enum usb_dc_status_code zmk_usb_get_status() { return usb_status; }
enum zmk_usb_conn_state zmk_usb_get_conn_state() {
LOG_DBG("state: %d", usb_status);
switch (usb_status) {
case USB_DC_DISCONNECTED:
case USB_DC_UNKNOWN:
@ -78,7 +81,7 @@ enum zmk_usb_conn_state zmk_usb_get_conn_state() {
void usb_status_cb(enum usb_dc_status_code status, const uint8_t *params) {
usb_status = status;
raise_usb_status_changed_event();
k_work_submit(&usb_status_notifier_work);
};
static int zmk_usb_init(const struct device *_arg) {

View file

@ -0,0 +1,30 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
behaviors {
cht_h: conditional_hold_tap_hold_preferred {
compatible = "zmk,behavior-hold-tap";
label = "CONDITIONAL_HOLD_TAP";
#binding-cells = <2>;
flavor = "hold-preferred";
tapping-term-ms = <1000>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
hold-enabler-keys = <1>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&cht_h LEFT_SHIFT F &kp D
&kp G
>;
};
};
};

View file

@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p

View file

@ -0,0 +1,7 @@
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 0 new undecided hold_tap
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
ht_decide: 0 decided tap (hold-preferred decision moment key-up)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap

View file

@ -0,0 +1,14 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,1,100) /* enabled other key pressed */
ZMK_MOCK_PRESS(0,0,100) /* cht pressed */
ZMK_MOCK_RELEASE(0,1,100) /* enabled other key released */
ZMK_MOCK_RELEASE(0,0,10) /* cht released */
/* timer fires */
>;
};

View file

@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p

View file

@ -0,0 +1,7 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap

View file

@ -0,0 +1,14 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,100) /* cht pressed */
ZMK_MOCK_PRESS(0,1,100) /* enabled other key pressed */
ZMK_MOCK_RELEASE(0,1,100) /* enabled other key released */
ZMK_MOCK_RELEASE(0,0,10) /* cht released */
/* timer fires */
>;
};

View file

@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p

View file

@ -0,0 +1,9 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided tap (hold-preferred decision moment other-key-down)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x0a implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0a implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap

View file

@ -0,0 +1,16 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,100) /* cht pressed */
ZMK_MOCK_PRESS(1,0,100) /* disabled other key pressed */
ZMK_MOCK_RELEASE(1,0,100) /* disabled other key released */
ZMK_MOCK_PRESS(0,1,100) /* enabled other key pressed */
ZMK_MOCK_RELEASE(0,1,100) /* enabled other key released */
ZMK_MOCK_RELEASE(0,0,10) /* cht released */
/* timer fires */
>;
};

View file

@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*on_hold_tap_binding/ht_binding/p
s/.*decide_hold_tap/ht_decide/p

View file

@ -0,0 +1,10 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (tap-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 1 new undecided hold_tap
ht_decide: 1 decided tap (tap-preferred decision moment key-up)
kp_pressed: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 1 cleaning up hold-tap
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap

View file

@ -0,0 +1,53 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/*
* A hold-tap with long tapping term is pressed first.
* A hold-tap with short tapping term is quickly tapped.
* The short tapping term hold-tap should 'tap', not 'hold'.
*/
/ {
behaviors {
tp_short: short_tap {
compatible = "zmk,behavior-hold-tap";
label = "MOD_TAP_SHORT";
#binding-cells = <2>;
flavor = "tap-preferred";
tapping-term-ms = <100>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
};
tp_long: long_tap {
compatible = "zmk,behavior-hold-tap";
label = "MOD_TAP_LONG";
#binding-cells = <2>;
flavor = "tap-preferred";
tapping-term-ms = <200>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&tp_long LEFT_SHIFT F &tp_short LEFT_CONTROL J
&kp D &kp RIGHT_CONTROL>;
};
};
};
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,20)
ZMK_MOCK_PRESS(0,1,20)
ZMK_MOCK_RELEASE(0,1,200)
ZMK_MOCK_RELEASE(0,0,10)
>;
};

View file

@ -9,7 +9,7 @@ manifest:
projects:
- name: zephyr
remote: zmkfirmware
revision: v2.4.0+zmk-fixes
revision: v2.5.0+zmk-fixes
clone-depth: 1
import:
# TODO: Rename once upstream offers option like `exclude` or `denylist`

View file

@ -0,0 +1,75 @@
---
title: "Zephyr 2.5 Update"
author: Pete Johanson
author_title: Project Creator
author_url: https://gitlab.com/petejohanson
author_image_url: https://www.gravatar.com/avatar/2001ceff7e9dc753cf96fcb2e6f41110
tags: [firmware, zephyr, core]
---
I'm happy to announce that we have completed the [work](https://github.com/zmkfirmware/zmk/pull/736/) to upgrade ZMK to [Zephyr 2.5](https://docs.zephyrproject.org/2.5.0/releases/release-notes-2.5.html)!
A big part of this work was some _major_ refactors and improvements by [innovaker] to our [zmk-docker](https://github.com/zmkfirmware/zmk-docker/) Docker image and GH Actions automation.
- Faster build times with improved caching.
- Integration tests which automatically verify new images.
- PRs to the repo now build properly and run the tests as well.
- Build images for multiple target architectures, e.g. `zmk-build-riscv64`, all in parallel.
- Nightly builds to be sure we're pulling in the latest OS/package updates, to ensure we keep our images up to date, address any reported vulnerabilities, etc.
- Faster upgrade paths for future Zephyr SDK and Zephyr versions.
In addition, [petejohanson] did the upgrade work to adjust ZMK for the Zephyr changes.
- Updated to newer devicetree/driver Zephyr API
- Adjustment for Zephyr pinmux changes
- Fixes for power management, LVGL, and formatter changes
## Getting The Changes
Use the following steps to update to the latest tooling in order to properly use the new ZMK changes:
### User Config Repositories Using GitHub Actions
Existing user config repositories using Github Actions to build will pull down Zephyr 2.5 automatically,
and should work, fine as is. However, to upgrade to the newer Docker image, you should:
- Open `.github/workflows/build.yml` in your editor/IDE
- Change `zmkfirmware/zmk-build-arm:2.4` to `zmkfirmware/zmk-build-arm:2.5` wherever it is found
:::note
If you created your user config repository a while ago, you may find that your `build.yml` file instead references
a `zephyr-west-action-arm` custom GitHub Action instead. In this case, the upgrade is not as direct. We suggest that
instead you [re-create your config repository](/docs/user-setup) to get an updated setup using the new automation
approach.
:::
### VS Code & Docker (Dev Container)
If you build locally using VS Code & Docker then:
- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- reload the project
- if you are prompted to rebuild the remote container, click `Rebuild`
- otherwise, press `F1` and run `Remote Containers: Rebuild Container`
- Once the container has rebuilt and reloaded, run `west update` to pull the updated Zephyr version and its dependencies.
Once the container has rebuilt, VS Code will be running the 2.5 Docker image.
### Local Host Development
The following steps will get you building ZMK locally against Zephyr 2.5:
- Run the updated [toolchain installation](/docs/development/setup#toolchain-installation) steps, and once completed, remove the previously installed SDK version (optional, existing SDK should still work)
- pull the latest ZMK `main` with `git pull` for your ZMK checkout
- run `west update` to pull the updated Zephyr version and its dependencies
From there, you should be ready to build as normal!
## Thanks!
Thanks again to [innovaker] for all the hard work, and to all the testers who have helped verify ZMK functionality on the newer Zephyr version.
[petejohanson]: https://github.com/petejohanson
[innovaker]: https://github.com/innovaker

View file

@ -84,5 +84,5 @@ ZMK support bluetooth “profiles” which allows connection to multiple devices
The bluetooth MAC address and negotiated keys during pairing are stored in the permanent storage on your chip and can be reused even after reflashing the firmware. If for some reason you want to delete the stored information, you can bind the `BT_CLR` behavior described above to a key and use it to clear the _current_ profile.
:::note
If you clear bond of a paired profile, make sure you do the same thing on the peer device as well (typically achieved by _removing_ or _forgetting_ the bluetooth connection). Otherwise the peer will try to connect to your keyboard whenever it discovers it. But while the MAC address of both devices could remain the same, the security key no longer match: the peer device still possess the old key negotiated in the previous pairing procedure, but our keyboard firmware has deleted that key. So the connection will fail. If you [enabled USB logging](../development/usb-logging), you might see a lot of failed connection attempts due to the reason of “Security failed”.
If you clear bond of a paired profile, make sure you do the same thing on the peer device as well (typically achieved by _removing_ or _forgetting_ the bluetooth connection). Otherwise the peer will try to connect to your keyboard whenever it discovers it. But while the MAC address of both devices could remain the same, the security key no longer match: the peer device still possess the old key negotiated in the previous pairing procedure, but our keyboard firmware has deleted that key. So the connection will fail. If you [enabled USB logging](../development/usb-logging.md), you might see a lot of failed connection attempts due to the reason of “Security failed”.
:::

View file

@ -33,7 +33,7 @@ When the hold-tap key is released and the hold behavior has not been triggered,
### Basic usage
For basic usage, please see [mod-tap](./mod-tap.md) and [layer-tap](./layers.md) pages.
For basic usage, please see [mod-tap](mod-tap.md) and [layer-tap](layers.md) pages.
### Advanced Configuration
@ -95,6 +95,46 @@ This example configures a hold-tap that works well for homerow mods:
If this config does not work for you, try the flavor "balanced" with a medium `tapping-term-ms` such as 200ms.
#### Conditional hold-tap and `hold-enabler-keys`
Including `hold-enabler-keys` in your hold-tap behavior definition turns on conditional hold-tap. This causes the hold-tap behavior to only be allowed to produce a hold behavior if the next key pressed is one of the `hold-enabler-keys`. For example, with the following configuration:
```
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
/ {
behaviors {
cht: conditional_hold_tap {
compatible = "zmk,behavior-hold-tap";
label = "CONDITIONAL_HOLD_TAP";
#binding-cells = <2>;
flavor = "hold-preferred";
tapping-term-ms = <400>;
quick-tap-ms = <200>;
bindings = <&kp>, <&kp>;
hold-enabler-keys = <1>; // <---[[the W key]]
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&cht LEFT_SHIFT Q &kp W &kp E
>;
};
};
};
```
The sequence `(cht_down, W_down, W_up, E_down, E_up, cht_up)` produces `WE`, because the conditional hold-tap **IS** permitted to produce a hold behavior, because the next key pressed (the W key in position 1) **IS** one of the hold-enabler-keys.
Meanwhile, the sequence `(cht_down, E_down, E_up, W_down, W_up cht_up)` produces `qew`, because the conditional hold-tap is **NOT** permitted to produce a hold behavior, because the next key pressed (the E key in position 2) is **NOT** one of the hold-enabler-keys.
Conditional hold-taps can be useful with home-row modifiers for example. By setting `hold-enabler-keys` to include only the keys controlled by the opposite hand, conditional hold-taps can prevent one-handed "rolls" from accidentally triggering hold behaviors.
#### Comparison to QMK
The hold-preferred flavor works similar to the `HOLD_ON_OTHER_KEY_PRESS` setting in QMK. The 'balanced' flavor is similar to the `PERMISSIVE_HOLD` setting, and the `tap-preferred` flavor is similar to `IGNORE_MOD_TAP_INTERRUPT`.

View file

@ -10,14 +10,14 @@ a certain key.
The categories of supported codes are:
- [Keyboard & Keypad](../codes/keyboard-keypad)
- [Editing](../codes/editing)
- [Media](../codes/media)
- [Applications](../codes/applications)
- [Input Assist](../codes/input-assist)
- [Power](../codes/power)
- [Keyboard & Keypad](../codes/keyboard-keypad.mdx)
- [Editing](../codes/editing.mdx)
- [Media](../codes/media.mdx)
- [Applications](../codes/applications.mdx)
- [Input Assist](../codes/input-assist.mdx)
- [Power](../codes/power.mdx)
Please visit the [codes](../codes) section for a comprehensive list.
Please visit the [codes](../codes/index.mdx) section for a comprehensive list.
For advanced users, user-defined HID usages are also supported but must be encoded, please see [`dt-bindings/zmk/keys.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/keys.h) for further insight.
@ -36,7 +36,7 @@ Doing so makes a set of defines such as `A`, `N1`, etc. available for use with t
### Improperly defined keymap - `dtlib.DTError: <board>.dts.pre.tmp:<line number>`
When compiling firmware from a keymap, it may be common to encounter an error in the form of a`dtlib.DTError: <board>.dts.pre.tmp:<line number>`.
For instructions to resolve such an error, click [here](../troubleshooting###Improperly-defined-keymap)
For instructions to resolve such an error, click [here](../troubleshooting.md###Improperly-defined-keymap)
## Key Press

View file

@ -43,7 +43,7 @@ Example:
## Layer-tap
The "layer-tap" behavior enables a layer when a key is held, and output another key when the key is only tapped for a short time. For more information on the inner workings of layer-tap, see [hold-tap](./hold-tap.md).
The "layer-tap" behavior enables a layer when a key is held, and output another key when the key is only tapped for a short time. For more information on the inner workings of layer-tap, see [hold-tap](hold-tap.md).
### Behavior Binding

View file

@ -46,4 +46,4 @@ You can configure a different tapping term in your keymap:
### Additional information
The mod-tap is a [hold-tap](./hold-tap.md) under the hood with the "balanced" flavor and tapping-term-ms 200.
The mod-tap is a [hold-tap](hold-tap.md) under the hood with the "balanced" flavor and tapping-term-ms 200.

View file

@ -24,7 +24,7 @@ import Table from "@site/src/components/codes/Table";
### Modifiers
The [Modifiers](./modifiers) page includes further information.
The [Modifiers](modifiers.mdx) page includes further information.
<Table group="keyboard-modifiers" />

View file

@ -4,7 +4,7 @@ sidebar_label: Customizing ZMK
---
After verifying you can successfully flash the default firmware, you will probably want to begin customizing your keymap and other keyboard options.
[In the initial setup tutorial](user-setup), you created a Github repository called `zmk-config`. This repository is a discrete filesystem which works
[In the initial setup tutorial](user-setup.md), you created a Github repository called `zmk-config`. This repository is a discrete filesystem which works
with the main `zmk` firmware repository to build your desired firmware. The main advantage of a discrete configuration folder is ensuring that the
working components of ZMK are kept separate from your personal keyboard settings, reducing the amount of file manipulation in the configuration process.
This makes flashing ZMK to your keyboard much easier, especially because you don't need to keep an up-to-date copy of zmk on your computer at all times.
@ -26,7 +26,7 @@ various config settings that can be commented/uncommented to modify how your fir
## Keymap
Once you have the basic user config completed, you can find the keymap file in `config/<shield>.keymap` and customize from there.
Refer to the [Keymap](/docs/features/keymaps) documentation to learn more.
Refer to the [Keymap](features/keymaps.md) documentation to learn more.
## Publishing
@ -39,7 +39,7 @@ If you need to, a review of [Learn The Basics Of Git In Under 10 Minutes](https:
## Building from a local `zmk` fork using `zmk-config`
[As outlined here](development/build-flash), firmware comes in the form of .uf2 files, which can be built locally using the command `west build`. Normally,
[As outlined here](development/build-flash.md), firmware comes in the form of .uf2 files, which can be built locally using the command `west build`. Normally,
`west build` will default to using the in-tree .keymap and .conf files found in your local copy of the `zmk` repository. However, you can append the command, `-DZMK_CONFIG="C:/the/absolute/path/config"` to `west build` in order to use the contents of your `zmk-config` folder instead of the
default keyboard settings.
**Notice that this path should point to the folder labelled `config` within your `zmk-config` folder.**
@ -55,4 +55,4 @@ west build -b nice_nano -- -DSHIELD=kyria_left -DZMK_CONFIG="C:/Users/myUser/Doc
For normal keyboards, follow the same flashing instructions as before to flash your updated firmware.
For split keyboards, only the central (left) side will need to be reflashed if you are just updating your keymap.
More troubleshooting information for split keyboards can be found [here](troubleshooting#split-keyboard-halves-unable-to-pair).
More troubleshooting information for split keyboards can be found [here](troubleshooting.md#split-keyboard-halves-unable-to-pair).

View file

@ -6,12 +6,12 @@ title: Boards, Shields, and Keymaps
The foundational elements needed to get a specific keyboard working with ZMK can be broken down into:
- A [KSCAN driver](https://docs.zephyrproject.org/2.3.0/reference/peripherals/kscan.html), which uses `compatible="zmk,kscan-gpio-matrix"` for GPIO matrix based keyboards, or uses `compatible="zmk,kscan-gpio-direct"` for small direct wires.
- A [KSCAN driver](https://docs.zephyrproject.org/2.5.0/reference/peripherals/kscan.html), which uses `compatible="zmk,kscan-gpio-matrix"` for GPIO matrix based keyboards, or uses `compatible="zmk,kscan-gpio-direct"` for small direct wires.
- An optional matrix transform, which defines how the KSCAN row/column events are translated into logical "key positions". This is required for non-rectangular keyboards/matrices, where the key positions don't naturally follow the row/columns from the GPIO matrix.
- A keymap, which binds each key position to a behavior, e.g. key press, mod-tap, momentary layer, in a set of layers.
These three core architectural elements are defined per-keyboard, and _where_ they are defined depends on the specifics of how that
keyboard works. For an overview on the general concepts of boards and shields, please see the [FAQs on boards and shields](/docs/faq#why-boards-and-shields--why-not-just-keyboard).
keyboard works. For an overview on the general concepts of boards and shields, please see the [FAQs on boards and shields](../faq.md#why-boards-and-shields--why-not-just-keyboard).
## Self-Contained Keyboard
@ -27,8 +27,8 @@ in the `app/boards/${arch}/${board_name}` directory, e.g. `app/boards/arm/planck
- A `${board_name}_defconfig` file that forces specific Kconfig settings that are specific to this hardware configuration. Mostly this is SoC settings around the specific hardware configuration.
- `${board_name}.dts` which contains all the devicetree definitions, including:
- An `#include` line that pulls in the specific microprocessor that is used, e.g. `#include <st/f3/stm32f303Xc.dtsi>`.
- A [chosen](https://docs.zephyrproject.org/2.3.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,kscan` which references the configured KSCAN driver (usually a GPIO matrix)
- (Optional) A [chosen](https://docs.zephyrproject.org/2.3.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,matrix_transform` that defines the mapping from KSCAN row/column values to the logical key position for the keyboard.
- A [chosen](https://docs.zephyrproject.org/2.5.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,kscan` which references the configured KSCAN driver (usually a GPIO matrix)
- (Optional) A [chosen](https://docs.zephyrproject.org/2.5.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,matrix_transform` that defines the mapping from KSCAN row/column values to the logical key position for the keyboard.
- A `board.cmake` file with CMake directives for how to flash to the device.
- A `keymap/keymap.overlay` file that includes the default keymap for that keyboard. Users will be able to override this keymap in their user configs.
@ -47,6 +47,6 @@ in the `app/boards/shields/${board_name}` directory, e.g. `app/boards/shields/cl
- A `Kconfig.shield` that defines the toplevel Kconfig value for the shield, which uses a supplied utility to function to default the value based on the shield list, e.g. `def_bool $(shields_list_contains,clueboard_california)`.
- A `Kconfig.defconfig` file to set default values for things like `ZMK_KEYBOARD_NAME`
- A `${shield_name}.overlay` file, which is a devicetree overlay file, that includes:
- A [chosen](https://docs.zephyrproject.org/2.3.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,kscan` which references the configured KSCAN driver (usually a GPIO matrix). For these keyboards, to be compatible with any Pro Micro compatible boards, the KSCAN configuration should reference the [nexus node](https://docs.zephyrproject.org/2.3.0/guides/porting/shields.html#gpio-nexus-nodes) that ZMK has standardized on. In particular, the `&pro_micro_a` and `&pro_micro_d` aliases can be used to reference the standard `A#` and `D#` pins in shields.
- (Optional) A [chosen](https://docs.zephyrproject.org/2.3.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,matrix_transform` that defines the mapping from KSCAN row/column values to the logical key position for the keyboard.
- A [chosen](https://docs.zephyrproject.org/2.5.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,kscan` which references the configured KSCAN driver (usually a GPIO matrix). For these keyboards, to be compatible with any Pro Micro compatible boards, the KSCAN configuration should reference the [nexus node](https://docs.zephyrproject.org/2.5.0/guides/porting/shields.html#gpio-nexus-nodes) that ZMK has standardized on. In particular, the `&pro_micro_a` and `&pro_micro_d` aliases can be used to reference the standard `A#` and `D#` pins in shields.
- (Optional) A [chosen](https://docs.zephyrproject.org/2.5.0/guides/dts/intro.html#aliases-and-chosen-nodes) node named `zmk,matrix_transform` that defines the mapping from KSCAN row/column values to the logical key position for the keyboard.
- A `keymap/keymap.overlay` file that includes the default keymap for that keyboard. Users will be able to override this keymap in their user configs.

View file

@ -31,7 +31,7 @@ an onboard MCU, or one that uses an MCU board addon.
### Keyboard (Shield) + MCU Board
ZMK treats keyboards that take an MCU addon board as [shields](https://docs.zephyrproject.org/2.3.0/guides/porting/shields.html), and treats the smaller MCU board as the true [board](https://docs.zephyrproject.org/2.3.0/guides/porting/board_porting.html)
ZMK treats keyboards that take an MCU addon board as [shields](https://docs.zephyrproject.org/2.5.0/guides/porting/shields.html), and treats the smaller MCU board as the true [board](https://docs.zephyrproject.org/2.5.0/guides/porting/board_porting.html)
Given the following:
@ -47,7 +47,7 @@ west build -b proton_c -- -DSHIELD=kyria_left
### Keyboard With Onboard MCU
Keyboards with onboard MCU chips are simply treated as the [board](https://docs.zephyrproject.org/2.3.0/guides/porting/board_porting.html) as far as Zephyr™ is concerned.
Keyboards with onboard MCU chips are simply treated as the [board](https://docs.zephyrproject.org/2.5.0/guides/porting/board_porting.html) as far as Zephyr™ is concerned.
Given the following:
@ -90,7 +90,7 @@ This produces `left` and `right` subfolders under the `build` directory and two
### Building from `zmk-config` Folder
Instead of building .uf2 files using the default keymap and config files, you can build directly from your [`zmk-config` folder](../user-setup#github-repo) by adding
Instead of building .uf2 files using the default keymap and config files, you can build directly from your [`zmk-config` folder](../user-setup.md#github-repo) by adding
`-DZMK_CONFIG="C:/the/absolute/path/config"` to your `west build` command. **Notice that this path should point to the folder labelled `config` within your `zmk-config` folder.**
For instance, building kyria firmware from a user `myUser`'s `zmk-config` folder on Windows 10 may look something like this:
@ -107,7 +107,7 @@ volume automatically -- we need to delete the default volume before binding it t
1. Remove all the containers that are not running via the command `docker container prune`. We need to remove the ZMK container before we can delete the default `zmk-config` volume referenced by it. If you do not want to delete all the containers that are not running, you can find the id of the ZMK container and use `docker rm` to delete that one only.
1. Remove the default volume via the command `docker volume rm zmk-config`.
Then you can bind the `zmk-config` volume to the correct path pointing to your local [zmk-config](./customization.md) folder:
Then you can bind the `zmk-config` volume to the correct path pointing to your local [zmk-config](customization.md) folder:
```
docker volume create --driver local -o o=bind -o type=none -o \

View file

@ -35,7 +35,7 @@ terminal to the ZMK repository and run the following command:
west config build.cmake-args -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
```
Every [build](build-flash#building) will now update the database. You will
Every [build](build-flash.md#building) will now update the database. You will
need to build once to create the database before code completion will work.
We'll tell Visual Studio Code where to find the database in the next step.

Some files were not shown because too many files have changed in this diff Show more