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.
This commit is contained in:
parent
e78b25a445
commit
a0ad1d4c94
8 changed files with 192 additions and 209 deletions
|
@ -25,11 +25,11 @@ 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(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/matrix_transform.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)
|
||||||
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_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_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)
|
target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key_wakeup_trigger.c)
|
||||||
|
|
|
@ -512,6 +512,12 @@ config ZMK_KSCAN_EVENT_QUEUE_SIZE
|
||||||
|
|
||||||
endif # ZMK_KSCAN
|
endif # ZMK_KSCAN
|
||||||
|
|
||||||
|
config ZMK_KSCAN_SIDEBAND_BEHAVIORS
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
depends on DT_HAS_ZMK_KSCAN_SIDEBAND_BEHAVIORS_ENABLED
|
||||||
|
select KSCAN
|
||||||
|
|
||||||
menu "Logging"
|
menu "Logging"
|
||||||
|
|
||||||
config ZMK_LOGGING_MINIMAL
|
config ZMK_LOGGING_MINIMAL
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
# Copyright (c) 2023 The ZMK Contributors
|
# Copyright (c) 2023 The ZMK Contributors
|
||||||
# SPDX-License-Identifier: MIT
|
# 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
|
config ZMK_BEHAVIOR_KEY_TOGGLE
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
|
|
@ -57,10 +57,19 @@ encoder: &qdec0 {
|
||||||
wakeup-sources = <&wakeup_source>;
|
wakeup-sources = <&wakeup_source>;
|
||||||
};
|
};
|
||||||
|
|
||||||
soft_off_behavior_key {
|
soft_off_direct_kscan: soft_off_direct_kscan {
|
||||||
compatible = "zmk,gpio-key-behavior-trigger";
|
compatible = "zmk,kscan-gpio-direct";
|
||||||
status = "okay";
|
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>;
|
bindings = <&soft_off>;
|
||||||
key = <&button0>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
};
|
29
app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml
Normal file
29
app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml
Normal file
|
@ -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
|
|
@ -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.
|
|
|
@ -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)
|
|
142
app/src/kscan_sideband_behaviors.c
Normal file
142
app/src/kscan_sideband_behaviors.c
Normal file
|
@ -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)
|
Loading…
Add table
Reference in a new issue