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_MOCK_DRIVER kscan_mock.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
|
||||
|
||||
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_DIRECT := zmk,kscan-gpio-direct
|
||||
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
|
||||
|
@ -11,6 +12,10 @@ config ZMK_KSCAN_COMPOSITE_DRIVER
|
|||
bool
|
||||
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
|
||||
bool
|
||||
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 keyboard scan driver that simulates key events.
|
||||
|
|
Loading…
Add table
Reference in a new issue