Merge branch 'zmkfirmware:main' into main

This commit is contained in:
Timoyoungster 2024-07-12 10:16:29 +02:00 committed by GitHub
commit e9663a4948
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 1461 additions and 360 deletions

View file

@ -11,6 +11,10 @@ project(zmk)
zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld) zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld)
zephyr_linker_sources(RODATA include/linker/zmk-events.ld) zephyr_linker_sources(RODATA include/linker/zmk-events.ld)
if(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS)
zephyr_linker_sources(DATA_SECTIONS include/linker/zmk-behavior-local-id-map.ld)
endif()
zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h) zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h)
zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h) zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h)
@ -20,9 +24,9 @@ target_include_directories(app PRIVATE include)
target_sources(app PRIVATE src/stdlib.c) target_sources(app PRIVATE src/stdlib.c)
target_sources(app PRIVATE src/activity.c) target_sources(app PRIVATE src/activity.c)
target_sources(app PRIVATE src/behavior.c) target_sources(app PRIVATE src/behavior.c)
target_sources(app PRIVATE src/kscan.c)
target_sources_ifdef(CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS app PRIVATE src/kscan_sideband_behaviors.c) target_sources_ifdef(CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS app PRIVATE src/kscan_sideband_behaviors.c)
target_sources(app PRIVATE src/matrix_transform.c) target_sources(app PRIVATE src/matrix_transform.c)
target_sources(app PRIVATE src/physical_layouts.c)
target_sources(app PRIVATE src/sensors.c) target_sources(app PRIVATE src/sensors.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
target_sources(app PRIVATE src/event_manager.c) target_sources(app PRIVATE src/event_manager.c)

View file

@ -7,6 +7,39 @@ config ZMK_BEHAVIOR_METADATA
Enabling this option adds APIs for documenting and fetching Enabling this option adds APIs for documenting and fetching
metadata describing a behaviors name, and supported parameters. metadata describing a behaviors name, and supported parameters.
config ZMK_BEHAVIOR_LOCAL_IDS
bool "Local IDs"
if ZMK_BEHAVIOR_LOCAL_IDS
config ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS
bool "Track in behavior bindings"
choice ZMK_BEHAVIOR_LOCAL_ID_TYPE
prompt "Local ID Type"
config ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE
bool "Settings Table"
depends on SETTINGS
select ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS
help
Use persistent entries in the settings subsystem to identify
behaviors by local ID, which uses the device name to generate
a new settings entry tying a presistant local ID to that name.
This guarantees stable, colllision-free local IDs at the expense
of settings storage used.
config ZMK_BEHAVIOR_LOCAL_ID_TYPE_CRC16
bool "CRC16 Hash"
help
Use the CRC16-ANSI hash of behavior device names to generate
stable behavior local IDs. This saves on settings storage at
the expense of (highly unlikely) risk of collisions.
endchoice
endif
config ZMK_BEHAVIOR_KEY_TOGGLE config ZMK_BEHAVIOR_KEY_TOGGLE
bool bool
default y default y

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1,9 @@
#
# Copyright (c) 2024 The ZMK Contributors
# SPDX-License-Identifier: MIT
#
# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller
# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -2,3 +2,10 @@ CONFIG_CONSOLE=n
CONFIG_SERIAL=n CONFIG_SERIAL=n
CONFIG_UART_CONSOLE=n CONFIG_UART_CONSOLE=n
CONFIG_ZMK_USB=y CONFIG_ZMK_USB=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

@ -5,3 +5,18 @@
*/ */
&xiao_serial { status = "disabled"; }; &xiao_serial { status = "disabled"; };
&code_partition {
reg = <0x100 (DT_SIZE_M(2) - 0x100 - DT_SIZE_K(512))>;
};
&flash0 {
reg = <0x10000000 DT_SIZE_M(2)>;
partitions {
storage_partition: partition@180000 {
reg = <0x180000 DT_SIZE_K(512)>;
read-only;
};
};
};

View file

@ -40,10 +40,8 @@ nice_view_spi: &arduino_spi {
/ { / {
chosen { chosen {
zmk,kscan = &kscan_matrix;
zmk,backlight = &backlight; zmk,backlight = &backlight;
zmk,underglow = &led_strip; zmk,underglow = &led_strip;
zmk,matrix-transform = &matrix_transform;
}; };
// Commented out until we add more powerful power domain support // Commented out until we add more powerful power domain support
@ -109,7 +107,6 @@ nice_view_spi: &arduino_spi {
kscan_direct: kscan_direct { kscan_direct: kscan_direct {
compatible = "zmk,kscan-gpio-direct"; compatible = "zmk,kscan-gpio-direct";
wakeup-source; wakeup-source;
status = "disabled";
input-gpios input-gpios
= <&arduino_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> = <&arduino_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>

View file

@ -13,13 +13,9 @@
// Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire. // Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire.
// &kscan_direct { status = "okay"; };
// &kscan_matrix { status = "disabled"; };
// / { // / {
// chosen { // chosen {
// zmk,matrix-transform = &direct_matrix_transform; // zmk,physical-layout = &direct_physical_layout;
// zmk,kscan = &kscan_direct;
// }; // };
// }; // };

View file

@ -7,13 +7,15 @@
#include "zmk_uno.dtsi" #include "zmk_uno.dtsi"
#include <behaviors.dtsi> #include <behaviors.dtsi>
#include <physical_layouts.dtsi>
#include <dt-bindings/zmk/bt.h> #include <dt-bindings/zmk/bt.h>
#include <dt-bindings/zmk/outputs.h> #include <dt-bindings/zmk/outputs.h>
/ { / {
chosen { chosen {
zmk,matrix-transform = &matrix_transform; zmk,physical-layout = &matrix_physical_layout;
}; };
sensors: sensors { sensors: sensors {
compatible = "zmk,keymap-sensors"; compatible = "zmk,keymap-sensors";
sensors = <&encoder>; sensors = <&encoder>;
@ -38,6 +40,8 @@
endpoint_sideband_behaviors { endpoint_sideband_behaviors {
compatible = "zmk,kscan-sideband-behaviors"; compatible = "zmk,kscan-sideband-behaviors";
auto-enable;
kscan = <&kscan_sp3t_toggle>; kscan = <&kscan_sp3t_toggle>;
first_toggle_sideband: first_toggle_sideband { first_toggle_sideband: first_toggle_sideband {
@ -56,4 +60,35 @@
}; };
}; };
matrix_physical_layout: matrix_physical_layout {
compatible = "zmk,physical-layout";
display-name = "Matrix Layout";
kscan = <&kscan_matrix>;
transform = <&matrix_transform>;
keys
= <&key_physical_attrs 100 100 0 0 0 0 0>
, <&key_physical_attrs 100 100 100 0 0 0 0>
, <&key_physical_attrs 100 100 0 100 0 0 0>
, <&key_physical_attrs 100 100 100 100 0 0 0>
;
};
direct_physical_layout: direct_physical_layout {
compatible = "zmk,physical-layout";
display-name = "Direct Wire Layout";
kscan = <&kscan_direct>;
transform = <&direct_matrix_transform>;
keys
= <&key_physical_attrs 100 100 0 0 0 0 0>
, <&key_physical_attrs 100 100 100 0 0 0 0>
, <&key_physical_attrs 100 100 0 100 0 0 0>
, <&key_physical_attrs 100 100 100 100 0 0 0>
;
};
}; };

View file

@ -6,13 +6,15 @@
#include "zmk_uno.dtsi" #include "zmk_uno.dtsi"
#include <physical_layouts.dtsi>
left_encoder: &encoder { left_encoder: &encoder {
status = "disabled"; status = "disabled";
}; };
/ { / {
chosen { chosen {
zmk,matrix-transform = &split_matrix_transform; zmk,physical-layout = &matrix_physical_layout;
}; };
split_matrix_transform: split_matrix_transform { split_matrix_transform: split_matrix_transform {
@ -31,18 +33,57 @@
split_direct_matrix_transform: split_direct_matrix_transform { split_direct_matrix_transform: split_direct_matrix_transform {
compatible = "zmk,matrix-transform"; compatible = "zmk,matrix-transform";
rows = <3>; rows = <2>;
columns = <4>; columns = <4>;
map = < map = <
RC(0,0) RC(0,1) RC(0,0) RC(0,1)
RC(0,2) RC(0,3) RC(0,2) RC(0,3)
RC(2,0) RC(2,1) RC(1,0) RC(1,1)
RC(2,2) RC(2,3) RC(1,2) RC(1,3)
>; >;
}; };
matrix_physical_layout: matrix_physical_layout {
compatible = "zmk,physical-layout";
display-name = "Matrix Layout";
kscan = <&kscan_matrix>;
transform = <&split_matrix_transform>;
keys
= <&key_physical_attrs 100 100 0 0 0 0 0>
, <&key_physical_attrs 100 100 100 0 0 0 0>
, <&key_physical_attrs 100 100 0 100 0 0 0>
, <&key_physical_attrs 100 100 100 100 0 0 0>
, <&key_physical_attrs 100 100 0 200 0 0 0>
, <&key_physical_attrs 100 100 100 200 0 0 0>
, <&key_physical_attrs 100 100 0 300 0 0 0>
, <&key_physical_attrs 100 100 100 300 0 0 0>
;
};
direct_physical_layout: direct_physical_layout {
compatible = "zmk,physical-layout";
display-name = "Direct Wire Layout";
kscan = <&kscan_direct>;
transform = <&split_direct_matrix_transform>;
keys
= <&key_physical_attrs 100 100 0 0 0 0 0>
, <&key_physical_attrs 100 100 100 0 0 0 0>
, <&key_physical_attrs 100 100 0 100 0 0 0>
, <&key_physical_attrs 100 100 100 100 0 0 0>
, <&key_physical_attrs 100 100 0 200 0 0 0>
, <&key_physical_attrs 100 100 100 200 0 0 0>
, <&key_physical_attrs 100 100 0 300 0 0 0>
, <&key_physical_attrs 100 100 100 300 0 0 0>
;
};
right_encoder: right_encoder { right_encoder: right_encoder {
steps = <80>; steps = <80>;
status = "disabled"; status = "disabled";

View file

@ -14,14 +14,9 @@
// Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire. // Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire.
// &kscan_direct { status = "okay"; };
// &kscan_matrix { status = "disabled"; };
// / { // / {
// chosen { // chosen {
// zmk,matrix-transform = &split_direct_matrix_transform; // zmk,physical-layout = &direct_physical_layout;
// zmk,kscan = &kscan_direct;
// }; // };
// }; // };

View file

@ -11,7 +11,7 @@
}; };
&split_direct_matrix_transform { &split_direct_matrix_transform {
row-offset = <2>; row-offset = <1>;
}; };
&right_encoder { &right_encoder {

View file

@ -22,6 +22,10 @@ include:
shield: kyria_left shield: kyria_left
cmake-args: "-DCONFIG_ZMK_DISPLAY=y" cmake-args: "-DCONFIG_ZMK_DISPLAY=y"
nickname: "display" nickname: "display"
- board: nice_nano_v2
shield: kyria_left
cmake-args: "-DCONFIG_ZMK_MOUSE=y"
nickname: "mouse"
- board: sparkfun_pro_micro_rp2040 - board: sparkfun_pro_micro_rp2040
shield: reviung41 shield: reviung41
cmake-args: "-DSNIPPET='zmk-usb-logging'" cmake-args: "-DSNIPPET='zmk-usb-logging'"

View file

@ -11,6 +11,9 @@ compatible: "zmk,kscan-sideband-behaviors"
include: kscan.yaml include: kscan.yaml
properties: properties:
auto-enable:
type: boolean
kscan: kscan:
type: phandle type: phandle
required: true required: true

View file

@ -0,0 +1,24 @@
#
# Copyright (c) 2024 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
description: |
The physical attributes of a key, including size, location, and rotation
compatible: "zmk,key-physical-attrs"
properties:
"#key-cells":
type: int
required: true
const: 7
key-cells:
- width
- height
- x
- y
- r
- rx
- ry

View file

@ -0,0 +1,23 @@
#
# Copyright (c) 2024 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
description: |
Describes how to correlate equivalent keys between layouts that don't have the exact same X,Y location.
compatible: "zmk,physical-layout-position-map"
properties:
complete:
type: boolean
description: If the mapping complete describes the key mapping, and no position based mapping should be used.
child-binding:
properties:
physical-layout:
type: phandle
description: The physical layout that corresponds to this mapping entry.
positions:
type: array
description: Array of key positions that match the same array entry in the other sibling nodes.

View file

@ -0,0 +1,26 @@
#
# Copyright (c) 2024 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
description: |
Describe the physical layout of a keyboard, including deps like the transform and kscan
that are needed for that layout to work.
compatible: "zmk,physical-layout"
properties:
display-name:
type: string
required: true
description: The name of this layout to display in the UI
transform:
type: phandle
required: true
description: The matrix transform to use along with this layout.
kscan:
type: phandle
description: The kscan to use along with this layout. The `zmk,kscan` chosen will be used as a fallback if this property is omitted.
keys:
type: phandle-array
description: Array of key physical attributes.

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/ {
key_physical_attrs: key_physical_attrs {
compatible = "zmk,key-physical-attrs";
#key-cells = <7>;
};
};

View file

@ -39,7 +39,7 @@ struct behavior_parameter_value_metadata {
BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE = 1, BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE = 1,
BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE = 2, BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE = 2,
BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE = 3, BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE = 3,
BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX = 4, BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID = 4,
} type; } type;
}; };
@ -108,6 +108,15 @@ struct zmk_behavior_ref {
const struct zmk_behavior_metadata metadata; const struct zmk_behavior_metadata metadata;
}; };
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS)
struct zmk_behavior_local_id_map {
const struct device *device;
zmk_behavior_local_id_t local_id;
};
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS)
#define ZMK_BEHAVIOR_REF_DT_NAME(node_id) _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id)) #define ZMK_BEHAVIOR_REF_DT_NAME(node_id) _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id))
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) #if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
@ -125,9 +134,17 @@ struct zmk_behavior_ref {
#define ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) \ #define ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) \
{ .device = _dev, .metadata = ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id), } { .device = _dev, .metadata = ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id), }
#define ZMK_BEHAVIOR_LOCAL_ID_MAP_INITIALIZER(node_id, _dev) \
{ .device = _dev, }
#define ZMK_BEHAVIOR_REF_DEFINE(name, node_id, _dev) \ #define ZMK_BEHAVIOR_REF_DEFINE(name, node_id, _dev) \
static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, name) = \ static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, name) = \
ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev); \
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS), \
(static const STRUCT_SECTION_ITERABLE(zmk_behavior_local_id_map, \
_CONCAT(_zmk_behavior_local_id_map, name)) = \
ZMK_BEHAVIOR_LOCAL_ID_MAP_INITIALIZER(node_id, _dev)), \
());
#define ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) \ #define ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) \
ZMK_BEHAVIOR_REF_DEFINE(ZMK_BEHAVIOR_REF_DT_NAME(node_id), node_id, DEVICE_DT_GET(node_id)) ZMK_BEHAVIOR_REF_DEFINE(ZMK_BEHAVIOR_REF_DT_NAME(node_id), node_id, DEVICE_DT_GET(node_id))

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/linker/linker-defs.h>
ITERABLE_SECTION_RAM(zmk_behavior_local_id_map, 4)

View file

@ -11,7 +11,12 @@
#define ZMK_BEHAVIOR_OPAQUE 0 #define ZMK_BEHAVIOR_OPAQUE 0
#define ZMK_BEHAVIOR_TRANSPARENT 1 #define ZMK_BEHAVIOR_TRANSPARENT 1
typedef uint16_t zmk_behavior_local_id_t;
struct zmk_behavior_binding { struct zmk_behavior_binding {
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS)
zmk_behavior_local_id_t local_id;
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS)
const char *behavior_dev; const char *behavior_dev;
uint32_t param1; uint32_t param1;
uint32_t param2; uint32_t param2;
@ -36,3 +41,23 @@ struct zmk_behavior_binding_event {
* unrelated node which shares the same name as a behavior. * unrelated node which shares the same name as a behavior.
*/ */
const struct device *zmk_behavior_get_binding(const char *name); const struct device *zmk_behavior_get_binding(const char *name);
/**
* @brief Get a local ID for a behavior from its @p name field.
*
* @param name Behavior name to search for.
*
* @retval The local ID value that can be used to reference the behavior later, across reboots.
* @retval UINT16_MAX if the behavior is not found or its initialization function failed.
*/
zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name);
/**
* @brief Get a behavior name for a behavior from its @p local_id .
*
* @param local_id Behavior local ID used to search for the behavior
*
* @retval The name of the behavior that is associated with that local ID.
* @retval NULL if the behavior is not found or its initialization function failed.
*/
const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id);

View file

@ -29,7 +29,10 @@ int zmk_ble_prof_disconnect(uint8_t index);
int zmk_ble_active_profile_index(void); int zmk_ble_active_profile_index(void);
int zmk_ble_profile_index(const bt_addr_le_t *addr); int zmk_ble_profile_index(const bt_addr_le_t *addr);
bt_addr_le_t *zmk_ble_active_profile_addr(void); bt_addr_le_t *zmk_ble_active_profile_addr(void);
struct bt_conn *zmk_ble_active_profile_conn(void);
bool zmk_ble_active_profile_is_open(void); bool zmk_ble_active_profile_is_open(void);
bool zmk_ble_active_profile_is_connected(void); bool zmk_ble_active_profile_is_connected(void);
char *zmk_ble_active_profile_name(void); char *zmk_ble_active_profile_name(void);

View file

@ -64,6 +64,7 @@ struct zmk_event_subscription {
#define ZMK_LISTENER(mod, cb) const struct zmk_listener zmk_listener_##mod = {.callback = cb}; #define ZMK_LISTENER(mod, cb) const struct zmk_listener zmk_listener_##mod = {.callback = cb};
#define ZMK_SUBSCRIPTION(mod, ev_type) \ #define ZMK_SUBSCRIPTION(mod, ev_type) \
extern const struct zmk_listener zmk_listener_##mod; \
const Z_DECL_ALIGN(struct zmk_event_subscription) \ const Z_DECL_ALIGN(struct zmk_event_subscription) \
_CONCAT(_CONCAT(zmk_event_sub_, mod), ev_type) __used \ _CONCAT(_CONCAT(zmk_event_sub_, mod), ev_type) __used \
__attribute__((__section__(".event_subscription"))) = { \ __attribute__((__section__(".event_subscription"))) = { \

View file

@ -1,11 +0,0 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/device.h>
int zmk_kscan_init(const struct device *dev);

View file

@ -9,15 +9,25 @@
#include <zephyr/devicetree.h> #include <zephyr/devicetree.h>
#define ZMK_MATRIX_NODE_ID DT_CHOSEN(zmk_kscan) #define ZMK_MATRIX_NODE_ID DT_CHOSEN(zmk_kscan)
#define ZMK_MATRIX_HAS_TRANSFORM DT_HAS_CHOSEN(zmk_matrix_transform)
#if DT_HAS_CHOSEN(zmk_matrix_transform) #if DT_HAS_COMPAT_STATUS_OKAY(zmk_physical_layout)
#if ZMK_MATRIX_HAS_TRANSFORM
#error "To use physical layouts, remove the chosen `zmk,matrix-transform` value."
#endif
#define ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY(node_id) \
uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(DT_PHANDLE(node_id, transform), map)];
#define ZMK_KEYMAP_LEN \
sizeof(union {DT_FOREACH_STATUS_OKAY(zmk_physical_layout, ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY)})
#elif ZMK_MATRIX_HAS_TRANSFORM
#define ZMK_KEYMAP_TRANSFORM_NODE DT_CHOSEN(zmk_matrix_transform) #define ZMK_KEYMAP_TRANSFORM_NODE DT_CHOSEN(zmk_matrix_transform)
#define ZMK_KEYMAP_LEN DT_PROP_LEN(ZMK_KEYMAP_TRANSFORM_NODE, map) #define ZMK_KEYMAP_LEN DT_PROP_LEN(ZMK_KEYMAP_TRANSFORM_NODE, map)
#define ZMK_MATRIX_ROWS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, rows)
#define ZMK_MATRIX_COLS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, columns)
#else /* DT_HAS_CHOSEN(zmk_matrix_transform) */ #else /* DT_HAS_CHOSEN(zmk_matrix_transform) */
#if DT_NODE_HAS_PROP(ZMK_MATRIX_NODE_ID, row_gpios) #if DT_NODE_HAS_PROP(ZMK_MATRIX_NODE_ID, row_gpios)

View file

@ -6,4 +6,16 @@
#pragma once #pragma once
int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column); #include <zephyr/sys/util.h>
typedef const struct zmk_matrix_transform *zmk_matrix_transform_t;
#define ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN() \
extern const struct zmk_matrix_transform zmk_matrix_transform_default
#define ZMK_MATRIX_TRANSFORM_EXTERN(node_id) \
extern const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, node_id)
#define ZMK_MATRIX_TRANSFORM_T_FOR_NODE(node_id) &_CONCAT(zmk_matrix_transform_, node_id)
int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row,
uint32_t column);

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/matrix_transform.h>
struct zmk_key_physical_attrs {
int16_t width;
int16_t height;
int16_t x;
int16_t y;
int16_t rx;
int16_t ry;
int16_t r;
};
struct zmk_physical_layout {
const char *display_name;
zmk_matrix_transform_t matrix_transform;
const struct device *kscan;
const struct zmk_key_physical_attrs *keys;
size_t keys_len;
};
#define ZMK_PHYS_LAYOUTS_FOREACH(_ref) STRUCT_SECTION_FOREACH(zmk_physical_layout, _ref)
size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **phys_layouts);
int zmk_physical_layouts_select(uint8_t index);
int zmk_physical_layouts_get_selected(void);
int zmk_physical_layouts_check_unsaved_selection(void);
int zmk_physical_layouts_save_selected(void);
int zmk_physical_layouts_revert_selected(void);
int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map);

View file

@ -12,6 +12,7 @@
#include <zephyr/drivers/kscan.h> #include <zephyr/drivers/kscan.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/__assert.h> #include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h> #include <zephyr/sys/util.h>
@ -167,6 +168,21 @@ static int kscan_charlieplex_set_all_outputs(const struct device *dev, const int
return 0; return 0;
} }
static int kscan_charlieplex_disconnect_all(const struct device *dev) {
const struct kscan_charlieplex_config *config = dev->config;
for (int i = 0; i < config->cells.len; i++) {
const struct gpio_dt_spec *gpio = &config->cells.gpios[i];
int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
}
return 0;
}
static int kscan_charlieplex_interrupt_configure(const struct device *dev, static int kscan_charlieplex_interrupt_configure(const struct device *dev,
const gpio_flags_t flags) { const gpio_flags_t flags) {
const struct kscan_charlieplex_config *config = dev->config; const struct kscan_charlieplex_config *config = dev->config;
@ -359,11 +375,7 @@ static int kscan_charlieplex_init_interrupt(const struct device *dev) {
return err; return err;
} }
static int kscan_charlieplex_init(const struct device *dev) { static void kscan_charlieplex_setup_pins(const struct device *dev) {
struct kscan_charlieplex_data *data = dev->data;
data->dev = dev;
kscan_charlieplex_init_inputs(dev); kscan_charlieplex_init_inputs(dev);
kscan_charlieplex_set_all_outputs(dev, 0); kscan_charlieplex_set_all_outputs(dev, 0);
@ -371,7 +383,46 @@ static int kscan_charlieplex_init(const struct device *dev) {
if (config->use_interrupt) { if (config->use_interrupt) {
kscan_charlieplex_init_interrupt(dev); kscan_charlieplex_init_interrupt(dev);
} }
}
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_charlieplex_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
kscan_charlieplex_interrupt_configure(dev, GPIO_INT_DISABLE);
kscan_charlieplex_disconnect_all(dev);
return kscan_charlieplex_disable(dev);
case PM_DEVICE_ACTION_RESUME:
kscan_charlieplex_setup_pins(dev);
return kscan_charlieplex_enable(dev);
default:
return -ENOTSUP;
}
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_charlieplex_init(const struct device *dev) {
struct kscan_charlieplex_data *data = dev->data;
data->dev = dev;
k_work_init_delayable(&data->work, kscan_charlieplex_work_handler); k_work_init_delayable(&data->work, kscan_charlieplex_work_handler);
#if IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_init_suspended(dev);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
pm_device_runtime_enable(dev);
#endif
#else
kscan_charlieplex_setup_pins(dev);
#endif
return 0; return 0;
} }
@ -406,8 +457,10 @@ static const struct kscan_driver_api kscan_charlieplex_api = {
COND_THIS_INTERRUPT(n, (.use_interrupt = INST_INTR_DEFINED(n), )) \ COND_THIS_INTERRUPT(n, (.use_interrupt = INST_INTR_DEFINED(n), )) \
COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \
\ \
DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, NULL, &kscan_charlieplex_data_##n, \ PM_DEVICE_DT_INST_DEFINE(n, kscan_charlieplex_pm_action); \
&kscan_charlieplex_config_##n, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \ \
&kscan_charlieplex_api); DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, PM_DEVICE_DT_INST_GET(n), \
&kscan_charlieplex_data_##n, &kscan_charlieplex_config_##n, POST_KERNEL, \
CONFIG_KSCAN_INIT_PRIORITY, &kscan_charlieplex_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIEPLEX_INIT); DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIEPLEX_INIT);

View file

@ -294,6 +294,24 @@ static int kscan_direct_init_input_inst(const struct device *dev, const struct g
return 0; return 0;
} }
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_direct_disconnect_inputs(const struct device *dev) {
const struct kscan_direct_data *data = dev->data;
for (int i = 0; i < data->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED);
if (err) {
return err;
}
}
return 0;
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_direct_init_inputs(const struct device *dev) { static int kscan_direct_init_inputs(const struct device *dev) {
const struct kscan_direct_data *data = dev->data; const struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config; const struct kscan_direct_config *config = dev->config;
@ -317,9 +335,20 @@ static int kscan_direct_init(const struct device *dev) {
// Sort inputs by port so we can read each port just once per scan. // Sort inputs by port so we can read each port just once per scan.
kscan_gpio_list_sort_by_port(&data->inputs); kscan_gpio_list_sort_by_port(&data->inputs);
k_work_init_delayable(&data->work, kscan_direct_work_handler);
#if IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_init_suspended(dev);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
pm_device_runtime_enable(dev);
#endif
#else
kscan_direct_init_inputs(dev); kscan_direct_init_inputs(dev);
k_work_init_delayable(&data->work, kscan_direct_work_handler); #endif
return 0; return 0;
} }
@ -329,8 +358,10 @@ static int kscan_direct_init(const struct device *dev) {
static int kscan_direct_pm_action(const struct device *dev, enum pm_device_action action) { static int kscan_direct_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) { switch (action) {
case PM_DEVICE_ACTION_SUSPEND: case PM_DEVICE_ACTION_SUSPEND:
kscan_direct_disconnect_inputs(dev);
return kscan_direct_disable(dev); return kscan_direct_disable(dev);
case PM_DEVICE_ACTION_RESUME: case PM_DEVICE_ACTION_RESUME:
kscan_direct_init_inputs(dev);
return kscan_direct_enable(dev); return kscan_direct_enable(dev);
default: default:
return -ENOTSUP; return -ENOTSUP;

View file

@ -405,6 +405,44 @@ static int kscan_matrix_init_outputs(const struct device *dev) {
return 0; return 0;
} }
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_matrix_disconnect_inputs(const struct device *dev) {
const struct kscan_matrix_data *data = dev->data;
for (int i = 0; i < data->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_disconnect_outputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec;
int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED);
if (err) {
return err;
}
}
return 0;
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
static void kscan_matrix_setup_pins(const struct device *dev) {
kscan_matrix_init_inputs(dev);
kscan_matrix_init_outputs(dev);
kscan_matrix_set_all_outputs(dev, 0);
}
static int kscan_matrix_init(const struct device *dev) { static int kscan_matrix_init(const struct device *dev) {
struct kscan_matrix_data *data = dev->data; struct kscan_matrix_data *data = dev->data;
@ -413,12 +451,19 @@ static int kscan_matrix_init(const struct device *dev) {
// Sort inputs by port so we can read each port just once per scan. // Sort inputs by port so we can read each port just once per scan.
kscan_gpio_list_sort_by_port(&data->inputs); kscan_gpio_list_sort_by_port(&data->inputs);
kscan_matrix_init_inputs(dev);
kscan_matrix_init_outputs(dev);
kscan_matrix_set_all_outputs(dev, 0);
k_work_init_delayable(&data->work, kscan_matrix_work_handler); k_work_init_delayable(&data->work, kscan_matrix_work_handler);
#if IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_init_suspended(dev);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
pm_device_runtime_enable(dev);
#endif
#else
kscan_matrix_setup_pins(dev);
#endif
return 0; return 0;
} }
@ -427,8 +472,12 @@ static int kscan_matrix_init(const struct device *dev) {
static int kscan_matrix_pm_action(const struct device *dev, enum pm_device_action action) { static int kscan_matrix_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) { switch (action) {
case PM_DEVICE_ACTION_SUSPEND: case PM_DEVICE_ACTION_SUSPEND:
kscan_matrix_disconnect_inputs(dev);
kscan_matrix_disconnect_outputs(dev);
return kscan_matrix_disable(dev); return kscan_matrix_disable(dev);
case PM_DEVICE_ACTION_RESUME: case PM_DEVICE_ACTION_RESUME:
kscan_matrix_setup_pins(dev);
return kscan_matrix_enable(dev); return kscan_matrix_enable(dev);
default: default:
return -ENOTSUP; return -ENOTSUP;

View file

@ -58,7 +58,7 @@ static int zmk_backlight_update(void) {
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
static int backlight_settings_load_cb(const char *name, size_t len, settings_read_cb read_cb, static int backlight_settings_load_cb(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg, void *param) { void *cb_arg) {
const char *next; const char *next;
if (settings_name_steq(name, "state", &next) && !next) { if (settings_name_steq(name, "state", &next) && !next) {
if (len != sizeof(state)) { if (len != sizeof(state)) {
@ -66,11 +66,18 @@ static int backlight_settings_load_cb(const char *name, size_t len, settings_rea
} }
int rc = read_cb(cb_arg, &state, sizeof(state)); int rc = read_cb(cb_arg, &state, sizeof(state));
if (rc >= 0) {
rc = zmk_backlight_update();
}
return MIN(rc, 0); return MIN(rc, 0);
} }
return -ENOENT; return -ENOENT;
} }
SETTINGS_STATIC_HANDLER_DEFINE(backlight, "backlight", NULL, backlight_settings_load_cb, NULL,
NULL);
static void backlight_save_work_handler(struct k_work *work) { static void backlight_save_work_handler(struct k_work *work) {
settings_save_one("backlight/state", &state, sizeof(state)); settings_save_one("backlight/state", &state, sizeof(state));
} }
@ -85,11 +92,6 @@ static int zmk_backlight_init(void) {
} }
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
int rc = settings_load_subtree_direct("backlight", backlight_settings_load_cb, NULL);
if (rc != 0) {
LOG_ERR("Failed to load backlight settings: %d", rc);
}
k_work_init_delayable(&backlight_save_work, backlight_save_work_handler); k_work_init_delayable(&backlight_save_work, backlight_save_work_handler);
#endif #endif
#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) #if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB)

View file

@ -6,9 +6,17 @@
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/init.h> #include <zephyr/init.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util_macro.h> #include <zephyr/sys/util_macro.h>
#include <string.h> #include <string.h>
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) && \
IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE)
#include <zephyr/settings/settings.h>
#endif
#include <drivers/behavior.h> #include <drivers/behavior.h>
#include <zmk/behavior.h> #include <zmk/behavior.h>
#include <zmk/hid.h> #include <zmk/hid.h>
@ -91,7 +99,7 @@ static int check_param_matches_value(const struct behavior_parameter_value_metad
} }
break; break;
case BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX: case BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID:
if (param >= 0 && param < ZMK_KEYMAP_LEN) { if (param >= 0 && param < ZMK_KEYMAP_LEN) {
return PARAM_MATCHES; return PARAM_MATCHES;
} }
@ -185,6 +193,125 @@ int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) {
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) #endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
} }
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS)
zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name) {
if (!name) {
return UINT16_MAX;
}
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (z_device_is_ready(item->device) && strcmp(item->device->name, name) == 0) {
return item->local_id;
}
}
return UINT16_MAX;
}
const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (z_device_is_ready(item->device) && item->local_id == local_id) {
return item->device->name;
}
}
return NULL;
}
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_CRC16)
static int behavior_local_id_init(void) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
item->local_id = crc16_ansi(item->device->name, strlen(item->device->name));
}
return 0;
}
SYS_INIT(behavior_local_id_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
#elif IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE)
static zmk_behavior_local_id_t largest_local_id = 0;
static int behavior_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
const char *next;
if (settings_name_steq(name, "local_id", &next) && next) {
char *endptr;
zmk_behavior_local_id_t local_id = strtoul(next, &endptr, 10);
if (*endptr != '\0') {
LOG_WRN("Invalid behavior local ID: %s with endptr %s", next, endptr);
return -EINVAL;
}
if (len >= 64) {
LOG_ERR("Too large binding setting size (got %d expected less than %d)", len, 64);
return -EINVAL;
}
char name[len + 1];
int err = read_cb(cb_arg, name, len);
if (err <= 0) {
LOG_ERR("Failed to handle keymap binding from settings (err %d)", err);
return err;
}
name[len] = '\0';
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (strcmp(name, item->device->name) == 0) {
item->local_id = local_id;
largest_local_id = MAX(largest_local_id, local_id);
return 0;
}
}
return -EINVAL;
}
return 0;
}
static int behavior_handle_commit(void) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (item->local_id != 0) {
continue;
}
if (!item->device || !item->device->name || !device_is_ready(item->device)) {
LOG_WRN("Skipping ID for device that doesn't exist or isn't ready");
continue;
}
item->local_id = ++largest_local_id;
char setting_name[32];
sprintf(setting_name, "behavior/local_id/%d", item->local_id);
// If the `device->name` is readonly in flash, settings save can fail to copy/read it while
// persisting to flash, so copy the device name into memory first before saving.
char device_name[32];
snprintf(device_name, ARRAY_SIZE(device_name), "%s", item->device->name);
settings_save_one(setting_name, device_name, strlen(device_name));
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(behavior, "behavior", NULL, behavior_handle_set,
behavior_handle_commit, NULL);
#else
#error "A behavior local ID mechanism must be selected"
#endif
#endif
#if IS_ENABLED(CONFIG_LOG) #if IS_ENABLED(CONFIG_LOG)
static int check_behavior_names(void) { static int check_behavior_names(void) {
// Behavior names must be unique, but we don't have a good way to enforce this // Behavior names must be unique, but we don't have a good way to enforce this

View file

@ -19,27 +19,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
static const struct behavior_parameter_value_metadata param_values[] = {
{
.display_name = "Key",
.type = BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE,
},
};
static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
.param1_values = param_values,
.param1_values_len = ARRAY_SIZE(param_values),
}};
static const struct behavior_parameter_metadata metadata = {
.sets_len = ARRAY_SIZE(param_metadata_set),
.sets = param_metadata_set,
};
#endif
struct behavior_key_repeat_config { struct behavior_key_repeat_config {
uint8_t index; uint8_t index;
uint8_t usage_pages_count; uint8_t usage_pages_count;
@ -89,7 +68,7 @@ static const struct behavior_driver_api behavior_key_repeat_driver_api = {
.binding_pressed = on_key_repeat_binding_pressed, .binding_pressed = on_key_repeat_binding_pressed,
.binding_released = on_key_repeat_binding_released, .binding_released = on_key_repeat_binding_released,
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) #if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
.parameter_metadata = &metadata, .get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) #endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
}; };

View file

@ -36,8 +36,7 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
static const struct behavior_parameter_value_metadata param_values[] = { static const struct behavior_parameter_value_metadata param_values[] = {
{ {
.display_name = "Key", .display_name = "Key",
.type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD, .type = BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE,
.standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE,
}, },
}; };

View file

@ -20,7 +20,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static const struct behavior_parameter_value_metadata param_values[] = { static const struct behavior_parameter_value_metadata param_values[] = {
{ {
.display_name = "Layer", .display_name = "Layer",
.type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX, .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID,
}, },
}; };

View file

@ -37,8 +37,7 @@ static int to_keymap_binding_released(struct zmk_behavior_binding *binding,
static const struct behavior_parameter_value_metadata param_values[] = { static const struct behavior_parameter_value_metadata param_values[] = {
{ {
.display_name = "Layer", .display_name = "Layer",
.type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD, .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID,
.standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX,
}, },
}; };

View file

@ -39,8 +39,7 @@ static int tog_keymap_binding_released(struct zmk_behavior_binding *binding,
static const struct behavior_parameter_value_metadata param_values[] = { static const struct behavior_parameter_value_metadata param_values[] = {
{ {
.display_name = "Layer", .display_name = "Layer",
.type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD, .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID,
.standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX,
}, },
}; };

View file

@ -318,6 +318,21 @@ int zmk_ble_prof_disconnect(uint8_t index) {
bt_addr_le_t *zmk_ble_active_profile_addr(void) { return &profiles[active_profile].peer; } bt_addr_le_t *zmk_ble_active_profile_addr(void) { return &profiles[active_profile].peer; }
struct bt_conn *zmk_ble_active_profile_conn(void) {
struct bt_conn *conn;
bt_addr_le_t *addr = zmk_ble_active_profile_addr();
if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
LOG_WRN("Not sending, no active address for current profile");
return NULL;
} else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) {
LOG_WRN("Not sending, not connected to active profile");
return NULL;
}
return conn;
}
char *zmk_ble_active_profile_name(void) { return profiles[active_profile].name; } char *zmk_ble_active_profile_name(void) { return profiles[active_profile].name; }
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
@ -430,7 +445,11 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c
return 0; return 0;
}; };
struct settings_handler profiles_handler = {.name = "ble", .h_set = ble_profiles_handle_set}; static int zmk_ble_complete_startup(void);
static struct settings_handler profiles_handler = {
.name = "ble", .h_set = ble_profiles_handle_set, .h_commit = zmk_ble_complete_startup};
#endif /* IS_ENABLED(CONFIG_SETTINGS) */ #endif /* IS_ENABLED(CONFIG_SETTINGS) */
static bool is_conn_active_profile(const struct bt_conn *conn) { static bool is_conn_active_profile(const struct bt_conn *conn) {
@ -629,29 +648,7 @@ static void zmk_ble_ready(int err) {
update_advertising(); update_advertising();
} }
static int zmk_ble_init(void) { static int zmk_ble_complete_startup(void) {
int err = bt_enable(NULL);
if (err) {
LOG_ERR("BLUETOOTH FAILED (%d)", err);
return err;
}
#if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
err = settings_register(&profiles_handler);
if (err) {
LOG_ERR("Failed to setup the profile settings handler (err %d)", err);
return err;
}
k_work_init_delayable(&ble_save_work, ble_save_profile_work);
settings_load_subtree("ble");
settings_load_subtree("bt");
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
LOG_WRN("Clearing all existing BLE bond information from the keyboard"); LOG_WRN("Clearing all existing BLE bond information from the keyboard");
@ -691,6 +688,24 @@ static int zmk_ble_init(void) {
return 0; return 0;
} }
static int zmk_ble_init(void) {
int err = bt_enable(NULL);
if (err < 0 && err != -EALREADY) {
LOG_ERR("BLUETOOTH FAILED (%d)", err);
return err;
}
#if IS_ENABLED(CONFIG_SETTINGS)
settings_register(&profiles_handler);
k_work_init_delayable(&ble_save_work, ble_save_profile_work);
#else
zmk_ble_complete_startup();
#endif
return 0;
}
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
static bool zmk_ble_numeric_usage_to_value(const zmk_key_t key, const zmk_key_t one, static bool zmk_ble_numeric_usage_to_value(const zmk_key_t key, const zmk_key_t one,

View file

@ -263,7 +263,8 @@ static int endpoints_handle_set(const char *name, size_t len, settings_read_cb r
return 0; return 0;
} }
struct settings_handler endpoints_handler = {.name = "endpoints", .h_set = endpoints_handle_set}; SETTINGS_STATIC_HANDLER_DEFINE(endpoints, "endpoints", NULL, endpoints_handle_set, NULL, NULL);
#endif /* IS_ENABLED(CONFIG_SETTINGS) */ #endif /* IS_ENABLED(CONFIG_SETTINGS) */
static bool is_usb_ready(void) { static bool is_usb_ready(void) {
@ -322,17 +323,7 @@ static struct zmk_endpoint_instance get_selected_instance(void) {
static int zmk_endpoints_init(void) { static int zmk_endpoints_init(void) {
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
int err = settings_register(&endpoints_handler);
if (err) {
LOG_ERR("Failed to register the endpoints settings handler (err %d)", err);
return err;
}
k_work_init_delayable(&endpoints_save_work, endpoints_save_preferred_work); k_work_init_delayable(&endpoints_save_work, endpoints_save_preferred_work);
settings_load_subtree("endpoints");
#endif #endif
current_instance = get_selected_instance(); current_instance = get_selected_instance();

View file

@ -121,12 +121,27 @@ static int ext_power_settings_set(const char *name, size_t len, settings_read_cb
return -ENOENT; return -ENOENT;
} }
struct settings_handler ext_power_conf = {.name = "ext_power/state", static int ext_power_settings_commit() {
.h_set = ext_power_settings_set}; const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0));
struct ext_power_generic_data *data = dev->data;
if (!data->settings_init) {
data->status = true;
k_work_schedule(&ext_power_save_work, K_NO_WAIT);
ext_power_enable(dev);
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(ext_power, "ext_power/state", NULL, ext_power_settings_set,
ext_power_settings_commit, NULL);
#endif #endif
static int ext_power_generic_init(const struct device *dev) { static int ext_power_generic_init(const struct device *dev) {
struct ext_power_generic_data *data = dev->data;
const struct ext_power_generic_config *config = dev->config; const struct ext_power_generic_config *config = dev->config;
if (gpio_pin_configure_dt(&config->control, GPIO_OUTPUT_INACTIVE)) { if (gpio_pin_configure_dt(&config->control, GPIO_OUTPUT_INACTIVE)) {
@ -135,30 +150,12 @@ static int ext_power_generic_init(const struct device *dev) {
} }
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
int err = settings_register(&ext_power_conf);
if (err) {
LOG_ERR("Failed to register the ext_power settings handler (err %d)", err);
return err;
}
k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work);
// Set default value (on) if settings isn't set
settings_load_subtree("ext_power");
if (!data->settings_init) {
data->status = true;
k_work_schedule(&ext_power_save_work, K_NO_WAIT);
ext_power_enable(dev);
}
#else
// Default to the ext_power being open when no settings
ext_power_enable(dev);
#endif #endif
// Enable by default. We may get disabled again once settings load.
ext_power_enable(dev);
if (config->init_delay_ms) { if (config->init_delay_ms) {
k_msleep(config->init_delay_ms); k_msleep(config->init_delay_ms);
} }

View file

@ -64,5 +64,5 @@ static int profile_listener(const zmk_event_t *eh) {
return 0; return 0;
} }
static ZMK_LISTENER(profile_listener, profile_listener); ZMK_LISTENER(profile_listener, profile_listener);
static ZMK_SUBSCRIPTION(profile_listener, zmk_endpoint_changed); ZMK_SUBSCRIPTION(profile_listener, zmk_endpoint_changed);

View file

@ -220,21 +220,6 @@ BT_GATT_SERVICE_DEFINE(
BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point));
struct bt_conn *destination_connection(void) {
struct bt_conn *conn;
bt_addr_le_t *addr = zmk_ble_active_profile_addr();
LOG_DBG("Address pointer %p", addr);
if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
LOG_WRN("Not sending, no active address for current profile");
return NULL;
} else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) {
LOG_WRN("Not sending, not connected to active profile");
return NULL;
}
return conn;
}
K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE);
struct k_work_q hog_work_q; struct k_work_q hog_work_q;
@ -246,7 +231,7 @@ void send_keyboard_report_callback(struct k_work *work) {
struct zmk_hid_keyboard_report_body report; struct zmk_hid_keyboard_report_body report;
while (k_msgq_get(&zmk_hog_keyboard_msgq, &report, K_NO_WAIT) == 0) { while (k_msgq_get(&zmk_hog_keyboard_msgq, &report, K_NO_WAIT) == 0) {
struct bt_conn *conn = destination_connection(); struct bt_conn *conn = zmk_ble_active_profile_conn();
if (conn == NULL) { if (conn == NULL) {
return; return;
} }
@ -298,7 +283,7 @@ void send_consumer_report_callback(struct k_work *work) {
struct zmk_hid_consumer_report_body report; struct zmk_hid_consumer_report_body report;
while (k_msgq_get(&zmk_hog_consumer_msgq, &report, K_NO_WAIT) == 0) { while (k_msgq_get(&zmk_hog_consumer_msgq, &report, K_NO_WAIT) == 0) {
struct bt_conn *conn = destination_connection(); struct bt_conn *conn = zmk_ble_active_profile_conn();
if (conn == NULL) { if (conn == NULL) {
return; return;
} }
@ -351,7 +336,7 @@ K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body),
void send_mouse_report_callback(struct k_work *work) { void send_mouse_report_callback(struct k_work *work) {
struct zmk_hid_mouse_report_body report; struct zmk_hid_mouse_report_body report;
while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) {
struct bt_conn *conn = destination_connection(); struct bt_conn *conn = zmk_ble_active_profile_conn();
if (conn == NULL) { if (conn == NULL) {
return; return;
} }

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix_transform.h>
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#define ZMK_KSCAN_EVENT_STATE_PRESSED 0
#define ZMK_KSCAN_EVENT_STATE_RELEASED 1
struct zmk_kscan_event {
uint32_t row;
uint32_t column;
uint32_t state;
};
struct zmk_kscan_msg_processor {
struct k_work work;
} msg_processor;
K_MSGQ_DEFINE(zmk_kscan_msgq, sizeof(struct zmk_kscan_event), CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4);
static void zmk_kscan_callback(const struct device *dev, uint32_t row, uint32_t column,
bool pressed) {
struct zmk_kscan_event ev = {
.row = row,
.column = column,
.state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)};
k_msgq_put(&zmk_kscan_msgq, &ev, K_NO_WAIT);
k_work_submit(&msg_processor.work);
}
void zmk_kscan_process_msgq(struct k_work *item) {
struct zmk_kscan_event ev;
while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) {
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
int32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column);
if (position < 0) {
LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column,
(pressed ? "true" : "false"));
continue;
}
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position,
(pressed ? "true" : "false"));
raise_zmk_position_state_changed(
(struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
.state = pressed,
.position = position,
.timestamp = k_uptime_get()});
}
}
int zmk_kscan_init(const struct device *dev) {
if (dev == NULL) {
LOG_ERR("Failed to get the KSCAN device");
return -EINVAL;
}
k_work_init(&msg_processor.work, zmk_kscan_process_msgq);
#if IS_ENABLED(CONFIG_PM_DEVICE)
if (pm_device_wakeup_is_capable(dev)) {
pm_device_wakeup_enable(dev, true);
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
kscan_config(dev, zmk_kscan_callback);
kscan_enable_callback(dev);
return 0;
}

View file

@ -26,6 +26,7 @@ struct ksbb_entry {
struct ksbb_config { struct ksbb_config {
const struct device *kscan; const struct device *kscan;
bool auto_enable;
struct ksbb_entry *entries; struct ksbb_entry *entries;
size_t entries_len; size_t entries_len;
}; };
@ -93,34 +94,65 @@ void ksbb_inner_kscan_callback(const struct device *dev, uint32_t row, uint32_t
} }
static int ksbb_configure(const struct device *dev, kscan_callback_t callback) { static int ksbb_configure(const struct device *dev, kscan_callback_t callback) {
const struct ksbb_config *cfg = dev->config;
struct ksbb_data *data = dev->data; struct ksbb_data *data = dev->data;
data->callback = callback; data->callback = callback;
#if IS_ENABLED(CONFIG_PM_DEVICE)
if (pm_device_wakeup_is_enabled(dev) && pm_device_wakeup_is_capable(cfg->kscan)) {
pm_device_wakeup_enable(cfg->kscan, true);
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
return 0; return 0;
} }
static int ksbb_enable(const struct device *dev) { static int ksbb_enable(const struct device *dev) {
struct ksbb_data *data = dev->data; struct ksbb_data *data = dev->data;
const struct ksbb_config *config = dev->config;
data->enabled = true; data->enabled = true;
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) {
pm_device_runtime_get(config->kscan);
}
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(config->kscan, PM_DEVICE_ACTION_RESUME);
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
kscan_config(config->kscan, &ksbb_inner_kscan_callback);
kscan_enable_callback(config->kscan);
return 0; return 0;
} }
static int ksbb_disable(const struct device *dev) { static int ksbb_disable(const struct device *dev) {
struct ksbb_data *data = dev->data; struct ksbb_data *data = dev->data;
const struct ksbb_config *config = dev->config;
data->enabled = false; data->enabled = false;
kscan_disable_callback(config->kscan);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) {
pm_device_runtime_put(config->kscan);
}
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(config->kscan, PM_DEVICE_ACTION_SUSPEND);
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
return 0; return 0;
} }
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
return ksbb_disable(dev);
case PM_DEVICE_ACTION_RESUME:
return ksbb_enable(dev);
default:
return -ENOTSUP;
}
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
static int ksbb_init(const struct device *dev) { static int ksbb_init(const struct device *dev) {
const struct ksbb_config *config = dev->config; const struct ksbb_config *config = dev->config;
@ -129,8 +161,16 @@ static int ksbb_init(const struct device *dev) {
return -ENODEV; return -ENODEV;
} }
if (config->auto_enable) {
#if !IS_ENABLED(CONFIG_PM_DEVICE)
kscan_config(config->kscan, &ksbb_inner_kscan_callback); kscan_config(config->kscan, &ksbb_inner_kscan_callback);
kscan_enable_callback(config->kscan); kscan_enable_callback(config->kscan);
#else
ksbb_pm_action(dev, PM_DEVICE_ACTION_RESUME);
} else {
pm_device_init_suspended(dev);
#endif
}
return 0; return 0;
} }
@ -141,21 +181,6 @@ static const struct kscan_driver_api ksbb_api = {
.disable_callback = ksbb_disable, .disable_callback = ksbb_disable,
}; };
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
return ksbb_disable(dev);
case PM_DEVICE_ACTION_RESUME:
return ksbb_disable(dev);
default:
return -ENOTSUP;
}
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
#define ENTRY(e) \ #define ENTRY(e) \
{ \ { \
.row = DT_PROP(e, row), .column = DT_PROP(e, column), \ .row = DT_PROP(e, row), .column = DT_PROP(e, column), \
@ -167,13 +192,14 @@ static int ksbb_pm_action(const struct device *dev, enum pm_device_action action
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ENTRY, (, ))}; \ DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ENTRY, (, ))}; \
const struct ksbb_config ksbb_config_##n = { \ const struct ksbb_config ksbb_config_##n = { \
.kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \ .kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \
.auto_enable = DT_INST_PROP_OR(n, auto_enable, false), \
.entries = entries_##n, \ .entries = entries_##n, \
.entries_len = ARRAY_SIZE(entries_##n), \ .entries_len = ARRAY_SIZE(entries_##n), \
}; \ }; \
struct ksbb_data ksbb_data_##n = {}; \ struct ksbb_data ksbb_data_##n = {}; \
PM_DEVICE_DT_INST_DEFINE(n, ksbb_pm_action); \ PM_DEVICE_DT_INST_DEFINE(n, ksbb_pm_action); \
DEVICE_DT_INST_DEFINE(n, ksbb_init, PM_DEVICE_DT_INST_GET(n), &ksbb_data_##n, \ DEVICE_DT_INST_DEFINE(n, ksbb_init, PM_DEVICE_DT_INST_GET(n), &ksbb_data_##n, \
&ksbb_config_##n, APPLICATION, \ &ksbb_config_##n, POST_KERNEL, \
CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS_INIT_PRIORITY, &ksbb_api); CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS_INIT_PRIORITY, &ksbb_api);
DT_INST_FOREACH_STATUS_OKAY(KSBB_INST) DT_INST_FOREACH_STATUS_OKAY(KSBB_INST)

View file

@ -12,17 +12,15 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix.h>
#include <zmk/kscan.h>
#include <zmk/display.h> #include <zmk/display.h>
#include <drivers/ext_power.h>
int main(void) { int main(void) {
LOG_INF("Welcome to ZMK!\n"); LOG_INF("Welcome to ZMK!\n");
if (zmk_kscan_init(DEVICE_DT_GET(ZMK_MATRIX_NODE_ID)) != 0) { #if IS_ENABLED(CONFIG_SETTINGS)
return -ENOTSUP; settings_subsys_init();
} settings_load();
#endif
#ifdef CONFIG_ZMK_DISPLAY #ifdef CONFIG_ZMK_DISPLAY
zmk_display_init(); zmk_display_init();

View file

@ -4,12 +4,23 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <zephyr/init.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zmk/matrix_transform.h> #include <zmk/matrix_transform.h>
#include <zmk/matrix.h> #include <zmk/matrix.h>
#include <dt-bindings/zmk/matrix_transform.h> #include <dt-bindings/zmk/matrix_transform.h>
#ifdef ZMK_KEYMAP_TRANSFORM_NODE #define DT_DRV_COMPAT zmk_matrix_transform
struct zmk_matrix_transform {
uint32_t const *lookup_table;
size_t len;
uint8_t rows;
uint8_t columns;
uint8_t col_offset;
uint8_t row_offset;
};
/* the transform in the device tree is a list of (row,column) pairs that is /* the transform in the device tree is a list of (row,column) pairs that is
* indexed by by the keymap position of that key. We want to invert this in * indexed by by the keymap position of that key. We want to invert this in
@ -28,38 +39,58 @@
#define INDEX_OFFSET 1 #define INDEX_OFFSET 1
#define TRANSFORM_ENTRY(i, _) \ #if DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform)
[(KT_ROW(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i)) * ZMK_MATRIX_COLS) + \
KT_COL(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i))] = i + INDEX_OFFSET
static uint32_t transform[] = {LISTIFY(ZMK_KEYMAP_LEN, TRANSFORM_ENTRY, (, ), 0)}; #define TRANSFORM_LOOKUP_ENTRY(i, n) \
[(KT_ROW(DT_INST_PROP_BY_IDX(n, map, i)) * DT_INST_PROP(n, columns)) + \
KT_COL(DT_INST_PROP_BY_IDX(n, map, i))] = i + INDEX_OFFSET
#endif #define MATRIX_TRANSFORM_INIT(n) \
static const uint32_t _CONCAT(zmk_transform_lookup_table_, n)[] = { \
LISTIFY(DT_INST_PROP_LEN(n, map), TRANSFORM_LOOKUP_ENTRY, (, ), n)}; \
const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, DT_DRV_INST(n)) = { \
.rows = DT_INST_PROP(n, rows), \
.columns = DT_INST_PROP(n, columns), \
.col_offset = DT_INST_PROP(n, col_offset), \
.row_offset = DT_INST_PROP(n, row_offset), \
.lookup_table = _CONCAT(zmk_transform_lookup_table_, n), \
.len = ARRAY_SIZE(_CONCAT(zmk_transform_lookup_table_, n)), \
};
int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column) { DT_INST_FOREACH_STATUS_OKAY(MATRIX_TRANSFORM_INIT);
#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset)
column += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset);
#endif
#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset) #elif DT_HAS_CHOSEN(zmk_kscan) && defined(ZMK_MATRIX_COLS) && defined(ZMK_MATRIX_ROWS)
row += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset);
#endif
const uint32_t matrix_index = (row * ZMK_MATRIX_COLS) + column; const struct zmk_matrix_transform zmk_matrix_transform_default = {
.rows = ZMK_MATRIX_ROWS,
#ifdef ZMK_KEYMAP_TRANSFORM_NODE .columns = ZMK_MATRIX_COLS,
if (matrix_index >= ARRAY_SIZE(transform)) { .len = ZMK_KEYMAP_LEN,
return -EINVAL; };
}
#else
const uint32_t value = transform[matrix_index];
#error "Need a matrix transform or compatible kscan selected to determine keymap size!"
if (!value) { `
return -EINVAL; #endif // DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform)
}
int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row,
return value - INDEX_OFFSET; uint32_t column) {
#else column += mt->col_offset;
return matrix_index; row += mt->row_offset;
#endif /* ZMK_KEYMAP_TRANSFORM_NODE */
if (!mt->lookup_table) {
return (row * mt->columns) + column;
}
uint16_t lookup_index = ((row * mt->columns) + column);
if (lookup_index >= mt->len) {
return -EINVAL;
}
int32_t val = mt->lookup_table[lookup_index];
if (val == 0) {
return -EINVAL;
}
return val - INDEX_OFFSET;
}; };

386
app/src/physical_layouts.c Normal file
View file

@ -0,0 +1,386 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/devicetree.h>
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/drivers/kscan.h>
#if IS_ENABLED(CONFIG_SETTINGS)
#include <zephyr/settings/settings.h>
#endif
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/physical_layouts.h>
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#define DT_DRV_COMPAT zmk_physical_layout
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#define ZKPA_INIT(i, n) \
(const struct zmk_key_physical_attrs) { \
.width = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, width), \
.height = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, height), \
.x = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, x), \
.y = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, y), \
.rx = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, rx), \
.ry = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, ry), \
.r = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, r), \
}
#define ZMK_LAYOUT_INST(n) \
static const struct zmk_key_physical_attrs const _CONCAT( \
_zmk_physical_layout_keys_, n)[DT_INST_PROP_LEN_OR(n, keys, 0)] = { \
LISTIFY(DT_INST_PROP_LEN_OR(n, keys, 0), ZKPA_INIT, (, ), n)}; \
ZMK_MATRIX_TRANSFORM_EXTERN(DT_INST_PHANDLE(n, transform)); \
static const struct zmk_physical_layout const _CONCAT(_zmk_physical_layout_, \
DT_DRV_INST(n)) = { \
.display_name = DT_INST_PROP_OR(n, display_name, "Layout #" #n), \
.matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_INST_PHANDLE(n, transform)), \
.keys = _CONCAT(_zmk_physical_layout_keys_, n), \
.keys_len = DT_INST_PROP_LEN_OR(n, keys, 0), \
.kscan = DEVICE_DT_GET(COND_CODE_1(DT_INST_PROP_LEN(n, kscan), \
(DT_INST_PHANDLE(n, kscan)), (DT_CHOSEN(zmk_kscan))))};
DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_INST)
#define POS_MAP_COMPAT zmk_physical_layout_position_map
#define HAVE_POS_MAP DT_HAS_COMPAT_STATUS_OKAY(POS_MAP_COMPAT)
#define POS_MAP_COMPLETE (HAVE_POS_MAP && DT_PROP(DT_INST(0, POS_MAP_COMPAT), complete))
#if HAVE_POS_MAP
// Using sizeof + union trick to calculate the "positions" length statically.
#define ZMK_POS_MAP_POSITIONS_ARRAY(node_id) \
uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(node_id, positions)];
#define ZMK_POS_MAP_LEN \
sizeof(union {DT_FOREACH_CHILD(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_POSITIONS_ARRAY)})
struct position_map_entry {
const struct zmk_physical_layout *layout;
const uint32_t positions[ZMK_POS_MAP_LEN];
};
#define ZMK_POS_MAP_ENTRY(node_id) \
{ \
.layout = &_CONCAT(_zmk_physical_layout_, DT_PHANDLE(node_id, physical_layout)), \
.positions = DT_PROP(node_id, positions), \
}
static const struct position_map_entry positions_maps[] = {
DT_FOREACH_CHILD_SEP(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_ENTRY, (, ))};
#endif
#define ZMK_LAYOUT_REF(n) &_CONCAT(_zmk_physical_layout_, DT_DRV_INST(n)),
static const struct zmk_physical_layout *const layouts[] = {
DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_REF)};
#elif DT_HAS_CHOSEN(zmk_matrix_transform)
ZMK_MATRIX_TRANSFORM_EXTERN(DT_CHOSEN(zmk_matrix_transform));
static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = {
.display_name = "Default",
.matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_CHOSEN(zmk_matrix_transform)),
COND_CODE_1(DT_HAS_CHOSEN(zmk_kscan), (.kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)), ), ())};
static const struct zmk_physical_layout *const layouts[] = {
&_CONCAT(_zmk_physical_layout_, chosen)};
#elif DT_HAS_CHOSEN(zmk_kscan)
ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN();
static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = {
.display_name = "Default",
.matrix_transform = &zmk_matrix_transform_default,
.kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)),
};
static const struct zmk_physical_layout *const layouts[] = {
&_CONCAT(_zmk_physical_layout_, chosen)};
#endif
const struct zmk_physical_layout *active;
size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **dest_layouts) {
*dest_layouts = &layouts[0];
return ARRAY_SIZE(layouts);
}
#define ZMK_KSCAN_EVENT_STATE_PRESSED 0
#define ZMK_KSCAN_EVENT_STATE_RELEASED 1
struct zmk_kscan_event {
uint32_t row;
uint32_t column;
uint32_t state;
};
static struct zmk_kscan_msg_processor { struct k_work work; } msg_processor;
K_MSGQ_DEFINE(physical_layouts_kscan_msgq, sizeof(struct zmk_kscan_event),
CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4);
static void zmk_physical_layout_kscan_callback(const struct device *dev, uint32_t row,
uint32_t column, bool pressed) {
if (dev != active->kscan) {
return;
}
struct zmk_kscan_event ev = {
.row = row,
.column = column,
.state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)};
k_msgq_put(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT);
k_work_submit(&msg_processor.work);
}
static void zmk_physical_layouts_kscan_process_msgq(struct k_work *item) {
struct zmk_kscan_event ev;
while (k_msgq_get(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT) == 0) {
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
int32_t position = zmk_matrix_transform_row_column_to_position(active->matrix_transform,
ev.row, ev.column);
if (position < 0) {
LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column,
(pressed ? "true" : "false"));
continue;
}
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position,
(pressed ? "true" : "false"));
raise_zmk_position_state_changed(
(struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL,
.state = pressed,
.position = position,
.timestamp = k_uptime_get()});
}
}
int zmk_physical_layouts_select_layout(const struct zmk_physical_layout *dest_layout) {
if (!dest_layout) {
return -ENODEV;
}
if (dest_layout == active) {
return 0;
}
if (active) {
if (active->kscan) {
kscan_disable_callback(active->kscan);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
pm_device_runtime_put(active->kscan);
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(active->kscan, PM_DEVICE_ACTION_SUSPEND);
#endif
}
}
active = dest_layout;
if (active->kscan) {
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
int err = pm_device_runtime_get(active->kscan);
if (err < 0) {
LOG_WRN("PM runtime get of kscan device to enable it %d", err);
return err;
}
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(active->kscan, PM_DEVICE_ACTION_RESUME);
#endif
kscan_config(active->kscan, zmk_physical_layout_kscan_callback);
kscan_enable_callback(active->kscan);
}
return 0;
}
int zmk_physical_layouts_select(uint8_t index) {
if (index >= ARRAY_SIZE(layouts)) {
return -EINVAL;
}
return zmk_physical_layouts_select_layout(layouts[index]);
}
int zmk_physical_layouts_get_selected(void) {
for (int i = 0; i < ARRAY_SIZE(layouts); i++) {
if (layouts[i] == active) {
return i;
}
}
return -ENODEV;
}
#if IS_ENABLED(CONFIG_SETTINGS)
static int8_t saved_selected_index = -1;
#endif
int zmk_physical_layouts_select_initial(void) {
const struct zmk_physical_layout *initial;
#if DT_HAS_CHOSEN(zmk_physical_layout)
initial = &_CONCAT(_zmk_physical_layout_, DT_CHOSEN(zmk_physical_layout));
#else
initial = layouts[0];
#endif
int ret = zmk_physical_layouts_select_layout(initial);
return ret;
}
int zmk_physical_layouts_check_unsaved_selection(void) {
#if IS_ENABLED(CONFIG_SETTINGS)
return saved_selected_index < 0 ||
saved_selected_index == (uint8_t)zmk_physical_layouts_get_selected()
? 0
: 1;
#else
return -ENOTSUP;
#endif
}
int zmk_physical_layouts_save_selected(void) {
#if IS_ENABLED(CONFIG_SETTINGS)
uint8_t val = (uint8_t)zmk_physical_layouts_get_selected();
return settings_save_one("physical_layouts/selected", &val, sizeof(val));
#else
return -ENOTSUP;
#endif
}
int zmk_physical_layouts_revert_selected(void) { return zmk_physical_layouts_select_initial(); }
int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map) {
if (source >= ARRAY_SIZE(layouts) || dest >= ARRAY_SIZE(layouts)) {
return -EINVAL;
}
const struct zmk_physical_layout *src_layout = layouts[source];
const struct zmk_physical_layout *dest_layout = layouts[dest];
#if HAVE_POS_MAP
const struct position_map_entry *src_pos_map = NULL;
const struct position_map_entry *dest_pos_map = NULL;
for (int pm = 0; pm < ARRAY_SIZE(positions_maps); pm++) {
if (positions_maps[pm].layout == src_layout) {
src_pos_map = &positions_maps[pm];
}
if (positions_maps[pm].layout == dest_layout) {
dest_pos_map = &positions_maps[pm];
}
}
#endif
memset(map, UINT32_MAX, dest_layout->keys_len);
for (int b = 0; b < dest_layout->keys_len; b++) {
bool found = false;
#if HAVE_POS_MAP
if (src_pos_map && dest_pos_map) {
for (int m = 0; m < ZMK_POS_MAP_LEN; m++) {
if (dest_pos_map->positions[m] == b) {
map[b] = src_pos_map->positions[m];
found = true;
break;
}
}
}
#endif
#if !POS_MAP_COMPLETE
if (!found) {
const struct zmk_key_physical_attrs *key = &dest_layout->keys[b];
for (int old_b = 0; old_b < src_layout->keys_len; old_b++) {
const struct zmk_key_physical_attrs *candidate_key = &src_layout->keys[old_b];
if (candidate_key->x == key->x && candidate_key->y == key->y) {
map[b] = old_b;
found = true;
break;
}
}
}
#endif
if (!found || map[b] >= src_layout->keys_len) {
map[b] = UINT32_MAX;
}
}
return dest_layout->keys_len;
}
#if IS_ENABLED(CONFIG_SETTINGS)
static int physical_layouts_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
const char *next;
if (settings_name_steq(name, "selected", &next) && !next) {
if (len != sizeof(saved_selected_index)) {
return -EINVAL;
}
int err = read_cb(cb_arg, &saved_selected_index, len);
if (err <= 0) {
LOG_ERR("Failed to handle selected physical dest_layout from settings (err %d)", err);
return err;
}
return zmk_physical_layouts_select(saved_selected_index);
}
return 0;
};
SETTINGS_STATIC_HANDLER_DEFINE(physical_layouts, "physical_layouts", NULL,
physical_layouts_handle_set, NULL, NULL);
#endif // IS_ENABLED(CONFIG_SETTINGS)
static int zmk_physical_layouts_init(void) {
k_work_init(&msg_processor.work, zmk_physical_layouts_kscan_process_msgq);
#if IS_ENABLED(CONFIG_PM_DEVICE)
for (int l = 0; l < ARRAY_SIZE(layouts); l++) {
const struct zmk_physical_layout *pl = layouts[l];
if (pl->kscan) {
if (pm_device_wakeup_is_capable(pl->kscan)) {
pm_device_wakeup_enable(pl->kscan, true);
}
}
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
return zmk_physical_layouts_select_initial();
}
SYS_INIT(zmk_physical_layouts_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

View file

@ -221,6 +221,10 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_
rc = read_cb(cb_arg, &state, sizeof(state)); rc = read_cb(cb_arg, &state, sizeof(state));
if (rc >= 0) { if (rc >= 0) {
if (state.on) {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
}
return 0; return 0;
} }
@ -230,7 +234,7 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_
return -ENOENT; return -ENOENT;
} }
struct settings_handler rgb_conf = {.name = "rgb/underglow", .h_set = rgb_settings_set}; SETTINGS_STATIC_HANDLER_DEFINE(rgb_underglow, "rgb/underglow", NULL, rgb_settings_set, NULL, NULL);
static void zmk_rgb_underglow_save_state_work(struct k_work *_work) { static void zmk_rgb_underglow_save_state_work(struct k_work *_work) {
settings_save_one("rgb/underglow/state", &state, sizeof(state)); settings_save_one("rgb/underglow/state", &state, sizeof(state));
@ -262,17 +266,7 @@ static int zmk_rgb_underglow_init(void) {
}; };
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
int err = settings_register(&rgb_conf);
if (err) {
LOG_ERR("Failed to register the ext_power settings handler (err %d)", err);
return err;
}
k_work_init_delayable(&underglow_save_work, zmk_rgb_underglow_save_state_work); k_work_init_delayable(&underglow_save_work, zmk_rgb_underglow_save_state_work);
settings_load_subtree("rgb/underglow");
#endif #endif
#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB)

View file

@ -76,7 +76,7 @@ if !ZMK_SPLIT_ROLE_CENTRAL
config ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE config ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE
int "BLE split peripheral notify thread stack size" int "BLE split peripheral notify thread stack size"
default 650 default 756
config ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY config ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY
int "BLE split peripheral notify thread priority" int "BLE split peripheral notify thread priority"

View file

@ -12,6 +12,7 @@
#include <zephyr/bluetooth/uuid.h> #include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h> #include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h> #include <zephyr/bluetooth/hci.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/byteorder.h> #include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
@ -865,13 +866,34 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) {
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
static int finish_init() {
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning();
}
#if IS_ENABLED(CONFIG_SETTINGS)
static int central_ble_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
return 0;
}
static struct settings_handler ble_central_settings_handler = {
.name = "ble_central", .h_set = central_ble_handle_set, .h_commit = finish_init};
#endif // IS_ENABLED(CONFIG_SETTINGS)
static int zmk_split_bt_central_init(void) { static int zmk_split_bt_central_init(void) {
k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack, k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack,
K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack), K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack),
CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL);
bt_conn_cb_register(&conn_callbacks); bt_conn_cb_register(&conn_callbacks);
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning(); #if IS_ENABLED(CONFIG_SETTINGS)
settings_register(&ble_central_settings_handler);
return 0;
#else
return finish_init();
#endif // IS_ENABLED(CONFIG_SETTINGS)
} }
SYS_INIT(zmk_split_bt_central_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); SYS_INIT(zmk_split_bt_central_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);

View file

@ -146,21 +146,7 @@ bool zmk_split_bt_peripheral_is_connected(void) { return is_connected; }
bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; } bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; }
static int zmk_peripheral_ble_init(void) { static int zmk_peripheral_ble_complete_startup(void) {
int err = bt_enable(NULL);
if (err) {
LOG_ERR("BLUETOOTH FAILED (%d)", err);
return err;
}
#if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
settings_load_subtree("ble");
settings_load_subtree("bt");
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
LOG_WRN("Clearing all existing BLE bond information from the keyboard"); LOG_WRN("Clearing all existing BLE bond information from the keyboard");
@ -176,4 +162,35 @@ static int zmk_peripheral_ble_init(void) {
return 0; return 0;
} }
#if IS_ENABLED(CONFIG_SETTINGS)
static int peripheral_ble_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
return 0;
}
static struct settings_handler ble_peripheral_settings_handler = {
.name = "ble_peripheral",
.h_set = peripheral_ble_handle_set,
.h_commit = zmk_peripheral_ble_complete_startup};
#endif // IS_ENABLED(CONFIG_SETTINGS)
static int zmk_peripheral_ble_init(void) {
int err = bt_enable(NULL);
if (err) {
LOG_ERR("BLUETOOTH FAILED (%d)", err);
return err;
}
#if IS_ENABLED(CONFIG_SETTINGS)
settings_register(&ble_peripheral_settings_handler);
#else
zmk_peripheral_ble_complete_startup();
#endif
return 0;
}
SYS_INIT(zmk_peripheral_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); SYS_INIT(zmk_peripheral_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);

View file

@ -19,7 +19,8 @@ The high level steps are:
- Create a new shield directory. - Create a new shield directory.
- Add the base Kconfig files. - Add the base Kconfig files.
- Add the shield overlay file to define the KSCAN driver for detecting key press/release. - Add the shield overlay file to define the KSCAN driver for detecting key press/release.
- (Optional) Add the matrix transform for mapping KSCAN row/column values to sane key positions. This is needed for non-rectangular keyboards, or where the underlying row/column pin arrangement does not map one to one with logical locations on the keyboard. - Add the matrix transform for mapping KSCAN row/column values to key positions in the keymap.
- Add a physical layout definition to select the matrix transform and KSCAN instance.
- Add a default keymap, which users can override in their own configs as needed. - Add a default keymap, which users can override in their own configs as needed.
- Add a `<my_shield>.zmk.yml` metadata file to document the high level details of your shield, and the features it supports. - Add a `<my_shield>.zmk.yml` metadata file to document the high level details of your shield, and the features it supports.
- Update the `build.yaml` file from the repository template to have some sample builds of the firmware to test. - Update the `build.yaml` file from the repository template to have some sample builds of the firmware to test.
@ -318,7 +319,7 @@ The shared configuration in `my_awesome_split_board.conf` is only applied when y
</TabItem> </TabItem>
</Tabs> </Tabs>
## (Optional) Matrix Transform ## Matrix Transform
Internally ZMK translates all row/column events into "key position" events to maintain a consistent model that works no matter what any possible GPIO matrix may look like for a certain keyboard. This is particularly helpful when: Internally ZMK translates all row/column events into "key position" events to maintain a consistent model that works no matter what any possible GPIO matrix may look like for a certain keyboard. This is particularly helpful when:
@ -328,15 +329,7 @@ Internally ZMK translates all row/column events into "key position" events to ma
A "key position" is the numeric index (zero-based) of a given key, which identifies A "key position" is the numeric index (zero-based) of a given key, which identifies
the logical key location as perceived by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values. the logical key location as perceived by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values.
_Without_ a matrix transform, that intentionally map each key position to the row/column pair that position corresponds to, the default equation to determine that is: The `<shield_name>.overlay` must include a matrix transform that defines this mapping from row/column values to key positions.
```c
($row * NUMBER_OF_COLUMNS) + $column
```
Which effectively amounts to numbering the key positions by traversing each row from top to bottom and assigning numerically incrementing key positions.
Whenever that default key position mapping is insufficient, the `<shield_name>.overlay` file should _also_ include a matrix transform.
Here is an example for the [nice60](https://github.com/Nicell/nice60), which uses an efficient 8x8 GPIO matrix, and uses a transform: Here is an example for the [nice60](https://github.com/Nicell/nice60), which uses an efficient 8x8 GPIO matrix, and uses a transform:
@ -344,11 +337,6 @@ Here is an example for the [nice60](https://github.com/Nicell/nice60), which use
#include <dt-bindings/zmk/matrix_transform.h> #include <dt-bindings/zmk/matrix_transform.h>
/ { / {
chosen {
zmk,kscan = &kscan0;
zmk,matrix-transform = &default_transform;
};
/* define kscan node with label `kscan0`... */ /* define kscan node with label `kscan0`... */
default_transform: keymap_transform_0 { default_transform: keymap_transform_0 {
@ -377,10 +365,58 @@ Some important things to note:
- The `#include <dt-bindings/zmk/matrix_transform.h>` is critical. The `RC` macro is used to generate the internal storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK. - The `#include <dt-bindings/zmk/matrix_transform.h>` is critical. The `RC` macro is used to generate the internal storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK.
- `RC(row, column)` is placed sequentially to define what row and column values that position corresponds to. - `RC(row, column)` is placed sequentially to define what row and column values that position corresponds to.
- If you have a keyboard with options for `2u` keys in certain positions, or break away portions, it is a good idea to set the chosen `zmk,matrix-transform` to the default arrangement, and include _other_ possible matrix transform nodes in the devicetree that users can select in their user config by overriding the chosen node. - If you have a keyboard with options for `2u` keys in certain positions, ANSI vs. ISO layouts, or break away portions, define one matrix transform for each possible arrangement to be used in the physical layouts. This will allow the users to select the right layout in their keymap files.
See the [matrix transform section](../config/kscan.md#matrix-transform) in the Keyboard Scan configuration documentation for details and more examples of matrix transforms. See the [matrix transform section](../config/kscan.md#matrix-transform) in the Keyboard Scan configuration documentation for details and more examples of matrix transforms.
## Physical Layout
The physical layout is the top level entity that aggregates all details about a certain possible layout for a keyboard: the matrix transform that defines the set of key positions and what row/column they correspond to, what kscan driver is used for that layout, etc.
For keyboards that support multiple layouts, setting a `chosen` node to a defined physical layout in your keymap will allow selecting the specific layout that you've built.
A physical layout is very basic, e.g.:
```
/ {
default_layout: default_layout {
compatible = "zmk,physical-layout";
transform = <&default_transform>;
kscan = <&kscan0>;
};
};
```
When supporting multiple layouts, define the multiple layout nodes and then set a `chosen` for the default:
```
/ {
chosen {
zmk,physical-layout = &default_layout;
...
};
default_layout: default_layout {
compatible = "zmk,physical-layout";
transform = <&default_transform>;
kscan = <&kscan0>;
};
alt_layout: alt_layout {
compatible = "zmk,physical-layout";
transform = <&alt_transform>;
kscan = <&alt_kscan0>;
};
};
```
This way, users can select a different layout by overriding the `zmk,physical-layout` chosen node in their keymap file.
:::note
Some keyboards use different GPIO pins for different layouts, and need different kscan nodes created for each layout.
However, if all of your physical layouts use the same `kscan` node under the hood, you can skip setting the `kscan` property on each
layout and instead assign the `zmk,kscan` chosen node to your single kscan instance.
:::
## Default Keymap ## Default Keymap
Each keyboard should provide a default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `boards/shields/<shield_name>/<shield_name>.keymap` file. The keymap is configured as an additional devicetree overlay that includes the following: Each keyboard should provide a default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `boards/shields/<shield_name>/<shield_name>.keymap` file. The keymap is configured as an additional devicetree overlay that includes the following:

View file

@ -121,6 +121,7 @@ With that in place, the kscan sideband behavior will wrap the new driver:
compatible = "zmk,kscan-sideband-behaviors"; compatible = "zmk,kscan-sideband-behaviors";
kscan = <&soft_off_direct_scan>; kscan = <&soft_off_direct_scan>;
auto-enable;
wakeup-source; wakeup-source;
soft_off { soft_off {