refactor(kscan): batch GPIO reads by port
Changed the GPIO matrix and direct GPIO key scan drivers to do a single read per port instead of one read per pin. This is much more efficient for some types of GPIO drivers, such as I2C GPIO expanders. To accomplish this with minimal overhead, we now sort input pins by port at driver init. if we iterate through the pins in the sorted order, all pins on the same port are consecutive, so we only need to read each port once the first time we see it.
This commit is contained in:
parent
1adfcf92bf
commit
b276a3bfb0
5 changed files with 188 additions and 89 deletions
|
@ -5,6 +5,7 @@ zephyr_library_named(zmk__drivers__kscan)
|
||||||
zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include)
|
zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||||
|
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER debounce.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER debounce.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c)
|
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)
|
||||||
|
|
33
app/drivers/kscan/kscan_gpio.c
Normal file
33
app/drivers/kscan/kscan_gpio.c
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kscan_gpio.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int compare_ports(const void *a, const void *b) {
|
||||||
|
const struct kscan_gpio *gpio_a = a;
|
||||||
|
const struct kscan_gpio *gpio_b = b;
|
||||||
|
|
||||||
|
return gpio_a->spec.port - gpio_b->spec.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list) {
|
||||||
|
qsort(list->gpios, list->len, sizeof(list->gpios[0]), compare_ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state) {
|
||||||
|
if (gpio->spec.port != state->port) {
|
||||||
|
state->port = gpio->spec.port;
|
||||||
|
|
||||||
|
const int err = gpio_port_get(state->port, &state->value);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (state->value & BIT(gpio->spec.pin)) != 0;
|
||||||
|
}
|
61
app/drivers/kscan/kscan_gpio.h
Normal file
61
app/drivers/kscan/kscan_gpio.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
|
||||||
|
struct kscan_gpio {
|
||||||
|
struct gpio_dt_spec spec;
|
||||||
|
/** The index of the GPIO in the devicetree *-gpios array. */
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** GPIO_DT_SPEC_GET_BY_IDX(), but for a struct kscan_gpio. */
|
||||||
|
#define KSCAN_GPIO_GET_BY_IDX(node_id, prop, idx) \
|
||||||
|
((struct kscan_gpio){.spec = GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx), .index = idx})
|
||||||
|
|
||||||
|
struct kscan_gpio_list {
|
||||||
|
struct kscan_gpio *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_gpio_port_state {
|
||||||
|
const struct device *port;
|
||||||
|
gpio_port_value_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts a GPIO list by port so it can be used with kscan_gpio_pin_get().
|
||||||
|
*/
|
||||||
|
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logical level of an input pin.
|
||||||
|
*
|
||||||
|
* This is equivalent to gpio_pin_get() except that, when iterating through the
|
||||||
|
* pins in a list which is sorted by kscan_gpio_list_sort_by_port(), it only
|
||||||
|
* performs one read per port instead of one read per pin.
|
||||||
|
*
|
||||||
|
* @param gpio The input pin to read.
|
||||||
|
* @param state An object to track state between reads. Must be zero-initialized before the first
|
||||||
|
* use.
|
||||||
|
*
|
||||||
|
* @retval 1 If pin logical value is 1 / active.
|
||||||
|
* @retval 0 If pin logical value is 0 / inactive.
|
||||||
|
* @retval -EIO I/O error when accessing an external GPIO chip.
|
||||||
|
* @retval -EWOULDBLOCK if operation would block.
|
||||||
|
*/
|
||||||
|
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state);
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "debounce.h"
|
#include "debounce.h"
|
||||||
|
#include "kscan_gpio.h"
|
||||||
|
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
#include <zephyr/devicetree.h>
|
#include <zephyr/devicetree.h>
|
||||||
|
@ -41,7 +42,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
|
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
|
||||||
#define KSCAN_DIRECT_INPUT_CFG_INIT(idx, inst_idx) \
|
#define KSCAN_DIRECT_INPUT_CFG_INIT(idx, inst_idx) \
|
||||||
GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx)
|
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx)
|
||||||
|
|
||||||
struct kscan_direct_irq_callback {
|
struct kscan_direct_irq_callback {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
|
@ -50,6 +51,7 @@ struct kscan_direct_irq_callback {
|
||||||
|
|
||||||
struct kscan_direct_data {
|
struct kscan_direct_data {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
|
struct kscan_gpio_list inputs;
|
||||||
kscan_callback_t callback;
|
kscan_callback_t callback;
|
||||||
struct k_work_delayable work;
|
struct k_work_delayable work;
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
|
@ -62,17 +64,7 @@ struct kscan_direct_data {
|
||||||
struct debounce_state *pin_state;
|
struct debounce_state *pin_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_direct_config {
|
struct kscan_direct_config {
|
||||||
struct kscan_gpio_list inputs;
|
|
||||||
struct debounce_config debounce_config;
|
struct debounce_config debounce_config;
|
||||||
int32_t debounce_scan_period_ms;
|
int32_t debounce_scan_period_ms;
|
||||||
int32_t poll_period_ms;
|
int32_t poll_period_ms;
|
||||||
|
@ -81,10 +73,10 @@ struct kscan_direct_config {
|
||||||
|
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
static int kscan_direct_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
static int kscan_direct_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
||||||
const struct kscan_direct_config *config = dev->config;
|
const struct kscan_direct_data *data = dev->data;
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
|
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
|
||||||
|
|
||||||
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
|
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -134,16 +126,16 @@ static gpio_flags_t kscan_gpio_get_extra_flags(const struct gpio_dt_spec *gpio,
|
||||||
|
|
||||||
static int kscan_inputs_set_flags(const struct kscan_gpio_list *inputs,
|
static int kscan_inputs_set_flags(const struct kscan_gpio_list *inputs,
|
||||||
const struct gpio_dt_spec *active_gpio) {
|
const struct gpio_dt_spec *active_gpio) {
|
||||||
gpio_flags_t extra_flags;
|
|
||||||
for (int i = 0; i < inputs->len; i++) {
|
for (int i = 0; i < inputs->len; i++) {
|
||||||
extra_flags = GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i],
|
const bool active = &inputs->gpios[i].spec == active_gpio;
|
||||||
&inputs->gpios[i] == active_gpio);
|
const gpio_flags_t extra_flags =
|
||||||
|
GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i].spec, active);
|
||||||
LOG_DBG("Extra flags equal to: %d", extra_flags);
|
LOG_DBG("Extra flags equal to: %d", extra_flags);
|
||||||
|
|
||||||
int err = gpio_pin_configure_dt(&inputs->gpios[i], extra_flags);
|
int err = gpio_pin_configure_dt(&inputs->gpios[i].spec, extra_flags);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].pin,
|
LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].spec.pin,
|
||||||
inputs->gpios[i].port->name);
|
inputs->gpios[i].spec.port->name);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,28 +171,35 @@ static int kscan_direct_read(const struct device *dev) {
|
||||||
const struct kscan_direct_config *config = dev->config;
|
const struct kscan_direct_config *config = dev->config;
|
||||||
|
|
||||||
// Read the inputs.
|
// Read the inputs.
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
struct kscan_gpio_port_state state = {0};
|
||||||
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
|
|
||||||
|
|
||||||
const bool active = gpio_pin_get_dt(gpio);
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
|
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||||
|
|
||||||
debounce_update(&data->pin_state[i], active, config->debounce_scan_period_ms,
|
const int active = kscan_gpio_pin_get(gpio, &state);
|
||||||
|
if (active < 0) {
|
||||||
|
LOG_ERR("Failed to read port %s: %i", gpio->spec.port->name, active);
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
debounce_update(&data->pin_state[gpio->index], active, config->debounce_scan_period_ms,
|
||||||
&config->debounce_config);
|
&config->debounce_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the new state.
|
// Process the new state.
|
||||||
bool continue_scan = false;
|
bool continue_scan = false;
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
struct debounce_state *state = &data->pin_state[i];
|
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||||
|
struct debounce_state *state = &data->pin_state[gpio->index];
|
||||||
|
|
||||||
if (debounce_get_changed(state)) {
|
if (debounce_get_changed(state)) {
|
||||||
const bool pressed = debounce_is_pressed(state);
|
const bool pressed = debounce_is_pressed(state);
|
||||||
|
|
||||||
LOG_DBG("Sending event at 0,%i state %s", i, pressed ? "on" : "off");
|
LOG_DBG("Sending event at 0,%i state %s", gpio->index, pressed ? "on" : "off");
|
||||||
data->callback(dev, 0, i, pressed);
|
data->callback(dev, 0, gpio->index, pressed);
|
||||||
if (config->toggle_mode && pressed) {
|
if (config->toggle_mode && pressed) {
|
||||||
kscan_inputs_set_flags(&config->inputs, &config->inputs.gpios[i]);
|
kscan_inputs_set_flags(&data->inputs, &gpio->spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,10 +288,11 @@ static int kscan_direct_init_input_inst(const struct device *dev, const struct g
|
||||||
}
|
}
|
||||||
|
|
||||||
static int kscan_direct_init_inputs(const struct device *dev) {
|
static int kscan_direct_init_inputs(const struct device *dev) {
|
||||||
|
const struct kscan_direct_data *data = dev->data;
|
||||||
const struct kscan_direct_config *config = dev->config;
|
const struct kscan_direct_config *config = dev->config;
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
|
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
|
||||||
int err = kscan_direct_init_input_inst(dev, gpio, i, config->toggle_mode);
|
int err = kscan_direct_init_input_inst(dev, gpio, i, config->toggle_mode);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
|
@ -307,6 +307,9 @@ static int kscan_direct_init(const struct device *dev) {
|
||||||
|
|
||||||
data->dev = dev;
|
data->dev = dev;
|
||||||
|
|
||||||
|
// Sort inputs by port so we can read each port just once per scan.
|
||||||
|
kscan_gpio_list_sort_by_port(&data->inputs);
|
||||||
|
|
||||||
kscan_direct_init_inputs(dev);
|
kscan_direct_init_inputs(dev);
|
||||||
|
|
||||||
k_work_init_delayable(&data->work, kscan_direct_work_handler);
|
k_work_init_delayable(&data->work, kscan_direct_work_handler);
|
||||||
|
@ -326,7 +329,7 @@ static const struct kscan_driver_api kscan_direct_api = {
|
||||||
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||||
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
||||||
\
|
\
|
||||||
static const struct gpio_dt_spec kscan_direct_inputs_##n[] = { \
|
static struct kscan_gpio kscan_direct_inputs_##n[] = { \
|
||||||
LISTIFY(INST_INPUTS_LEN(n), KSCAN_DIRECT_INPUT_CFG_INIT, (, ), n)}; \
|
LISTIFY(INST_INPUTS_LEN(n), KSCAN_DIRECT_INPUT_CFG_INIT, (, ), n)}; \
|
||||||
\
|
\
|
||||||
static struct debounce_state kscan_direct_state_##n[INST_INPUTS_LEN(n)]; \
|
static struct debounce_state kscan_direct_state_##n[INST_INPUTS_LEN(n)]; \
|
||||||
|
@ -335,10 +338,11 @@ static const struct kscan_driver_api kscan_direct_api = {
|
||||||
(static struct kscan_direct_irq_callback kscan_direct_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
(static struct kscan_direct_irq_callback kscan_direct_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
||||||
\
|
\
|
||||||
static struct kscan_direct_data kscan_direct_data_##n = { \
|
static struct kscan_direct_data kscan_direct_data_##n = { \
|
||||||
.pin_state = kscan_direct_state_##n, COND_INTERRUPTS((.irqs = kscan_direct_irqs_##n, ))}; \
|
.inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \
|
||||||
|
.pin_state = kscan_direct_state_##n, \
|
||||||
|
COND_INTERRUPTS((.irqs = kscan_direct_irqs_##n, ))}; \
|
||||||
\
|
\
|
||||||
static struct kscan_direct_config kscan_direct_config_##n = { \
|
static struct kscan_direct_config kscan_direct_config_##n = { \
|
||||||
.inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \
|
|
||||||
.debounce_config = \
|
.debounce_config = \
|
||||||
{ \
|
{ \
|
||||||
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
|
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "debounce.h"
|
#include "debounce.h"
|
||||||
|
#include "kscan_gpio.h"
|
||||||
|
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
#include <zephyr/devicetree.h>
|
#include <zephyr/devicetree.h>
|
||||||
|
@ -50,9 +51,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
|
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
|
||||||
|
|
||||||
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
|
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
|
||||||
GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx)
|
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx)
|
||||||
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
|
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
|
||||||
GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx)
|
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx)
|
||||||
|
|
||||||
enum kscan_diode_direction {
|
enum kscan_diode_direction {
|
||||||
KSCAN_ROW2COL,
|
KSCAN_ROW2COL,
|
||||||
|
@ -66,6 +67,7 @@ struct kscan_matrix_irq_callback {
|
||||||
|
|
||||||
struct kscan_matrix_data {
|
struct kscan_matrix_data {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
|
struct kscan_gpio_list inputs;
|
||||||
kscan_callback_t callback;
|
kscan_callback_t callback;
|
||||||
struct k_work_delayable work;
|
struct k_work_delayable work;
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
|
@ -76,26 +78,16 @@ struct kscan_matrix_data {
|
||||||
int64_t scan_time;
|
int64_t scan_time;
|
||||||
/**
|
/**
|
||||||
* Current state of the matrix as a flattened 2D array of length
|
* Current state of the matrix as a flattened 2D array of length
|
||||||
* (config->rows.len * config->cols.len)
|
* (config->rows * config->cols)
|
||||||
*/
|
*/
|
||||||
struct debounce_state *matrix_state;
|
struct debounce_state *matrix_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_matrix_config {
|
struct kscan_matrix_config {
|
||||||
struct kscan_gpio_list rows;
|
|
||||||
struct kscan_gpio_list cols;
|
|
||||||
struct kscan_gpio_list inputs;
|
|
||||||
struct kscan_gpio_list outputs;
|
struct kscan_gpio_list outputs;
|
||||||
struct debounce_config debounce_config;
|
struct debounce_config debounce_config;
|
||||||
|
size_t rows;
|
||||||
|
size_t cols;
|
||||||
int32_t debounce_scan_period_ms;
|
int32_t debounce_scan_period_ms;
|
||||||
int32_t poll_period_ms;
|
int32_t poll_period_ms;
|
||||||
enum kscan_diode_direction diode_direction;
|
enum kscan_diode_direction diode_direction;
|
||||||
|
@ -105,10 +97,10 @@ struct kscan_matrix_config {
|
||||||
* Get the index into a matrix state array from a row and column.
|
* Get the index into a matrix state array from a row and column.
|
||||||
*/
|
*/
|
||||||
static int state_index_rc(const struct kscan_matrix_config *config, const int row, const int col) {
|
static int state_index_rc(const struct kscan_matrix_config *config, const int row, const int col) {
|
||||||
__ASSERT(row < config->rows.len, "Invalid row %i", row);
|
__ASSERT(row < config->rows, "Invalid row %i", row);
|
||||||
__ASSERT(col < config->cols.len, "Invalid column %i", col);
|
__ASSERT(col < config->cols, "Invalid column %i", col);
|
||||||
|
|
||||||
return (col * config->rows.len) + row;
|
return (col * config->rows) + row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,7 +117,7 @@ static int kscan_matrix_set_all_outputs(const struct device *dev, const int valu
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
|
||||||
for (int i = 0; i < config->outputs.len; i++) {
|
for (int i = 0; i < config->outputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i];
|
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec;
|
||||||
|
|
||||||
int err = gpio_pin_set_dt(gpio, value);
|
int err = gpio_pin_set_dt(gpio, value);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -139,10 +131,10 @@ static int kscan_matrix_set_all_outputs(const struct device *dev, const int valu
|
||||||
|
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_data *data = dev->data;
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
|
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
|
||||||
|
|
||||||
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
|
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -226,32 +218,37 @@ static int kscan_matrix_read(const struct device *dev) {
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
|
||||||
// Scan the matrix.
|
// Scan the matrix.
|
||||||
for (int o = 0; o < config->outputs.len; o++) {
|
for (int i = 0; i < config->outputs.len; i++) {
|
||||||
const struct gpio_dt_spec *out_gpio = &config->outputs.gpios[o];
|
const struct kscan_gpio *out_gpio = &config->outputs.gpios[i];
|
||||||
|
|
||||||
int err = gpio_pin_set_dt(out_gpio, 1);
|
int err = gpio_pin_set_dt(&out_gpio->spec, 1);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Failed to set output %i active: %i", o, err);
|
LOG_ERR("Failed to set output %i active: %i", out_gpio->index, err);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0
|
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0
|
||||||
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS);
|
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS);
|
||||||
#endif
|
#endif
|
||||||
|
struct kscan_gpio_port_state state = {0};
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int j = 0; j < data->inputs.len; j++) {
|
||||||
const struct gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
|
const struct kscan_gpio *in_gpio = &data->inputs.gpios[j];
|
||||||
|
|
||||||
const int index = state_index_io(config, i, o);
|
const int index = state_index_io(config, in_gpio->index, out_gpio->index);
|
||||||
const bool active = gpio_pin_get_dt(in_gpio);
|
const int active = kscan_gpio_pin_get(in_gpio, &state);
|
||||||
|
if (active < 0) {
|
||||||
|
LOG_ERR("Failed to read port %s: %i", in_gpio->spec.port->name, active);
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
debounce_update(&data->matrix_state[index], active, config->debounce_scan_period_ms,
|
debounce_update(&data->matrix_state[index], active, config->debounce_scan_period_ms,
|
||||||
&config->debounce_config);
|
&config->debounce_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gpio_pin_set_dt(out_gpio, 0);
|
err = gpio_pin_set_dt(&out_gpio->spec, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Failed to set output %i inactive: %i", o, err);
|
LOG_ERR("Failed to set output %i inactive: %i", out_gpio->index, err);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,8 +260,8 @@ static int kscan_matrix_read(const struct device *dev) {
|
||||||
// Process the new state.
|
// Process the new state.
|
||||||
bool continue_scan = false;
|
bool continue_scan = false;
|
||||||
|
|
||||||
for (int r = 0; r < config->rows.len; r++) {
|
for (int r = 0; r < config->rows; r++) {
|
||||||
for (int c = 0; c < config->cols.len; c++) {
|
for (int c = 0; c < config->cols; c++) {
|
||||||
const int index = state_index_rc(config, r, c);
|
const int index = state_index_rc(config, r, c);
|
||||||
struct debounce_state *state = &data->matrix_state[index];
|
struct debounce_state *state = &data->matrix_state[index];
|
||||||
|
|
||||||
|
@ -329,28 +326,28 @@ static int kscan_matrix_disable(const struct device *dev) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int kscan_matrix_init_input_inst(const struct device *dev, const struct gpio_dt_spec *gpio,
|
static int kscan_matrix_init_input_inst(const struct device *dev, const struct kscan_gpio *gpio) {
|
||||||
const int index) {
|
if (!device_is_ready(gpio->spec.port)) {
|
||||||
if (!device_is_ready(gpio->port)) {
|
LOG_ERR("GPIO is not ready: %s", gpio->spec.port->name);
|
||||||
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
|
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = gpio_pin_configure_dt(gpio, GPIO_INPUT);
|
int err = gpio_pin_configure_dt(&gpio->spec, GPIO_INPUT);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
|
LOG_ERR("Unable to configure pin %u on %s for input", gpio->spec.pin,
|
||||||
|
gpio->spec.port->name);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
|
LOG_DBG("Configured pin %u on %s for input", gpio->spec.pin, gpio->spec.port->name);
|
||||||
|
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
struct kscan_matrix_data *data = dev->data;
|
struct kscan_matrix_data *data = dev->data;
|
||||||
struct kscan_matrix_irq_callback *irq = &data->irqs[index];
|
struct kscan_matrix_irq_callback *irq = &data->irqs[gpio->index];
|
||||||
|
|
||||||
irq->dev = dev;
|
irq->dev = dev;
|
||||||
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin));
|
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->spec.pin));
|
||||||
err = gpio_add_callback(gpio->port, &irq->callback);
|
err = gpio_add_callback(gpio->spec.port, &irq->callback);
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Error adding the callback to the input device: %i", err);
|
LOG_ERR("Error adding the callback to the input device: %i", err);
|
||||||
return err;
|
return err;
|
||||||
|
@ -361,11 +358,11 @@ static int kscan_matrix_init_input_inst(const struct device *dev, const struct g
|
||||||
}
|
}
|
||||||
|
|
||||||
static int kscan_matrix_init_inputs(const struct device *dev) {
|
static int kscan_matrix_init_inputs(const struct device *dev) {
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_data *data = dev->data;
|
||||||
|
|
||||||
for (int i = 0; i < config->inputs.len; i++) {
|
for (int i = 0; i < data->inputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
|
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||||
int err = kscan_matrix_init_input_inst(dev, gpio, i);
|
int err = kscan_matrix_init_input_inst(dev, gpio);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -396,7 +393,7 @@ static int kscan_matrix_init_outputs(const struct device *dev) {
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
|
||||||
for (int i = 0; i < config->outputs.len; i++) {
|
for (int i = 0; i < config->outputs.len; i++) {
|
||||||
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i];
|
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec;
|
||||||
int err = kscan_matrix_init_output_inst(dev, gpio);
|
int err = kscan_matrix_init_output_inst(dev, gpio);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
|
@ -411,6 +408,9 @@ static int kscan_matrix_init(const struct device *dev) {
|
||||||
|
|
||||||
data->dev = dev;
|
data->dev = dev;
|
||||||
|
|
||||||
|
// Sort inputs by port so we can read each port just once per scan.
|
||||||
|
kscan_gpio_list_sort_by_port(&data->inputs);
|
||||||
|
|
||||||
kscan_matrix_init_inputs(dev);
|
kscan_matrix_init_inputs(dev);
|
||||||
kscan_matrix_init_outputs(dev);
|
kscan_matrix_init_outputs(dev);
|
||||||
kscan_matrix_set_all_outputs(dev, 0);
|
kscan_matrix_set_all_outputs(dev, 0);
|
||||||
|
@ -432,10 +432,10 @@ static const struct kscan_driver_api kscan_matrix_api = {
|
||||||
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||||
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
||||||
\
|
\
|
||||||
static const struct gpio_dt_spec kscan_matrix_rows_##n[] = { \
|
static struct kscan_gpio kscan_matrix_rows_##n[] = { \
|
||||||
LISTIFY(INST_ROWS_LEN(n), KSCAN_GPIO_ROW_CFG_INIT, (, ), n)}; \
|
LISTIFY(INST_ROWS_LEN(n), KSCAN_GPIO_ROW_CFG_INIT, (, ), n)}; \
|
||||||
\
|
\
|
||||||
static const struct gpio_dt_spec kscan_matrix_cols_##n[] = { \
|
static struct kscan_gpio kscan_matrix_cols_##n[] = { \
|
||||||
LISTIFY(INST_COLS_LEN(n), KSCAN_GPIO_COL_CFG_INIT, (, ), n)}; \
|
LISTIFY(INST_COLS_LEN(n), KSCAN_GPIO_COL_CFG_INIT, (, ), n)}; \
|
||||||
\
|
\
|
||||||
static struct debounce_state kscan_matrix_state_##n[INST_MATRIX_LEN(n)]; \
|
static struct debounce_state kscan_matrix_state_##n[INST_MATRIX_LEN(n)]; \
|
||||||
|
@ -444,14 +444,14 @@ static const struct kscan_driver_api kscan_matrix_api = {
|
||||||
(static struct kscan_matrix_irq_callback kscan_matrix_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
(static struct kscan_matrix_irq_callback kscan_matrix_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
||||||
\
|
\
|
||||||
static struct kscan_matrix_data kscan_matrix_data_##n = { \
|
static struct kscan_matrix_data kscan_matrix_data_##n = { \
|
||||||
|
.inputs = \
|
||||||
|
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \
|
||||||
.matrix_state = kscan_matrix_state_##n, \
|
.matrix_state = kscan_matrix_state_##n, \
|
||||||
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \
|
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \
|
||||||
\
|
\
|
||||||
static struct kscan_matrix_config kscan_matrix_config_##n = { \
|
static struct kscan_matrix_config kscan_matrix_config_##n = { \
|
||||||
.rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##n), \
|
.rows = ARRAY_SIZE(kscan_matrix_rows_##n), \
|
||||||
.cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##n), \
|
.cols = ARRAY_SIZE(kscan_matrix_cols_##n), \
|
||||||
.inputs = \
|
|
||||||
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \
|
|
||||||
.outputs = \
|
.outputs = \
|
||||||
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \
|
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \
|
||||||
.debounce_config = \
|
.debounce_config = \
|
||||||
|
|
Loading…
Add table
Reference in a new issue