aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorPeter Johanson <[email protected]>2023-12-30 16:38:52 -0800
committerPete Johanson <[email protected]>2024-03-27 20:59:26 -0700
commita0ad1d4c9402fbfe5e3dee8996057150a3f0f209 (patch)
tree3901efbadebb89f7bf141354c8377ac1892c6bf8
parente78b25a445b5bfc4b0c9b979d68964729c3e8fa1 (diff)
downloadzmk-a0ad1d4c9402fbfe5e3dee8996057150a3f0f209.tar.gz
zmk-a0ad1d4c9402fbfe5e3dee8996057150a3f0f209.zip
refactor: Add kscan sideband behavior driver
* Instead of gpio key behavior trigger, add new kscan driver that decorates/wraps a given kscan driver and will invoke basic system behavior assigned to a given row + column, without the need for keymap mapping in the matrix transform, bypassing keymaps entirely.
-rw-r--r--app/CMakeLists.txt2
-rw-r--r--app/Kconfig6
-rw-r--r--app/Kconfig.behaviors5
-rw-r--r--app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay19
-rw-r--r--app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml29
-rw-r--r--app/dts/bindings/zmk,gpio-key-behavior-trigger.yaml31
-rw-r--r--app/src/gpio_key_behavior_trigger.c167
-rw-r--r--app/src/kscan_sideband_behaviors.c142
8 files changed, 192 insertions, 209 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 4ee9135d23..2abf943fb3 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -25,11 +25,11 @@ target_sources(app PRIVATE src/stdlib.c)
target_sources(app PRIVATE src/activity.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(app PRIVATE src/matrix_transform.c)
target_sources(app PRIVATE src/sensors.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c)
target_sources(app PRIVATE src/event_manager.c)
-target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_BEHAVIOR_TRIGGER app PRIVATE src/gpio_key_behavior_trigger.c)
target_sources_ifdef(CONFIG_ZMK_PM app PRIVATE src/pm.c)
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c)
target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key_wakeup_trigger.c)
diff --git a/app/Kconfig b/app/Kconfig
index df84d97d25..15c31375d3 100644
--- a/app/Kconfig
+++ b/app/Kconfig
@@ -512,6 +512,12 @@ config ZMK_KSCAN_EVENT_QUEUE_SIZE
endif # ZMK_KSCAN
+config ZMK_KSCAN_SIDEBAND_BEHAVIORS
+ bool
+ default y
+ depends on DT_HAS_ZMK_KSCAN_SIDEBAND_BEHAVIORS_ENABLED
+ select KSCAN
+
menu "Logging"
config ZMK_LOGGING_MINIMAL
diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors
index 6abdbddd77..c9754bf7d8 100644
--- a/app/Kconfig.behaviors
+++ b/app/Kconfig.behaviors
@@ -1,11 +1,6 @@
# Copyright (c) 2023 The ZMK Contributors
# SPDX-License-Identifier: MIT
-config ZMK_GPIO_KEY_BEHAVIOR_TRIGGER
- bool
- default y
- depends on DT_HAS_ZMK_GPIO_KEY_BEHAVIOR_TRIGGER_ENABLED
-
config ZMK_BEHAVIOR_KEY_TOGGLE
bool
default y
diff --git a/app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay b/app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay
index b068b431e2..47b67dc002 100644
--- a/app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay
+++ b/app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay
@@ -57,10 +57,19 @@ encoder: &qdec0 {
wakeup-sources = <&wakeup_source>;
};
- soft_off_behavior_key {
- compatible = "zmk,gpio-key-behavior-trigger";
- status = "okay";
- bindings = <&soft_off>;
- key = <&button0>;
+ soft_off_direct_kscan: soft_off_direct_kscan {
+ compatible = "zmk,kscan-gpio-direct";
+ input-keys = <&button0>;
};
+
+ soft_off_sideband_behaviors {
+ compatible = "zmk,kscan-sideband-behaviors";
+ kscan = <&soft_off_direct_kscan>;
+ soft_off {
+ row = <0>;
+ column = <0>;
+ bindings = <&soft_off>;
+ };
+ };
+
}; \ No newline at end of file
diff --git a/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml
new file mode 100644
index 0000000000..7289b9e169
--- /dev/null
+++ b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml
@@ -0,0 +1,29 @@
+# Copyright (c) 2023, The ZMK Contributors
+# SPDX-License-Identifier: MIT
+
+description: |
+ kscan sideband behavior runner. Only basic system behavior should be used,
+ since no keymap processing occurs when using them.
+
+compatible: "zmk,kscan-sideband-behaviors"
+
+include: [kscan.yaml]
+
+properties:
+ kscan:
+ type: phandle
+ required: true
+
+child-binding:
+ description: "A sideband behavior tied to a row/column pair"
+
+ properties:
+ row:
+ type: int
+ required: true
+ column:
+ type: int
+ required: true
+ bindings:
+ type: phandle-array
+ required: true
diff --git a/app/dts/bindings/zmk,gpio-key-behavior-trigger.yaml b/app/dts/bindings/zmk,gpio-key-behavior-trigger.yaml
deleted file mode 100644
index 2a1387f022..0000000000
--- a/app/dts/bindings/zmk,gpio-key-behavior-trigger.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (c) 2023 The ZMK Contributors
-# SPDX-License-Identifier: MIT
-
-description: |
- Driver for a dedicated key for invoking a connected behavior.
-
-compatible: "zmk,gpio-key-behavior-trigger"
-
-include: base.yaml
-
-properties:
- key:
- type: phandle
- required: true
- description: The GPIO key that triggers wake via interrupt
- bindings:
- type: phandle
- required: true
- description: The behavior to invoke when the GPIO key is pressed
- debounce-press-ms:
- type: int
- default: 5
- description: Debounce time for key press in milliseconds. Use 0 for eager debouncing.
- debounce-release-ms:
- type: int
- default: 5
- description: Debounce time for key release in milliseconds.
- debounce-scan-period-ms:
- type: int
- default: 1
- description: Time between reads in milliseconds when any key is pressed.
diff --git a/app/src/gpio_key_behavior_trigger.c b/app/src/gpio_key_behavior_trigger.c
deleted file mode 100644
index a72f8e4892..0000000000
--- a/app/src/gpio_key_behavior_trigger.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (c) 2023 The ZMK Contributors
- *
- * SPDX-License-Identifier: MIT
- */
-
-#define DT_DRV_COMPAT zmk_gpio_key_behavior_trigger
-
-#include <zephyr/device.h>
-#include <drivers/behavior.h>
-#include <zephyr/drivers/gpio.h>
-#include <zephyr/logging/log.h>
-#include <zephyr/pm/device.h>
-
-#include <zmk/event_manager.h>
-#include <zmk/behavior.h>
-#include <zmk/debounce.h>
-#include <zmk/keymap.h>
-
-LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
-
-struct gkbt_config {
- struct zmk_debounce_config debounce_config;
- int32_t debounce_scan_period_ms;
- struct gpio_dt_spec key;
-};
-
-struct gkbt_data {
- struct zmk_behavior_binding binding;
- struct zmk_debounce_state debounce_state;
- struct gpio_callback key_callback;
- const struct device *dev;
- struct k_work_delayable update_work;
- uint32_t read_time;
-};
-
-static void gkbt_enable_interrupt(const struct device *dev) {
- const struct gkbt_config *config = dev->config;
-
- gpio_pin_interrupt_configure_dt(&config->key, GPIO_INT_LEVEL_ACTIVE);
-}
-
-static void gkbt_disable_interrupt(const struct device *dev) {
- const struct gkbt_config *config = dev->config;
-
- gpio_pin_interrupt_configure_dt(&config->key, GPIO_INT_DISABLE);
-}
-
-static void gkbt_read(const struct device *dev) {
- const struct gkbt_config *config = dev->config;
- struct gkbt_data *data = dev->data;
-
- zmk_debounce_update(&data->debounce_state, gpio_pin_get_dt(&config->key),
- config->debounce_scan_period_ms, &config->debounce_config);
-
- if (zmk_debounce_get_changed(&data->debounce_state)) {
- const bool pressed = zmk_debounce_is_pressed(&data->debounce_state);
-
- struct zmk_behavior_binding_event event = {.position = INT32_MAX,
- .timestamp = k_uptime_get()};
-
- if (pressed) {
- behavior_keymap_binding_pressed(&data->binding, event);
- } else {
- behavior_keymap_binding_released(&data->binding, event);
- }
- }
-
- if (zmk_debounce_is_active(&data->debounce_state)) {
- data->read_time += config->debounce_scan_period_ms;
-
- k_work_reschedule(&data->update_work, K_TIMEOUT_ABS_MS(data->read_time));
- } else {
- gkbt_enable_interrupt(dev);
- }
-}
-
-static void gkbt_update_work(struct k_work *work) {
- struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
- struct gkbt_data *data = CONTAINER_OF(dwork, struct gkbt_data, update_work);
- gkbt_read(data->dev);
-}
-
-static void gkbt_gpio_irq_callback(const struct device *port, struct gpio_callback *cb,
- const gpio_port_pins_t pin) {
- struct gkbt_data *data = CONTAINER_OF(cb, struct gkbt_data, key_callback);
-
- gkbt_disable_interrupt(data->dev);
-
- data->read_time = k_uptime_get();
- k_work_reschedule(&data->update_work, K_NO_WAIT);
-}
-
-static void gkbt_wait_for_key_release(const struct device *dev) {
- const struct gkbt_config *config = dev->config;
-
- while (gpio_pin_get_dt(&config->key)) {
- k_sleep(K_MSEC(100));
- }
-}
-
-static int gkbt_init(const struct device *dev) {
- const struct gkbt_config *config = dev->config;
- struct gkbt_data *data = dev->data;
-
- if (!device_is_ready(config->key.port)) {
- LOG_ERR("GPIO port %s is not ready", config->key.port->name);
- return -ENODEV;
- }
-
- k_work_init_delayable(&data->update_work, gkbt_update_work);
- data->dev = dev;
-
- gpio_pin_configure_dt(&config->key, GPIO_INPUT);
- gpio_init_callback(&data->key_callback, gkbt_gpio_irq_callback, BIT(config->key.pin));
- gpio_add_callback(config->key.port, &data->key_callback);
-
- // Be sure our wakeup key is released before startup continues to avoid wake/sleep loop.
- gkbt_wait_for_key_release(dev);
-
- gkbt_enable_interrupt(dev);
-
- return 0;
-}
-
-static int gkbt_pm_action(const struct device *dev, enum pm_device_action action) {
- const struct gkbt_config *config = dev->config;
- struct gkbt_data *data = dev->data;
-
- int ret;
-
- switch (action) {
- case PM_DEVICE_ACTION_SUSPEND:
- gkbt_disable_interrupt(dev);
- ret = gpio_remove_callback(config->key.port, &data->key_callback);
- break;
- case PM_DEVICE_ACTION_RESUME:
- ret = gpio_add_callback(config->key.port, &data->key_callback);
- gkbt_enable_interrupt(dev);
- break;
- default:
- ret = -ENOTSUP;
- break;
- }
-
- return ret;
-}
-
-#define GKBT_INST(n) \
- const struct gkbt_config gkbt_config_##n = { \
- .key = GPIO_DT_SPEC_GET(DT_INST_PHANDLE(n, key), gpios), \
- .debounce_config = \
- { \
- .debounce_press_ms = DT_INST_PROP(n, debounce_press_ms), \
- .debounce_release_ms = DT_INST_PROP(n, debounce_release_ms), \
- }, \
- .debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
- }; \
- struct gkbt_data gkbt_data_##n = { \
- .binding = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)), \
- }; \
- PM_DEVICE_DT_INST_DEFINE(n, gkbt_pm_action); \
- DEVICE_DT_INST_DEFINE(n, gkbt_init, PM_DEVICE_DT_INST_GET(n), &gkbt_data_##n, \
- &gkbt_config_##n, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
- NULL);
-
-DT_INST_FOREACH_STATUS_OKAY(GKBT_INST)
diff --git a/app/src/kscan_sideband_behaviors.c b/app/src/kscan_sideband_behaviors.c
new file mode 100644
index 0000000000..3a03a29383
--- /dev/null
+++ b/app/src/kscan_sideband_behaviors.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2023 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_kscan_sideband_behaviors
+
+#include <zephyr/device.h>
+#include <drivers/behavior.h>
+#include <zephyr/drivers/kscan.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+
+#include <zmk/event_manager.h>
+#include <zmk/behavior.h>
+#include <zmk/keymap.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+struct ksbb_entry {
+ uint8_t row;
+ uint8_t column;
+ struct zmk_behavior_binding binding;
+};
+
+struct ksbb_config {
+ const struct device *kscan;
+ struct ksbb_entry *entries;
+ size_t entries_len;
+};
+
+struct ksbb_data {
+ kscan_callback_t callback;
+ bool enabled;
+};
+
+#define GET_KSBB_DEV(n) DEVICE_DT_GET(DT_DRV_INST(n)),
+
+// The kscan callback has no context with it, so we keep a static array of all possible
+// KSBBs to check when a kscan callback from the "wrapped" inner kscan fires.
+static const struct device *ksbbs[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)] = {
+ DT_INST_FOREACH_STATUS_OKAY(GET_KSBB_DEV)};
+
+void ksbb_inner_kscan_callback(const struct device *dev, uint32_t row, uint32_t column,
+ bool pressed) {
+ for (int i = 0; i < ARRAY_SIZE(ksbbs); i++) {
+ const struct device *ksbb = ksbbs[i];
+ const struct ksbb_config *cfg = ksbb->config;
+ struct ksbb_data *data = ksbb->data;
+
+ if (cfg->kscan != dev) {
+ continue;
+ }
+
+ for (int e = 0; e < cfg->entries_len; e++) {
+ struct ksbb_entry *entry = &cfg->entries[e];
+ if (entry->row == row && entry->column == column) {
+ struct zmk_behavior_binding_event event = {.position = INT32_MAX,
+ .timestamp = k_uptime_get()};
+
+ if (pressed) {
+ behavior_keymap_binding_pressed(&entry->binding, event);
+ } else {
+ behavior_keymap_binding_released(&entry->binding, event);
+ }
+ return;
+ }
+ }
+
+ if (data->enabled && data->callback) {
+ data->callback(ksbb, row, column, pressed);
+ }
+ }
+}
+
+static int ksbb_configure(const struct device *dev, kscan_callback_t callback) {
+ struct ksbb_data *data = dev->data;
+
+ if (!callback) {
+ return -EINVAL;
+ }
+
+ data->callback = callback;
+ return 0;
+}
+
+static int ksbb_enable(const struct device *dev) {
+ struct ksbb_data *data = dev->data;
+ data->enabled = true;
+
+ return 0;
+}
+
+static int ksbb_disable(const struct device *dev) {
+ struct ksbb_data *data = dev->data;
+ data->enabled = false;
+
+ return 0;
+}
+
+static int ksbb_init(const struct device *dev) {
+ const struct ksbb_config *config = dev->config;
+
+ if (!device_is_ready(config->kscan)) {
+ LOG_ERR("kscan %s is not ready", config->kscan->name);
+ return -ENODEV;
+ }
+
+ kscan_config(config->kscan, &ksbb_inner_kscan_callback);
+ kscan_enable_callback(config->kscan);
+
+ return 0;
+}
+
+static const struct kscan_driver_api ksbb_api = {
+ .config = ksbb_configure,
+ .enable_callback = ksbb_enable,
+ .disable_callback = ksbb_disable,
+};
+
+#define JUST_ONE(_id) 1
+
+#define ENTRY(e) \
+ { \
+ .row = DT_PROP(e, row), .column = DT_PROP(e, column), \
+ .binding = ZMK_KEYMAP_EXTRACT_BINDING(0, e), \
+ }
+
+#define KSBB_INST(n) \
+ static struct ksbb_entry entries_##n[] = { \
+ DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ENTRY, (, ))}; \
+ const struct ksbb_config ksbb_config_##n = { \
+ .kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \
+ .entries = entries_##n, \
+ .entries_len = DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, JUST_ONE, (+)), \
+ }; \
+ struct ksbb_data ksbb_data_##n = {}; \
+ DEVICE_DT_INST_DEFINE(n, ksbb_init, NULL, &ksbb_data_##n, &ksbb_config_##n, APPLICATION, \
+ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ksbb_api);
+
+DT_INST_FOREACH_STATUS_OKAY(KSBB_INST)