diff --git a/app/drivers/kscan/kscan_gpio_matrix.c b/app/drivers/kscan/kscan_gpio_matrix.c index 69859538..5465dd30 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 -#include +#include #include +#include #include +#include +#include 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 5ebcbdd3..20ee4ac5 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