Merge 9486e96bda
into eaeea4bdfa
This commit is contained in:
commit
8db8220cc4
5 changed files with 302 additions and 0 deletions
|
@ -11,3 +11,4 @@ zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_DEGHOST_DRIVER kscan_deghost.c)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
|
DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
|
||||||
|
DT_COMPAT_ZMK_KSCAN_DEGHOST := zmk,kscan-deghost
|
||||||
DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux
|
DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux
|
||||||
DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct
|
DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct
|
||||||
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
|
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
|
||||||
|
@ -11,6 +12,10 @@ config ZMK_KSCAN_COMPOSITE_DRIVER
|
||||||
bool
|
bool
|
||||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_COMPOSITE))
|
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_COMPOSITE))
|
||||||
|
|
||||||
|
config ZMK_KSCAN_DEGHOST_DRIVER
|
||||||
|
bool
|
||||||
|
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_DEGHOST))
|
||||||
|
|
||||||
config ZMK_KSCAN_GPIO_DRIVER
|
config ZMK_KSCAN_GPIO_DRIVER
|
||||||
bool
|
bool
|
||||||
select GPIO
|
select GPIO
|
||||||
|
|
187
app/drivers/kscan/kscan_deghost.c
Normal file
187
app/drivers/kscan/kscan_deghost.c
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_kscan_deghost
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/kscan.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <dt-bindings/zmk/matrix_transform.h>
|
||||||
|
#include <zmk/matrix.h>
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
struct kscan_deghost_config {
|
||||||
|
const struct device *child_kscan;
|
||||||
|
kscan_callback_t callback_for_child;
|
||||||
|
int rows;
|
||||||
|
int cols;
|
||||||
|
const uint8_t *transform_filled;
|
||||||
|
int transform_filled_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct kscan_deghost_data {
|
||||||
|
kscan_callback_t callback;
|
||||||
|
uint8_t *key_status;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int kscan_deghost_enable_callback(const struct device *dev) {
|
||||||
|
const struct kscan_deghost_config *config = dev->config;
|
||||||
|
return kscan_enable_callback(config->child_kscan);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kscan_deghost_disable_callback(const struct device *dev) {
|
||||||
|
const struct kscan_deghost_config *config = dev->config;
|
||||||
|
return kscan_disable_callback(config->child_kscan);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kscan_deghost_configure(const struct device *dev, kscan_callback_t callback) {
|
||||||
|
const struct kscan_deghost_config *config = dev->config;
|
||||||
|
struct kscan_deghost_data *data = dev->data;
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->callback = callback;
|
||||||
|
return kscan_config(config->child_kscan, config->callback_for_child);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define KEY_STATUS_FORCE_NOT_RECHECK (1 << 2)
|
||||||
|
#define KEY_STATUS_REPORTED_AS_PRESSED_MASK (1 << 1)
|
||||||
|
#define KEY_STATUS_SEEN_AS_PRESSED_MASK 1
|
||||||
|
#define CNT_KEY_STATUS_SEEN_AS_PRESSED(value) ((value)&1)
|
||||||
|
#define WAS_KEY_STATUS_GHOSTING_BEFORE(value) ((value) == 1)
|
||||||
|
#define KEY(row, col) data->key_status[(row)*config->cols + (col)]
|
||||||
|
#define EXISTS(row, col) \
|
||||||
|
((config->transform_filled_len == 0) \
|
||||||
|
? 1 \
|
||||||
|
: ((config->transform_filled_len <= (row)*config->cols + (col)) \
|
||||||
|
? 0 \
|
||||||
|
: (config->transform_filled[(row)*config->cols + (col)])))
|
||||||
|
|
||||||
|
static void kscan_deghost_callback_for_child(const struct device *deghost_dev, uint32_t row,
|
||||||
|
uint32_t col, bool pressed) {
|
||||||
|
const struct kscan_deghost_config *config = deghost_dev->config;
|
||||||
|
struct kscan_deghost_data *data = deghost_dev->data;
|
||||||
|
if (!EXISTS(row, col))
|
||||||
|
return;
|
||||||
|
if (pressed) {
|
||||||
|
KEY(row, col) |= KEY_STATUS_SEEN_AS_PRESSED_MASK;
|
||||||
|
bool ghosting = false;
|
||||||
|
for (uint32_t orow = 0; (orow < config->rows) && !ghosting; orow++) {
|
||||||
|
if (orow == row || !EXISTS(orow, col))
|
||||||
|
continue;
|
||||||
|
const int other_row_pressed = CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(orow, col));
|
||||||
|
for (uint32_t ocol = 0; ocol < config->cols; ocol++) {
|
||||||
|
if (ocol == col || (!EXISTS(row, ocol)) || (!EXISTS(orow, ocol)))
|
||||||
|
continue;
|
||||||
|
const int pressed_in_rectangle = 1 + other_row_pressed +
|
||||||
|
CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(row, ocol)) +
|
||||||
|
CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(orow, ocol));
|
||||||
|
if (pressed_in_rectangle > 2) {
|
||||||
|
ghosting = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ghosting) {
|
||||||
|
KEY(row, col) |= KEY_STATUS_REPORTED_AS_PRESSED_MASK;
|
||||||
|
data->callback(deghost_dev, row, col, pressed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (KEY(row, col) & KEY_STATUS_REPORTED_AS_PRESSED_MASK) {
|
||||||
|
KEY(row, col) = 0;
|
||||||
|
data->callback(deghost_dev, row, col, pressed);
|
||||||
|
} else {
|
||||||
|
KEY(row, col) = 0;
|
||||||
|
}
|
||||||
|
for (uint32_t orow = 0; (orow < config->rows); orow++) {
|
||||||
|
if (orow == row || !EXISTS(orow, col))
|
||||||
|
continue;
|
||||||
|
const int other_row_pressed = CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(orow, col));
|
||||||
|
int check_orow_col = WAS_KEY_STATUS_GHOSTING_BEFORE(KEY(orow, col));
|
||||||
|
for (uint32_t ocol = 0; ocol < config->cols; ocol++) {
|
||||||
|
if (ocol == col || (!EXISTS(row, ocol)) || (!EXISTS(orow, ocol)))
|
||||||
|
continue;
|
||||||
|
const int pressed_in_rectangle = 0 + other_row_pressed +
|
||||||
|
CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(row, ocol)) +
|
||||||
|
CNT_KEY_STATUS_SEEN_AS_PRESSED(KEY(orow, ocol));
|
||||||
|
if (pressed_in_rectangle == 2) {
|
||||||
|
if (WAS_KEY_STATUS_GHOSTING_BEFORE(KEY(row, ocol))) {
|
||||||
|
kscan_deghost_callback_for_child(deghost_dev, row, ocol, true);
|
||||||
|
KEY(row, ocol) |=
|
||||||
|
KEY_STATUS_FORCE_NOT_RECHECK; // optimization, next
|
||||||
|
// WAS_KEY_STATUS_GHOSTING_BEFORE()
|
||||||
|
// call will be false
|
||||||
|
}
|
||||||
|
if (WAS_KEY_STATUS_GHOSTING_BEFORE(KEY(orow, ocol))) {
|
||||||
|
kscan_deghost_callback_for_child(deghost_dev, orow, ocol, true);
|
||||||
|
}
|
||||||
|
if (check_orow_col) {
|
||||||
|
kscan_deghost_callback_for_child(deghost_dev, orow, col, true);
|
||||||
|
check_orow_col = 0; // for optimization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t ocol = 0; ocol < config->cols; ocol++) {
|
||||||
|
KEY(row, ocol) &= ~KEY_STATUS_FORCE_NOT_RECHECK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kscan_deghost_init(const struct device *dev) { return 0; }
|
||||||
|
|
||||||
|
static const struct kscan_driver_api deghost_driver_api = {
|
||||||
|
.config = kscan_deghost_configure,
|
||||||
|
.enable_callback = kscan_deghost_enable_callback,
|
||||||
|
.disable_callback = kscan_deghost_disable_callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define _TRANSFORM_ENTRY(i, transform_node) \
|
||||||
|
[(KT_ROW(DT_PROP_BY_IDX(transform_node, map, i)) * DT_PROP(transform_node, columns)) + \
|
||||||
|
KT_COL(DT_PROP_BY_IDX(transform_node, map, i))] = 1
|
||||||
|
|
||||||
|
#define KSCAN_DEGHOST_INIT(n) \
|
||||||
|
static const uint8_t transform_filled_##n[] = { \
|
||||||
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, transform), \
|
||||||
|
(LISTIFY(DT_PROP_LEN(DT_INST_PHANDLE(n, transform), map), _TRANSFORM_ENTRY, \
|
||||||
|
(, ), DT_INST_PHANDLE(n, transform))), \
|
||||||
|
())}; \
|
||||||
|
static void kscan_deghost_callback_for_child_##n(const struct device *child_dev, uint32_t row, \
|
||||||
|
uint32_t column, bool pressed) { \
|
||||||
|
const struct device *deghost_dev = DEVICE_DT_GET(DT_DRV_INST(n)); \
|
||||||
|
kscan_deghost_callback_for_child(deghost_dev, row, column, pressed); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
static uint8_t \
|
||||||
|
key_status_##n[DT_INST_PROP_OR( \
|
||||||
|
n, rows, \
|
||||||
|
DT_PROP_OR(DT_INST_PHANDLE(n, transform), rows, \
|
||||||
|
DT_PROP_LEN_OR(DT_INST_PHANDLE(n, kscan), row_gpios, ZMK_MATRIX_ROWS)))] \
|
||||||
|
[DT_INST_PROP_OR(n, columns, \
|
||||||
|
DT_PROP_OR(DT_INST_PHANDLE(n, transform), columns, \
|
||||||
|
DT_PROP_LEN_OR(DT_INST_PHANDLE(n, kscan), \
|
||||||
|
col_gpios, ZMK_MATRIX_COLS)))]; \
|
||||||
|
\
|
||||||
|
static const struct kscan_deghost_config kscan_deghost_config_##n = { \
|
||||||
|
.child_kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \
|
||||||
|
.callback_for_child = &kscan_deghost_callback_for_child_##n, \
|
||||||
|
.rows = ARRAY_SIZE(key_status_##n), \
|
||||||
|
.cols = ARRAY_SIZE(key_status_##n[0]), \
|
||||||
|
.transform_filled = transform_filled_##n, \
|
||||||
|
.transform_filled_len = ARRAY_SIZE(transform_filled_##n), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static struct kscan_deghost_data kscan_deghost_data_##n = { \
|
||||||
|
.key_status = &key_status_##n[0][0], \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(n, kscan_deghost_init, NULL, &kscan_deghost_data_##n, \
|
||||||
|
&kscan_deghost_config_##n, APPLICATION, \
|
||||||
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, °host_driver_api);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(KSCAN_DEGHOST_INIT);
|
15
app/dts/bindings/zmk,kscan-deghost.yaml
Normal file
15
app/dts/bindings/zmk,kscan-deghost.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
description: |
|
||||||
|
Allows deghosting a 2-key rollover matrix KSCAN device, resulting int a deghosted virtual device
|
||||||
|
|
||||||
|
compatible: "zmk,kscan-deghost"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
rows:
|
||||||
|
type: int
|
||||||
|
columns:
|
||||||
|
type: int
|
||||||
|
kscan:
|
||||||
|
type: phandle
|
||||||
|
required: true
|
||||||
|
transform:
|
||||||
|
type: phandle
|
|
@ -265,6 +265,100 @@ One possible way to do this is a 3x4 matrix where the direct GPIO keys are shift
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deghost Driver
|
||||||
|
|
||||||
|
Keyboard scan driver which takes another keyboard scan driver and applies deghosting on it.
|
||||||
|
Deghosting is needed for keyboard matrices that are not built to be n-key-rollover capable,
|
||||||
|
for example contact switch matrices that don't have one diode per key.
|
||||||
|
|
||||||
|
In keyboards such as these typically the output pins will be configured GPIO_OPEN_DRAIN, or
|
||||||
|
GPIO_OPEN_SOURCE, with pull resistors enabled in the opposite direction on the inputs, and
|
||||||
|
often on the outputs as well. Alternatively external circuitry can also be used to achieve
|
||||||
|
this if the outputs don't support single-ended output, or sometimes discrete pull-resistors
|
||||||
|
can be added per input pin.
|
||||||
|
|
||||||
|
On keyboards such as these, if 3 keys are pressed that form a right angle on the keyboard
|
||||||
|
matrix, then the 4th key will also appear to be incorrectly pressed from the perspective
|
||||||
|
of the matrix driver, for example:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th></th><th>...</th><th>Col 1</th><th>...</th><th>Col 2</th><th>...</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><th>...</th><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr>
|
||||||
|
<tr><th>Row 1</th><td>...</td><td>Pressed</td><td>...</td><td>Pressed</td><td>...</td></tr>
|
||||||
|
<tr><th>...</th><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr>
|
||||||
|
<tr><th>Row 2</th><td>...</td><td>Pressed</td><td>...</td><td><b>Ghosting</b></td><td>...</td></tr>
|
||||||
|
<tr><th>...</th><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
In the above example, if the keys were ressed in the following order:
|
||||||
|
|
||||||
|
- (Row1,Col1)
|
||||||
|
- (Row1,Col2)
|
||||||
|
- (Row2,Col1)
|
||||||
|
|
||||||
|
then in fact it is impossible to determine if the 3rd key pressed was actually
|
||||||
|
(Row2,Col1) or (Row2,Col2), because both matrix positions become active at the same time,
|
||||||
|
and with a simple switch matrix it's not possible to differentiate between those two.
|
||||||
|
|
||||||
|
Note that there are 4 possible rotation of the above example.
|
||||||
|
|
||||||
|
Deghosting works by looking for rectangles in the keyboard matrix on the corners of which
|
||||||
|
at least 2 keys have already been reported as pressed. If new keypresses come in on additional
|
||||||
|
corners, then they will be ignored. Since only two keys are guaranteed to be accepted, these
|
||||||
|
keyboards are often called 2-key rollover keyboards, even though in some cases many more keys
|
||||||
|
could be pressed, and successfully reported, as long as they don't form right angles on the
|
||||||
|
matrix.
|
||||||
|
|
||||||
|
It's possible to further optimize this behaviour, if the deghosting driver knows which matrix
|
||||||
|
positions are guaranteed to be empty, and never make contact. In this case certain right-angle
|
||||||
|
combinations can also be accepted. This can be done by giving the dehost driver a transform node.
|
||||||
|
|
||||||
|
### Devicetree
|
||||||
|
|
||||||
|
Applies to : `compatible = "zmk,kscan-deghost"`
|
||||||
|
|
||||||
|
Definition file: [zmk/app/dts/bindings/zmk,kscan-deghost.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk,kscan-deghost.yaml)
|
||||||
|
|
||||||
|
| Property | Type | Description | Default |
|
||||||
|
| ----------- | ------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| `kscan` | phandle | The kscan driver to perform deghosting on | |
|
||||||
|
| `transform` | phandle | Optional transform node for optimization | |
|
||||||
|
| `rows` | int | The number of rows in the deghost matrix | It will try to extract the row count from the transform, or from the referenced kscan. |
|
||||||
|
| `columnss` | int | The number of columns in the deghost matrix | It will try to extract the column count from the transform, or from the referenced kscan. |
|
||||||
|
|
||||||
|
### Example Configuration
|
||||||
|
|
||||||
|
The deghost driver will typically sit on top of a matrix driver,
|
||||||
|
like the following Devicetree code:
|
||||||
|
|
||||||
|
```devicetree
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zmk,kscan = &kscan0;
|
||||||
|
zmk,matrix_transform = &default_transform;
|
||||||
|
};
|
||||||
|
|
||||||
|
kscan0: kscan_deghost {
|
||||||
|
compatible = "zmk,kscan-deghost";
|
||||||
|
kscan = <&kscan1>;
|
||||||
|
transform = <&default_transform>;
|
||||||
|
};
|
||||||
|
|
||||||
|
kscan1: kscan_matrix {
|
||||||
|
compatible = "zmk,kscan-gpio-matrix";
|
||||||
|
// define matrix here...
|
||||||
|
};
|
||||||
|
|
||||||
|
default_transform: keymap_transform_0 {
|
||||||
|
// define default transform here...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Mock Driver
|
## Mock Driver
|
||||||
|
|
||||||
Mock keyboard scan driver that simulates key events.
|
Mock keyboard scan driver that simulates key events.
|
||||||
|
|
Loading…
Add table
Reference in a new issue