Browse Source

refactor(kscan): Demacroify GPIO matrix driver

Refactored the GPIO matrix kscan driver so that only the data and config
structures are defined in the foreach macro. Functionality is unchanged
except for the addition of DT properties to adjust polling speed.

This should make it easier to add other enhancements later, like
improved and customizable debounce behavior.
xmkb
Joel Spadin 3 years ago committed by Pete Johanson
parent
commit
82cb762698
  1. 696
      app/drivers/kscan/kscan_gpio_matrix.c
  2. 5
      app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml

696
app/drivers/kscan/kscan_gpio_matrix.c

@ -1,48 +1,158 @@
/* /*
* Copyright (c) 2020 The ZMK Contributors * Copyright (c) 2020-2021 The ZMK Contributors
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
#include <device.h> #include <device.h>
#include <drivers/kscan.h> #include <devicetree.h>
#include <drivers/gpio.h> #include <drivers/gpio.h>
#include <drivers/kscan.h>
#include <logging/log.h> #include <logging/log.h>
#include <sys/__assert.h>
#include <sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct kscan_gpio_item_config { #define INST_DIODE_DIR(n) DT_ENUM_IDX(DT_DRV_INST(n), diode_direction)
char *label; #define COND_DIODE_DIR(n, row2col_code, col2row_code) \
COND_CODE_0(INST_DIODE_DIR(n), row2col_code, col2row_code)
#define INST_ROWS_LEN(n) DT_INST_PROP_LEN(n, row_gpios)
#define INST_COLS_LEN(n) DT_INST_PROP_LEN(n, col_gpios)
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
#define USE_INTERRUPTS (!USE_POLLING)
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), code)
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
// TODO (Zephr 2.6): replace the following
// kscan_gpio_dt_spec -> gpio_dt_spec
// KSCAN_GPIO_DT_SPEC_GET_BY_IDX -> GPIO_DT_SPEC_GET_BY_IDX
// gpio_pin_get -> gpio_pin_get_dt
// gpio_pin_set -> gpio_pin_set_dt
// gpio_pin_interrupt_configure -> gpio_pin_interrupt_configure_dt
struct kscan_gpio_dt_spec {
const struct device *port;
gpio_pin_t pin; gpio_pin_t pin;
gpio_flags_t flags; gpio_dt_flags_t dt_flags;
}; };
#define _KSCAN_GPIO_ITEM_CFG_INIT(n, prop, idx) \ #define KSCAN_GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx) \
{ \ { \
.label = DT_INST_GPIO_LABEL_BY_IDX(n, prop, idx), \ .port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \
.pin = DT_INST_GPIO_PIN_BY_IDX(n, prop, idx), \ .pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx), \
.flags = DT_INST_GPIO_FLAGS_BY_IDX(n, prop, idx), \ .dt_flags = DT_GPIO_FLAGS_BY_IDX(node_id, prop, idx), \
}, }
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx),
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx),
enum kscan_diode_direction {
KSCAN_ROW2COL,
KSCAN_COL2ROW,
};
struct kscan_matrix_irq_callback {
const struct device *dev;
struct gpio_callback callback;
struct k_delayed_work *work;
};
struct kscan_matrix_data {
const struct device *dev;
kscan_callback_t callback;
struct k_delayed_work work;
#if USE_POLLING
struct k_timer poll_timer;
#else
/** Array of length config->inputs.len */
struct kscan_matrix_irq_callback *irqs;
#endif
/**
* Current state of the matrix as a flattened 2D array of length
* (config->rows.len * config->cols.len)
*/
bool *current_state;
/** Buffer for reading in the next matrix state. Parallel array to current_state. */
bool *next_state;
};
#define _KSCAN_GPIO_ROW_CFG_INIT(idx, n) _KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, idx) struct kscan_gpio_list {
#define _KSCAN_GPIO_COL_CFG_INIT(idx, n) _KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, idx) const struct kscan_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_gpio_list rows;
struct kscan_gpio_list cols;
struct kscan_gpio_list inputs;
struct kscan_gpio_list outputs;
int32_t debounce_period_ms;
int32_t poll_period_ms;
enum kscan_diode_direction diode_direction;
};
/**
* 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) {
__ASSERT(row < config->rows.len, "Invalid row %i", row);
__ASSERT(col < config->cols.len, "Invalid column %i", col);
return (col * config->rows.len) + row;
}
/**
* Get the index into a matrix state array from input/output pin indices.
*/
static int state_index_io(const struct kscan_matrix_config *config, const int input_idx,
const int output_idx) {
return (config->diode_direction == KSCAN_ROW2COL)
? state_index_rc(config, output_idx, input_idx)
: state_index_rc(config, input_idx, output_idx);
}
static int kscan_matrix_set_all_outputs(const struct device *dev, const int value) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->outputs.gpios[i];
int err = gpio_pin_set(gpio->port, gpio->pin, value);
if (err) {
LOG_ERR("Failed to set output %i to %i: %i", i, value, err);
return err;
}
}
return 0;
}
#if !defined(CONFIG_ZMK_KSCAN_MATRIX_POLLING) #if USE_INTERRUPTS
static int kscan_gpio_config_interrupts(const struct device **devices, static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_gpio_item_config *configs, size_t len, const struct kscan_matrix_config *config = dev->config;
gpio_flags_t flags) {
for (int i = 0; i < len; i++) {
const struct device *dev = devices[i];
const struct kscan_gpio_item_config *cfg = &configs[i];
int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags); for (int i = 0; i < config->inputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = gpio_pin_interrupt_configure(gpio->port, gpio->pin, flags);
if (err) { if (err) {
LOG_ERR("Unable to enable matrix GPIO interrupt"); LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err; return err;
} }
} }
@ -51,257 +161,299 @@ static int kscan_gpio_config_interrupts(const struct device **devices,
} }
#endif #endif
#define COND_POLLING(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (code), ()) #if USE_INTERRUPTS
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), (code)) static int kscan_matrix_interrupt_enable(const struct device *dev) {
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \ int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode) if (err) {
return err;
}
#define INST_MATRIX_ROWS(n) DT_INST_PROP_LEN(n, row_gpios) // While interrupts are enabled, set all outputs active so a pressed key
#define INST_MATRIX_COLS(n) DT_INST_PROP_LEN(n, col_gpios) // will trigger an interrupt.
#define INST_OUTPUT_LEN(n) \ return kscan_matrix_set_all_outputs(dev, 1);
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_ROWS(n)), \ }
(INST_MATRIX_COLS(n))) #endif
#define INST_INPUT_LEN(n) \
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_COLS(n)), \ #if USE_INTERRUPTS
(INST_MATRIX_ROWS(n))) static int kscan_matrix_interrupt_disable(const struct device *dev) {
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_DISABLE);
#define GPIO_INST_INIT(n) \ if (err) {
COND_INTERRUPTS( \ return err;
struct kscan_gpio_irq_callback_##n { \ }
struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) * \
work; \ // While interrupts are disabled, set all outputs inactive so
struct gpio_callback callback; \ // kscan_matrix_read() can scan them one by one.
const struct device *dev; \ return kscan_matrix_set_all_outputs(dev, 0);
}; \ }
static struct kscan_gpio_irq_callback_##n irq_callbacks_##n[INST_INPUT_LEN(n)];) \ #endif
struct kscan_gpio_config_##n { \
struct kscan_gpio_item_config rows[INST_MATRIX_ROWS(n)]; \ #if USE_INTERRUPTS
struct kscan_gpio_item_config cols[INST_MATRIX_COLS(n)]; \ static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
}; \ const gpio_port_pins_t pin) {
struct kscan_gpio_data_##n { \ struct kscan_matrix_irq_callback *data =
kscan_callback_t callback; \ CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
COND_POLLING(struct k_timer poll_timer;) \ const struct kscan_matrix_config *config = data->dev->config;
struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) work; \
bool matrix_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)]; \ // Disable our interrupts temporarily to avoid re-entry while we scan.
const struct device *rows[INST_MATRIX_ROWS(n)]; \ kscan_matrix_interrupt_disable(data->dev);
const struct device *cols[INST_MATRIX_COLS(n)]; \
const struct device *dev; \ // TODO (Zephyr 2.6): use k_work_reschedule()
}; \ k_delayed_work_cancel(data->work);
static const struct device **kscan_gpio_input_devices_##n(const struct device *dev) { \ k_delayed_work_submit(data->work, K_MSEC(config->debounce_period_ms));
struct kscan_gpio_data_##n *data = dev->data; \ }
return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->cols), \ #endif
(data->rows))); \
} \ static int kscan_matrix_read(const struct device *dev) {
static const struct kscan_gpio_item_config *kscan_gpio_input_configs_##n( \ struct kscan_matrix_data *data = dev->data;
const struct device *dev) { \ const struct kscan_matrix_config *config = dev->config;
const struct kscan_gpio_config_##n *cfg = dev->config; \
return (( \ // Scan the matrix.
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->cols), (cfg->rows)))); \ for (int o = 0; o < config->outputs.len; o++) {
} \ const struct kscan_gpio_dt_spec *out_gpio = &config->outputs.gpios[o];
static const struct device **kscan_gpio_output_devices_##n(const struct device *dev) { \
struct kscan_gpio_data_##n *data = dev->data; \ int err = gpio_pin_set(out_gpio->port, out_gpio->pin, 1);
return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->rows), \ if (err) {
(data->cols))); \ LOG_ERR("Failed to set output %i active: %i", o, err);
} \ return err;
static const struct kscan_gpio_item_config *kscan_gpio_output_configs_##n( \ }
const struct device *dev) { \
const struct kscan_gpio_config_##n *cfg = dev->config; \ for (int i = 0; i < config->inputs.len; i++) {
return ( \ const struct kscan_gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->rows), (cfg->cols))); \
} \ const int index = state_index_io(config, i, o);
COND_INTERRUPTS( \ data->next_state[index] = gpio_pin_get(in_gpio->port, in_gpio->pin);
static int kscan_gpio_enable_interrupts_##n(const struct device *dev) { \ }
return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), \
kscan_gpio_input_configs_##n(dev), \ err = gpio_pin_set(out_gpio->port, out_gpio->pin, 0);
INST_INPUT_LEN(n), GPIO_INT_LEVEL_ACTIVE); \ if (err) {
} static int kscan_gpio_disable_interrupts_##n(const struct device *dev) { \ LOG_ERR("Failed to set output %i inactive: %i", o, err);
return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), \ return err;
kscan_gpio_input_configs_##n(dev), \ }
INST_INPUT_LEN(n), GPIO_INT_DISABLE); \ }
}) \
static void kscan_gpio_set_output_state_##n(const struct device *dev, int value) { \ // Process the new state.
int err; \ #if USE_INTERRUPTS
for (int i = 0; i < INST_OUTPUT_LEN(n); i++) { \ bool submit_followup_read = false;
const struct device *in_dev = kscan_gpio_output_devices_##n(dev)[i]; \ #endif
const struct kscan_gpio_item_config *cfg = &kscan_gpio_output_configs_##n(dev)[i]; \
if ((err = gpio_pin_set(in_dev, cfg->pin, value))) { \ for (int r = 0; r < config->rows.len; r++) {
LOG_DBG("FAILED TO SET OUTPUT %d to %d", cfg->pin, err); \ for (int c = 0; c < config->cols.len; c++) {
} \ const int index = state_index_rc(config, r, c);
} \ const bool pressed = data->next_state[index];
} \
static void kscan_gpio_set_matrix_state_##n( \ // Follow up reads are needed if any key is pressed because further
bool state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)], uint32_t input_index, \ // interrupts won't fire on already tripped GPIO pins.
uint32_t output_index, bool value) { \ #if USE_INTERRUPTS
state[COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (output_index), \ submit_followup_read = submit_followup_read || pressed;
(input_index))] \ #endif
[COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (input_index), \ if (pressed != data->current_state[index]) {
(output_index))] = value; \ LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
} \ data->current_state[index] = pressed;
static int kscan_gpio_read_##n(const struct device *dev) { \ data->callback(dev, r, c, pressed);
COND_INTERRUPTS(bool submit_follow_up_read = false;) \ }
struct kscan_gpio_data_##n *data = dev->data; \ }
static bool read_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)]; \ }
int err; \
/* Disable our interrupts temporarily while we scan, to avoid */ \ #if USE_INTERRUPTS
/* re-entry while we iterate columns and set them active one by one */ \ if (submit_followup_read) {
/* to get pressed state for each matrix cell. */ \ // At least one key is pressed. Poll until everything is released.
COND_INTERRUPTS(kscan_gpio_set_output_state_##n(dev, 0);) \ // TODO (Zephyr 2.6): use k_work_reschedule()
for (int o = 0; o < INST_OUTPUT_LEN(n); o++) { \ k_delayed_work_cancel(&data->work);
const struct device *out_dev = kscan_gpio_output_devices_##n(dev)[o]; \ k_delayed_work_submit(&data->work, K_MSEC(config->debounce_period_ms));
const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o]; \ } else {
err = gpio_pin_set(out_dev, out_cfg->pin, 1); \ // All keys are released. Return to waiting for an interrupt.
if (err) { \ kscan_matrix_interrupt_enable(dev);
LOG_ERR("Failed to set output active (err %d)", err); \ }
return err; \ #endif
} \
for (int i = 0; i < INST_INPUT_LEN(n); i++) { \ return 0;
const struct device *in_dev = kscan_gpio_input_devices_##n(dev)[i]; \ }
const struct kscan_gpio_item_config *in_cfg = \
&kscan_gpio_input_configs_##n(dev)[i]; \ #if USE_POLLING
kscan_gpio_set_matrix_state_##n(read_state, i, o, \ static void kscan_matrix_timer_handler(struct k_timer *timer) {
gpio_pin_get(in_dev, in_cfg->pin) > 0); \ struct kscan_matrix_data *data = CONTAINER_OF(timer, struct kscan_matrix_data, poll_timer);
} \ k_delayed_work_submit(&data->work, K_NO_WAIT);
err = gpio_pin_set(out_dev, out_cfg->pin, 0); \ }
if (err) { \ #endif
LOG_ERR("Failed to set output inactive (err %d)", err); \
return err; \ static void kscan_matrix_work_handler(struct k_work *work) {
} \ struct k_delayed_work *dwork = CONTAINER_OF(work, struct k_delayed_work, work);
} \ struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
/* Set all our outputs as active again. */ \ kscan_matrix_read(data->dev);
COND_INTERRUPTS(kscan_gpio_set_output_state_##n(dev, 1);) \ }
for (int r = 0; r < INST_MATRIX_ROWS(n); r++) { \
for (int c = 0; c < INST_MATRIX_COLS(n); c++) { \ static int kscan_matrix_configure(const struct device *dev, const kscan_callback_t callback) {
bool pressed = read_state[r][c]; \ struct kscan_matrix_data *data = dev->data;
/* Follow up reads needed because further interrupts won't fire on already tripped \
* input GPIO pins */ \ if (!callback) {
COND_INTERRUPTS(submit_follow_up_read = (submit_follow_up_read || pressed);) \ return -EINVAL;
if (pressed != data->matrix_state[r][c]) { \ }
LOG_DBG("Sending event at %d,%d state %s", r, c, (pressed ? "on" : "off")); \
data->matrix_state[r][c] = pressed; \ data->callback = callback;
data->callback(dev, r, c, pressed); \ return 0;
} \ }
} \
} \ static int kscan_matrix_enable(const struct device *dev) {
COND_INTERRUPTS( \ #if USE_POLLING
if (submit_follow_up_read) { \ struct kscan_matrix_data *data = dev->data;
COND_CODE_0(DT_INST_PROP(n, debounce_period), ({ k_work_submit(&data->work); }), \ const struct kscan_matrix_config *config = dev->config;
({ \
k_delayed_work_cancel(&data->work); \ k_timer_start(&data->poll_timer, K_MSEC(config->poll_period_ms),
k_delayed_work_submit(&data->work, K_MSEC(5)); \ K_MSEC(config->poll_period_ms));
})) \ return 0;
} else { kscan_gpio_enable_interrupts_##n(dev); }) \ #else
return 0; \ // Read will automatically enable interrupts once done.
} \ return kscan_matrix_read(dev);
static void kscan_gpio_work_handler_##n(struct k_work *work) { \ #endif
struct kscan_gpio_data_##n *data = CONTAINER_OF(work, struct kscan_gpio_data_##n, work); \ }
kscan_gpio_read_##n(data->dev); \
} \ static int kscan_matrix_disable(const struct device *dev) {
COND_INTERRUPTS(static void kscan_gpio_irq_callback_handler_##n( \ #if USE_POLLING
const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pin) { \ struct kscan_matrix_data *data = dev->data;
struct kscan_gpio_irq_callback_##n *data = \
CONTAINER_OF(cb, struct kscan_gpio_irq_callback_##n, callback); \ k_timer_stop(&data->poll_timer);
kscan_gpio_disable_interrupts_##n(data->dev); \ return 0;
COND_CODE_0(DT_INST_PROP(n, debounce_period), ({ k_work_submit(data->work); }), ({ \ #else
k_delayed_work_cancel(data->work); \ return kscan_matrix_interrupt_disable(dev);
k_delayed_work_submit(data->work, \ #endif
K_MSEC(DT_INST_PROP(n, debounce_period))); \ }
})) \
}) \ static int kscan_matrix_init_input_inst(const struct device *dev,
const struct kscan_gpio_dt_spec *gpio, const int index) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure(gpio->port, gpio->pin, GPIO_INPUT | gpio->dt_flags);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
#if USE_INTERRUPTS
struct kscan_matrix_data *data = dev->data;
struct kscan_matrix_irq_callback *irq = &data->irqs[index];
irq->dev = dev;
irq->work = &data->work;
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin));
err = gpio_add_callback(gpio->port, &irq->callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
return err;
}
#endif
return 0;
}
static int kscan_matrix_init_inputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->inputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = kscan_matrix_init_input_inst(dev, gpio, i);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init_output_inst(const struct device *dev,
const struct kscan_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(gpio->port, gpio->pin, GPIO_OUTPUT | gpio->dt_flags);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for output", gpio->pin, gpio->port->name);
return 0;
}
static int kscan_matrix_init_outputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct kscan_gpio_dt_spec *gpio = &config->outputs.gpios[i];
int err = kscan_matrix_init_output_inst(dev, gpio);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
data->dev = dev;
kscan_matrix_init_inputs(dev);
kscan_matrix_init_outputs(dev);
kscan_matrix_set_all_outputs(dev, 0);
k_delayed_work_init(&data->work, kscan_matrix_work_handler);
#if USE_POLLING
k_timer_init(&data->poll_timer, kscan_matrix_timer_handler, NULL);
#endif
return 0;
}
static const struct kscan_driver_api kscan_matrix_api = {
.config = kscan_matrix_configure,
.enable_callback = kscan_matrix_enable,
.disable_callback = kscan_matrix_disable,
};
#define KSCAN_MATRIX_INIT(index) \
static const struct kscan_gpio_dt_spec kscan_matrix_rows_##index[] = { \
UTIL_LISTIFY(INST_ROWS_LEN(index), KSCAN_GPIO_ROW_CFG_INIT, index)}; \
\ \
static struct kscan_gpio_data_##n kscan_gpio_data_##n = { \ static const struct kscan_gpio_dt_spec kscan_matrix_cols_##index[] = { \
.rows = {[INST_MATRIX_ROWS(n) - 1] = NULL}, .cols = {[INST_MATRIX_COLS(n) - 1] = NULL}}; \ UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
static int kscan_gpio_configure_##n(const struct device *dev, kscan_callback_t callback) { \ \
struct kscan_gpio_data_##n *data = dev->data; \ static bool kscan_current_state_##index[INST_MATRIX_LEN(index)]; \
if (!callback) { \ static bool kscan_next_state_##index[INST_MATRIX_LEN(index)]; \
return -EINVAL; \ \
} \ COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
data->callback = callback; \ kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
LOG_DBG("Configured GPIO %d", n); \ \
return 0; \ static struct kscan_matrix_data kscan_matrix_data_##index = { \
}; \ .current_state = kscan_current_state_##index, \
static int kscan_gpio_enable_##n(const struct device *dev) { \ .next_state = kscan_next_state_##index, \
COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \ COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
k_timer_start(&data->poll_timer, K_MSEC(10), K_MSEC(10)); \ \
return 0;), \ static struct kscan_matrix_config kscan_matrix_config_##index = { \
(int err = kscan_gpio_enable_interrupts_##n(dev); \ .rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##index), \
if (err) { return err; } return kscan_gpio_read_##n(dev);)) \ .cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##index), \
}; \ .inputs = KSCAN_GPIO_LIST( \
static int kscan_gpio_disable_##n(const struct device *dev) { \ COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \ .outputs = KSCAN_GPIO_LIST( \
k_timer_stop(&data->poll_timer); return 0;), \ COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
(return kscan_gpio_disable_interrupts_##n(dev);)) \ .debounce_period_ms = DT_INST_PROP(index, debounce_period), \
}; \ .poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
COND_POLLING(static void kscan_gpio_timer_handler_##n(struct k_timer *timer) { \ .diode_direction = INST_DIODE_DIR(index), \
struct kscan_gpio_data_##n *data = \
CONTAINER_OF(timer, struct kscan_gpio_data_##n, poll_timer); \
k_work_submit(&data->work.work); \
}) \
static int kscan_gpio_init_##n(const struct device *dev) { \
struct kscan_gpio_data_##n *data = dev->data; \
int err; \
const struct device **input_devices = kscan_gpio_input_devices_##n(dev); \
for (int i = 0; i < INST_INPUT_LEN(n); i++) { \
const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs_##n(dev)[i]; \
input_devices[i] = device_get_binding(in_cfg->label); \
if (!input_devices[i]) { \
LOG_ERR("Unable to find input GPIO device"); \
return -EINVAL; \
} \
err = gpio_pin_configure(input_devices[i], in_cfg->pin, GPIO_INPUT | in_cfg->flags); \
if (err) { \
LOG_ERR("Unable to configure pin %d on %s for input", in_cfg->pin, in_cfg->label); \
return err; \
} else { \
LOG_DBG("Configured pin %d on %s for input", in_cfg->pin, in_cfg->label); \
} \
COND_INTERRUPTS( \
irq_callbacks_##n[i].work = &data->work; irq_callbacks_##n[i].dev = dev; \
gpio_init_callback(&irq_callbacks_##n[i].callback, \
kscan_gpio_irq_callback_handler_##n, BIT(in_cfg->pin)); \
err = gpio_add_callback(input_devices[i], &irq_callbacks_##n[i].callback); \
if (err) { \
LOG_ERR("Error adding the callback to the input device"); \
return err; \
}) \
} \
const struct device **output_devices = kscan_gpio_output_devices_##n(dev); \
for (int o = 0; o < INST_OUTPUT_LEN(n); o++) { \
const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o]; \
output_devices[o] = device_get_binding(out_cfg->label); \
if (!output_devices[o]) { \
LOG_ERR("Unable to find output GPIO device"); \
return -EINVAL; \
} \
err = \
gpio_pin_configure(output_devices[o], out_cfg->pin, GPIO_OUTPUT | out_cfg->flags); \
if (err) { \
LOG_ERR("Unable to configure pin %d on %s for output", out_cfg->pin, \
out_cfg->label); \
return err; \
} \
} \
data->dev = dev; \
(COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work_init), (k_delayed_work_init)))( \
&data->work, kscan_gpio_work_handler_##n); \
COND_POLL_OR_INTERRUPTS( \
(k_timer_init(&data->poll_timer, kscan_gpio_timer_handler_##n, NULL); \
kscan_gpio_set_output_state_##n(dev, 0);), \
(kscan_gpio_set_output_state_##n(dev, 1);)) \
return 0; \
} \
static const struct kscan_driver_api gpio_driver_api_##n = { \
.config = kscan_gpio_configure_##n, \
.enable_callback = kscan_gpio_enable_##n, \
.disable_callback = kscan_gpio_disable_##n, \
}; \
static const struct kscan_gpio_config_##n kscan_gpio_config_##n = { \
.rows = {UTIL_LISTIFY(INST_MATRIX_ROWS(n), _KSCAN_GPIO_ROW_CFG_INIT, n)}, \
.cols = {UTIL_LISTIFY(INST_MATRIX_COLS(n), _KSCAN_GPIO_COL_CFG_INIT, n)}, \
}; \ }; \
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, device_pm_control_nop, &kscan_gpio_data_##n, \ \
&kscan_gpio_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \ DEVICE_DT_INST_DEFINE(index, &kscan_matrix_init, device_pm_control_nop, \
&gpio_driver_api_##n); &kscan_matrix_data_##index, &kscan_matrix_config_##index, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &kscan_matrix_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT) DT_INST_FOREACH_STATUS_OKAY(KSCAN_MATRIX_INIT);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ #endif // DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

5
app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml

@ -17,6 +17,11 @@ properties:
debounce-period: debounce-period:
type: int type: int
default: 5 default: 5
description: Debounce time in milliseconds
poll-period-ms:
type: int
default: 10
description: Time between reads in milliseconds when polling is enabled
diode-direction: diode-direction:
type: string type: string
default: row2col default: row2col

Loading…
Cancel
Save