summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/drivers/kscan/kscan_gpio_matrix.c696
-rw-r--r--app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml5
2 files changed, 429 insertions, 272 deletions
diff --git a/app/drivers/kscan/kscan_gpio_matrix.c b/app/drivers/kscan/kscan_gpio_matrix.c
index 6985953..5465dd3 100644
--- a/app/drivers/kscan/kscan_gpio_matrix.c
+++ b/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
*/
-#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
-
#include <device.h>
-#include <drivers/kscan.h>
+#include <devicetree.h>
#include <drivers/gpio.h>
+#include <drivers/kscan.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_matrix
+
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
-struct kscan_gpio_item_config {
- char *label;
+#define INST_DIODE_DIR(n) DT_ENUM_IDX(DT_DRV_INST(n), diode_direction)
+#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_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), \
- .pin = DT_INST_GPIO_PIN_BY_IDX(n, prop, idx), \
- .flags = DT_INST_GPIO_FLAGS_BY_IDX(n, prop, idx), \
- },
+ .port = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \
+ .pin = DT_GPIO_PIN_BY_IDX(node_id, 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)
-#define _KSCAN_GPIO_COL_CFG_INIT(idx, n) _KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, idx)
+struct kscan_gpio_list {
+ 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)
-static int kscan_gpio_config_interrupts(const struct device **devices,
- const struct kscan_gpio_item_config *configs, size_t len,
- 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];
+#if USE_INTERRUPTS
+static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
+ const struct kscan_matrix_config *config = dev->config;
- 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) {
- 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;
}
}
@@ -51,257 +161,299 @@ static int kscan_gpio_config_interrupts(const struct device **devices,
}
#endif
-#define COND_POLLING(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (code), ())
-#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)
+#if USE_INTERRUPTS
+static int kscan_matrix_interrupt_enable(const struct device *dev) {
+ int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
+ if (err) {
+ return err;
+ }
-#define INST_MATRIX_ROWS(n) DT_INST_PROP_LEN(n, row_gpios)
-#define INST_MATRIX_COLS(n) DT_INST_PROP_LEN(n, col_gpios)
-#define INST_OUTPUT_LEN(n) \
- COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_ROWS(n)), \
- (INST_MATRIX_COLS(n)))
-#define INST_INPUT_LEN(n) \
- COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_COLS(n)), \
- (INST_MATRIX_ROWS(n)))
-
-#define GPIO_INST_INIT(n) \
- COND_INTERRUPTS( \
- struct kscan_gpio_irq_callback_##n { \
- struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) * \
- work; \
- struct gpio_callback callback; \
- const struct device *dev; \
- }; \
- static struct kscan_gpio_irq_callback_##n irq_callbacks_##n[INST_INPUT_LEN(n)];) \
- struct kscan_gpio_config_##n { \
- struct kscan_gpio_item_config rows[INST_MATRIX_ROWS(n)]; \
- struct kscan_gpio_item_config cols[INST_MATRIX_COLS(n)]; \
- }; \
- struct kscan_gpio_data_##n { \
- kscan_callback_t callback; \
- COND_POLLING(struct k_timer poll_timer;) \
- 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)]; \
- const struct device *rows[INST_MATRIX_ROWS(n)]; \
- const struct device *cols[INST_MATRIX_COLS(n)]; \
- const struct device *dev; \
- }; \
- static const struct device **kscan_gpio_input_devices_##n(const struct device *dev) { \
- struct kscan_gpio_data_##n *data = dev->data; \
- return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->cols), \
- (data->rows))); \
- } \
- static const struct kscan_gpio_item_config *kscan_gpio_input_configs_##n( \
- const struct device *dev) { \
- const struct kscan_gpio_config_##n *cfg = dev->config; \
- return (( \
- COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->cols), (cfg->rows)))); \
- } \
- static const struct device **kscan_gpio_output_devices_##n(const struct device *dev) { \
- struct kscan_gpio_data_##n *data = dev->data; \
- return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->rows), \
- (data->cols))); \
- } \
- 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; \
- return ( \
- COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->rows), (cfg->cols))); \
- } \
- COND_INTERRUPTS( \
- 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), \
- INST_INPUT_LEN(n), GPIO_INT_LEVEL_ACTIVE); \
- } static int kscan_gpio_disable_interrupts_##n(const struct device *dev) { \
- return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), \
- 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) { \
- int err; \
- for (int i = 0; i < INST_OUTPUT_LEN(n); i++) { \
- const struct device *in_dev = kscan_gpio_output_devices_##n(dev)[i]; \
- const struct kscan_gpio_item_config *cfg = &kscan_gpio_output_configs_##n(dev)[i]; \
- if ((err = gpio_pin_set(in_dev, cfg->pin, value))) { \
- LOG_DBG("FAILED TO SET OUTPUT %d to %d", cfg->pin, err); \
- } \
- } \
- } \
- static void kscan_gpio_set_matrix_state_##n( \
- bool state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)], uint32_t input_index, \
- uint32_t output_index, bool value) { \
- state[COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (output_index), \
- (input_index))] \
- [COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (input_index), \
- (output_index))] = value; \
- } \
- static int kscan_gpio_read_##n(const struct device *dev) { \
- 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 */ \
- /* re-entry while we iterate columns and set them active one by one */ \
- /* to get pressed state for each matrix cell. */ \
- COND_INTERRUPTS(kscan_gpio_set_output_state_##n(dev, 0);) \
- for (int o = 0; o < INST_OUTPUT_LEN(n); o++) { \
- const struct device *out_dev = kscan_gpio_output_devices_##n(dev)[o]; \
- const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o]; \
- err = gpio_pin_set(out_dev, out_cfg->pin, 1); \
- if (err) { \
- LOG_ERR("Failed to set output active (err %d)", err); \
- return err; \
- } \
- for (int i = 0; i < INST_INPUT_LEN(n); i++) { \
- 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]; \
- kscan_gpio_set_matrix_state_##n(read_state, i, o, \
- gpio_pin_get(in_dev, in_cfg->pin) > 0); \
- } \
- err = gpio_pin_set(out_dev, out_cfg->pin, 0); \
- if (err) { \
- LOG_ERR("Failed to set output inactive (err %d)", err); \
- return err; \
- } \
- } \
- /* Set all our outputs as active again. */ \
- 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++) { \
- bool pressed = read_state[r][c]; \
- /* Follow up reads needed because further interrupts won't fire on already tripped \
- * input GPIO pins */ \
- COND_INTERRUPTS(submit_follow_up_read = (submit_follow_up_read || pressed);) \
- 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(dev, r, c, pressed); \
- } \
- } \
- } \
- COND_INTERRUPTS( \
- if (submit_follow_up_read) { \
- COND_CODE_0(DT_INST_PROP(n, debounce_period), ({ k_work_submit(&data->work); }), \
- ({ \
- k_delayed_work_cancel(&data->work); \
- k_delayed_work_submit(&data->work, K_MSEC(5)); \
- })) \
- } else { kscan_gpio_enable_interrupts_##n(dev); }) \
- return 0; \
- } \
- static void kscan_gpio_work_handler_##n(struct k_work *work) { \
- struct kscan_gpio_data_##n *data = CONTAINER_OF(work, struct kscan_gpio_data_##n, work); \
- kscan_gpio_read_##n(data->dev); \
- } \
- COND_INTERRUPTS(static void kscan_gpio_irq_callback_handler_##n( \
- const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pin) { \
- struct kscan_gpio_irq_callback_##n *data = \
- CONTAINER_OF(cb, struct kscan_gpio_irq_callback_##n, callback); \
- kscan_gpio_disable_interrupts_##n(data->dev); \
- COND_CODE_0(DT_INST_PROP(n, debounce_period), ({ k_work_submit(data->work); }), ({ \
- k_delayed_work_cancel(data->work); \
- k_delayed_work_submit(data->work, \
- K_MSEC(DT_INST_PROP(n, debounce_period))); \
- })) \
- }) \
+ // While interrupts are enabled, set all outputs active so a pressed key
+ // will trigger an interrupt.
+ return kscan_matrix_set_all_outputs(dev, 1);
+}
+#endif
+
+#if USE_INTERRUPTS
+static int kscan_matrix_interrupt_disable(const struct device *dev) {
+ int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_DISABLE);
+ if (err) {
+ return err;
+ }
+
+ // While interrupts are disabled, set all outputs inactive so
+ // kscan_matrix_read() can scan them one by one.
+ return kscan_matrix_set_all_outputs(dev, 0);
+}
+#endif
+
+#if USE_INTERRUPTS
+static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
+ const gpio_port_pins_t pin) {
+ struct kscan_matrix_irq_callback *data =
+ CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
+ const struct kscan_matrix_config *config = data->dev->config;
+
+ // Disable our interrupts temporarily to avoid re-entry while we scan.
+ kscan_matrix_interrupt_disable(data->dev);
+
+ // TODO (Zephyr 2.6): use k_work_reschedule()
+ k_delayed_work_cancel(data->work);
+ k_delayed_work_submit(data->work, K_MSEC(config->debounce_period_ms));
+}
+#endif
+
+static int kscan_matrix_read(const struct device *dev) {
+ struct kscan_matrix_data *data = dev->data;
+ const struct kscan_matrix_config *config = dev->config;
+
+ // Scan the matrix.
+ for (int o = 0; o < config->outputs.len; o++) {
+ const struct kscan_gpio_dt_spec *out_gpio = &config->outputs.gpios[o];
+
+ int err = gpio_pin_set(out_gpio->port, out_gpio->pin, 1);
+ if (err) {
+ LOG_ERR("Failed to set output %i active: %i", o, err);
+ return err;
+ }
+
+ for (int i = 0; i < config->inputs.len; i++) {
+ const struct kscan_gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
+
+ const int index = state_index_io(config, i, o);
+ data->next_state[index] = gpio_pin_get(in_gpio->port, in_gpio->pin);
+ }
+
+ err = gpio_pin_set(out_gpio->port, out_gpio->pin, 0);
+ if (err) {
+ LOG_ERR("Failed to set output %i inactive: %i", o, err);
+ return err;
+ }
+ }
+
+ // Process the new state.
+#if USE_INTERRUPTS
+ bool submit_followup_read = false;
+#endif
+
+ for (int r = 0; r < config->rows.len; r++) {
+ 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];
+
+ // Follow up reads are needed if any key is pressed because further
+ // interrupts won't fire on already tripped GPIO pins.
+#if USE_INTERRUPTS
+ submit_followup_read = submit_followup_read || pressed;
+#endif
+ if (pressed != data->current_state[index]) {
+ LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
+ data->current_state[index] = pressed;
+ data->callback(dev, r, c, pressed);
+ }
+ }
+ }
+
+#if USE_INTERRUPTS
+ if (submit_followup_read) {
+ // At least one key is pressed. Poll until everything is released.
+ // TODO (Zephyr 2.6): use k_work_reschedule()
+ k_delayed_work_cancel(&data->work);
+ k_delayed_work_submit(&data->work, K_MSEC(config->debounce_period_ms));
+ } else {
+ // All keys are released. Return to waiting for an interrupt.
+ kscan_matrix_interrupt_enable(dev);
+ }
+#endif
+
+ return 0;
+}
+
+#if USE_POLLING
+static void kscan_matrix_timer_handler(struct k_timer *timer) {
+ struct kscan_matrix_data *data = CONTAINER_OF(timer, struct kscan_matrix_data, poll_timer);
+ k_delayed_work_submit(&data->work, K_NO_WAIT);
+}
+#endif
+
+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);
+ kscan_matrix_read(data->dev);
+}
+
+static int kscan_matrix_configure(const struct device *dev, const kscan_callback_t callback) {
+ struct kscan_matrix_data *data = dev->data;
+
+ if (!callback) {
+ return -EINVAL;
+ }
+
+ data->callback = callback;
+ return 0;
+}
+
+static int kscan_matrix_enable(const struct device *dev) {
+#if USE_POLLING
+ struct kscan_matrix_data *data = dev->data;
+ const struct kscan_matrix_config *config = dev->config;
+
+ k_timer_start(&data->poll_timer, K_MSEC(config->poll_period_ms),
+ K_MSEC(config->poll_period_ms));
+ return 0;
+#else
+ // Read will automatically enable interrupts once done.
+ return kscan_matrix_read(dev);
+#endif
+}
+
+static int kscan_matrix_disable(const struct device *dev) {
+#if USE_POLLING
+ struct kscan_matrix_data *data = dev->data;
+
+ k_timer_stop(&data->poll_timer);
+ return 0;
+#else
+ return kscan_matrix_interrupt_disable(dev);
+#endif
+}
+
+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 = { \
- .rows = {[INST_MATRIX_ROWS(n) - 1] = NULL}, .cols = {[INST_MATRIX_COLS(n) - 1] = NULL}}; \
- static int kscan_gpio_configure_##n(const struct device *dev, kscan_callback_t callback) { \
- struct kscan_gpio_data_##n *data = dev->data; \
- if (!callback) { \
- return -EINVAL; \
- } \
- data->callback = callback; \
- LOG_DBG("Configured GPIO %d", n); \
- return 0; \
- }; \
- static int kscan_gpio_enable_##n(const struct device *dev) { \
- COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \
- k_timer_start(&data->poll_timer, K_MSEC(10), K_MSEC(10)); \
- return 0;), \
- (int err = kscan_gpio_enable_interrupts_##n(dev); \
- if (err) { return err; } return kscan_gpio_read_##n(dev);)) \
- }; \
- static int kscan_gpio_disable_##n(const struct device *dev) { \
- COND_POLL_OR_INTERRUPTS((struct kscan_gpio_data_##n *data = dev->data; \
- k_timer_stop(&data->poll_timer); return 0;), \
- (return kscan_gpio_disable_interrupts_##n(dev);)) \
- }; \
- COND_POLLING(static void kscan_gpio_timer_handler_##n(struct k_timer *timer) { \
- 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)}, \
+ static const struct kscan_gpio_dt_spec kscan_matrix_cols_##index[] = { \
+ UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
+ \
+ static bool kscan_current_state_##index[INST_MATRIX_LEN(index)]; \
+ static bool kscan_next_state_##index[INST_MATRIX_LEN(index)]; \
+ \
+ COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
+ kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
+ \
+ static struct kscan_matrix_data kscan_matrix_data_##index = { \
+ .current_state = kscan_current_state_##index, \
+ .next_state = kscan_next_state_##index, \
+ COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
+ \
+ static struct kscan_matrix_config kscan_matrix_config_##index = { \
+ .rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##index), \
+ .cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##index), \
+ .inputs = KSCAN_GPIO_LIST( \
+ COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
+ .outputs = KSCAN_GPIO_LIST( \
+ COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
+ .debounce_period_ms = DT_INST_PROP(index, debounce_period), \
+ .poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
+ .diode_direction = INST_DIODE_DIR(index), \
}; \
- 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, \
- &gpio_driver_api_##n);
+ \
+ DEVICE_DT_INST_DEFINE(index, &kscan_matrix_init, device_pm_control_nop, \
+ &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)
diff --git a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml
index 5ebcbdd..20ee4ac 100644
--- a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml
+++ b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml
@@ -17,6 +17,11 @@ properties:
debounce-period:
type: int
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:
type: string
default: row2col