Add and enable Multiplex kscan code

Multiplex handler (all->all) with single pin for interrupt handling.

For wired boards/shields, the interrupt can be ignored to simplify the
electronics greatly.
This commit is contained in:
Hooky 2023-03-04 12:45:38 +08:00 committed by Peter Johanson
parent b35a5e83c0
commit 3ea866791a
4 changed files with 478 additions and 0 deletions

View file

@ -0,0 +1,412 @@
/*
* Copyright (c) 2020-2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include "debounce.h"
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/kscan.h>
#include <kernel.h>
#include <logging/log.h>
#include <sys/__assert.h>
#include <sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_multiplex
#define INST_LEN(n) DT_INST_PROP_LEN(n, gpios)
#define INST_MULTIPLEX_LEN(n) (INST_LEN(n) * (INST_LEN(n) - 1))
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
#else
#define INST_DEBOUNCE_PRESS_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
#endif
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
#else
#define INST_DEBOUNCE_RELEASE_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
#endif
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING)
#define USE_INTERRUPT (!USE_POLLING)
#define COND_INTERRUPT(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING, (), code)
#define KSCAN_GPIO_CFG_INIT(idx, inst_idx) \
GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx),
#define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios)
struct kscan_multiplex_data {
const struct device *dev;
kscan_callback_t callback;
struct k_work_delayable work;
int64_t scan_time; /* Timestamp of the current or scheduled scan. */
#if USE_INTERRUPT
struct gpio_callback irq_callback;
#endif
/**
* Current state of the matrix as a flattened 2D array of length
* (config->cells.length ^2)
*/
struct debounce_state *multiplex_state;
};
struct kscan_gpio_list {
const struct gpio_dt_spec *gpios;
size_t len;
};
/** Define a kscan_gpio_list from a compile-time GPIO array. */
#define KSCAN_GPIO_LIST(gpio_array) \
((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)})
struct kscan_multiplex_config {
struct kscan_gpio_list cells;
struct debounce_config debounce_config;
int32_t debounce_scan_period_ms;
int32_t poll_period_ms;
#if USE_INTERRUPT
const struct gpio_dt_spec interrupt;
#endif
};
/**
* Get the index into a matrix state array from a row and column.
* There are effectively (n) cols and (n-1) rows, but we use the full col x row space
* as a safety measure against someone accidentally defining a transform RC at (p,p)
*/
static int state_index(const struct kscan_multiplex_config *config, const int row, const int col) {
__ASSERT(row < config->cells.len, "Invalid row %i", row);
__ASSERT(col < config->cells.len, "Invalid column %i", col);
__ASSERT(col != row, "Invalid column row pair %i, %i", col, row);
return (col * config->cells.len) + row;
}
static int kscan_multiplex_set_as_input(const struct gpio_dt_spec *gpio) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(gpio, GPIO_INPUT);
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_multiplex_set_as_output(const struct gpio_dt_spec *gpio) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(gpio, GPIO_OUTPUT);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name);
return err;
}
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS);
#endif
err = gpio_pin_set_dt(gpio, 1);
if (err) {
LOG_ERR("Failed to set output pin %u active: %i", gpio->pin, err);
}
return err;
}
static int kscan_multiplex_set_all_as_input(const struct device *dev) {
const struct kscan_multiplex_config *config = dev->config;
int err = 0;
for (int i = 0; i < config->cells.len; i++) {
err = kscan_multiplex_set_as_input(&config->cells.gpios[i]);
if (err) {
return err;
}
}
return 0;
}
static int kscan_multiplex_set_all_outputs(const struct device *dev, const int value) {
const struct kscan_multiplex_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_OUTPUT);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
err = gpio_pin_set_dt(gpio, value);
if (err) {
LOG_ERR("Failed to set output %i to %i: %i", i, value, err);
return err;
}
}
return 0;
}
#if USE_INTERRUPT
static int kscan_multiplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_multiplex_config *config = dev->config;
const struct gpio_dt_spec *gpio = &config->interrupt;
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
if (err) {
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err;
}
return 0;
}
#endif
#if USE_INTERRUPT
static int kscan_multiplex_interrupt_enable(const struct device *dev) {
int err = kscan_multiplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
if (err) {
return err;
}
// While interrupts are enabled, set all outputs active so an pressed key will trigger
return kscan_multiplex_set_all_outputs(dev, 1);
}
#endif
#if USE_INTERRUPT
static void kscan_multiplex_irq_callback(const struct device *port, struct gpio_callback *cb,
const gpio_port_pins_t _pin) {
struct kscan_multiplex_data *data = CONTAINER_OF(cb, struct kscan_multiplex_data, irq_callback);
// Disable our interrupt to avoid re-entry while we scan.
kscan_multiplex_interrupt_configure(data->dev, GPIO_INT_DISABLE);
data->scan_time = k_uptime_get();
k_work_reschedule(&data->work, K_NO_WAIT);
}
#endif
static void kscan_multiplex_read_continue(const struct device *dev) {
const struct kscan_multiplex_config *config = dev->config;
struct kscan_multiplex_data *data = dev->data;
data->scan_time += config->debounce_scan_period_ms;
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
}
static void kscan_multiplex_read_end(const struct device *dev) {
#if USE_INTERRUPT
// Return to waiting for an interrupt.
kscan_multiplex_interrupt_enable(dev);
#else
struct kscan_multiplex_data *data = dev->data;
const struct kscan_multiplex_config *config = dev->config;
data->scan_time += config->poll_period_ms;
// Return to polling slowly.
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
#endif
}
static int kscan_multiplex_read(const struct device *dev) {
struct kscan_multiplex_data *data = dev->data;
const struct kscan_multiplex_config *config = dev->config;
bool continue_scan = false;
// NOTE: MULTI vs MATRIX: set all pins as input, in case there was a failure on a
// previous scan, and one of the pins is still set as output
int err = kscan_multiplex_set_all_as_input(dev);
if (err) {
return err;
}
// Scan the matrix.
for (int row = 0; row < config->cells.len; row++) {
const struct gpio_dt_spec *out_gpio = &config->cells.gpios[row];
err = kscan_multiplex_set_as_output(out_gpio);
if (err) {
return err;
}
#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS > 0
k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS);
#endif
for (int col = 0; col < config->cells.len; col++) {
if (col == row) {
continue; // pin can't drive itself
}
const struct gpio_dt_spec *in_gpio = &config->cells.gpios[col];
const int index = state_index(config, row, col);
struct debounce_state *state = &data->multiplex_state[index];
debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms,
&config->debounce_config);
// NOTE: MULTI vs MATRIX: because we don't need an input/output => row/column
// setup, we can update in the same loop.
if (debounce_get_changed(state)) {
const bool pressed = debounce_is_pressed(state);
LOG_DBG("Sending event at %i,%i state %s", row, col, pressed ? "on" : "off");
data->callback(dev, row, col, pressed);
}
continue_scan = continue_scan || debounce_is_active(state);
}
err = kscan_multiplex_set_as_input(out_gpio);
if (err) {
return err;
}
#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS > 0
k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS);
#endif
}
if (continue_scan) {
// At least one key is pressed or the debouncer has not yet decided if
// it is pressed. Poll quickly until everything is released.
kscan_multiplex_read_continue(dev);
} else {
// All keys are released. Return to normal.
kscan_multiplex_read_end(dev);
}
return 0;
}
static void kscan_multiplex_work_handler(struct k_work *work) {
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
struct kscan_multiplex_data *data = CONTAINER_OF(dwork, struct kscan_multiplex_data, work);
kscan_multiplex_read(data->dev);
}
static int kscan_multiplex_configure(const struct device *dev, const kscan_callback_t callback) {
if (!callback) {
return -EINVAL;
}
struct kscan_multiplex_data *data = dev->data;
data->callback = callback;
return 0;
}
static int kscan_multiplex_enable(const struct device *dev) {
struct kscan_multiplex_data *data = dev->data;
data->scan_time = k_uptime_get();
// Read will automatically start interrupts/polling once done.
return kscan_multiplex_read(dev);
}
static int kscan_multiplex_disable(const struct device *dev) {
struct kscan_multiplex_data *data = dev->data;
k_work_cancel_delayable(&data->work);
#if USE_INTERRUPT
return kscan_multiplex_interrupt_configure(dev, GPIO_INT_DISABLE);
#else
return 0;
#endif
}
static int kscan_multiplex_init_inputs(const struct device *dev) {
const struct kscan_multiplex_config *config = dev->config;
for (int i = 0; i < config->cells.len; i++) {
int err = kscan_multiplex_set_as_input(&config->cells.gpios[i]);
if (err) {
return err;
}
}
return 0;
}
#if USE_INTERRUPT
static int kscan_multiplex_init_interrupt(const struct device *dev) {
struct kscan_multiplex_data *data = dev->data;
const struct kscan_multiplex_config *config = dev->config;
const struct gpio_dt_spec *gpio = &config->interrupt;
int err = kscan_multiplex_set_as_input(gpio);
if (err) {
return err;
}
gpio_init_callback(&data->irq_callback, kscan_multiplex_irq_callback, BIT(gpio->pin));
err = gpio_add_callback(gpio->port, &data->irq_callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
}
return err;
}
#endif
static int kscan_multiplex_init(const struct device *dev) {
struct kscan_multiplex_data *data = dev->data;
data->dev = dev;
kscan_multiplex_init_inputs(dev);
kscan_multiplex_set_all_outputs(dev, 0);
#if USE_INTERRUPT
kscan_multiplex_init_interrupt(dev);
#endif
k_work_init_delayable(&data->work, kscan_multiplex_work_handler);
return 0;
}
static const struct kscan_driver_api kscan_multiplex_api = {
.config = kscan_multiplex_configure,
.enable_callback = kscan_multiplex_enable,
.disable_callback = kscan_multiplex_disable,
};
#define KSCAN_MULTIPLEX_INIT(n) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
\
static struct debounce_state kscan_multiplex_state_##n[INST_MULTIPLEX_LEN(n)]; \
static const struct gpio_dt_spec kscan_multiplex_cells_##n[] = { \
UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \
static struct kscan_multiplex_data kscan_multiplex_data_##n = { \
.multiplex_state = kscan_multiplex_state_##n, \
}; \
\
static struct kscan_multiplex_config kscan_multiplex_config_##n = { \
.cells = KSCAN_GPIO_LIST(kscan_multiplex_cells_##n), \
.debounce_config = \
{ \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
}, \
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
COND_INTERRUPT((.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \
\
DEVICE_DT_INST_DEFINE(n, &kscan_multiplex_init, NULL, &kscan_multiplex_data_##n, \
&kscan_multiplex_config_##n, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &kscan_multiplex_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_MULTIPLEX_INIT);

View file

@ -0,0 +1,32 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: GPIO keyboard full multiplexed matrix controller
compatible: "zmk,kscan-gpio-multiplex"
include: kscan.yaml
properties:
gpios:
type: phandle-array
required: true
interrupt-gpios:
type: phandle-array
required: true
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.
poll-period-ms:
type: int
default: 1
description: Time between reads in milliseconds

View file

@ -9,3 +9,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_GPIO_MULTIPLEX kscan_gpio_multiplex.c)

View file

@ -5,6 +5,7 @@ DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
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
DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX := zmk,kscan-gpio-multiplex
DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock
if KSCAN
@ -33,6 +34,11 @@ config ZMK_KSCAN_GPIO_MATRIX
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX))
select ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_GPIO_MULTIPLEX
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX))
select ZMK_KSCAN_GPIO_DRIVER
if ZMK_KSCAN_GPIO_MATRIX
config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS
@ -58,6 +64,30 @@ config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS
endif # ZMK_KSCAN_GPIO_MATRIX
if ZMK_KSCAN_GPIO_MULTIPLEX
config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS
int "Ticks to wait before reading inputs after an output set active"
default 0
help
When iterating over each output to drive it active, read inputs, then set
inactive again, some boards may take time for output to propagate to the
inputs. In that scenario, set this value to a positive value to configure
the number of ticks to wait after setting an output active before reading
the inputs for their active state.
config ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS
int "Ticks to wait between each output when scanning round robin matrix"
default 0
help
When iterating over each output to drive it active, read inputs, then set
inactive again, some boards may take time for the previous output to
"settle" before reading inputs for the next active output column. In that
scenario, set this value to a positive value to configure the number of
usecs to wait after reading each column of keys.
endif # ZMK_KSCAN_GPIO_MULTIPLEX
config ZMK_KSCAN_MOCK_DRIVER
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK))
@ -67,6 +97,9 @@ if ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_MATRIX_POLLING
bool "Poll for key event triggers instead of using interrupts on matrix boards."
config ZMK_KSCAN_MULTIPLEX_POLLING
bool "Poll for key event triggers instead of using interrupts on multiplex boards."
config ZMK_KSCAN_DIRECT_POLLING
bool "Poll for key event triggers instead of using interrupts on direct wired boards."