diff --git a/app/drivers/kscan/CMakeLists.txt b/app/drivers/kscan/CMakeLists.txt index ced31e6f..8fc7ed58 100644 --- a/app/drivers/kscan/CMakeLists.txt +++ b/app/drivers/kscan/CMakeLists.txt @@ -5,6 +5,7 @@ zephyr_library_named(zmk__drivers__kscan) 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 kscan_gpio.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_DEMUX kscan_gpio_demux.c) diff --git a/app/drivers/kscan/kscan_gpio.c b/app/drivers/kscan/kscan_gpio.c new file mode 100644 index 00000000..4963f678 --- /dev/null +++ b/app/drivers/kscan/kscan_gpio.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "kscan_gpio.h" + +#include + +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; +} diff --git a/app/drivers/kscan/kscan_gpio.h b/app/drivers/kscan/kscan_gpio.h new file mode 100644 index 00000000..39343136 --- /dev/null +++ b/app/drivers/kscan/kscan_gpio.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +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); diff --git a/app/drivers/kscan/kscan_gpio_direct.c b/app/drivers/kscan/kscan_gpio_direct.c index 586e0d95..d43d716b 100644 --- a/app/drivers/kscan/kscan_gpio_direct.c +++ b/app/drivers/kscan/kscan_gpio_direct.c @@ -5,6 +5,7 @@ */ #include "debounce.h" +#include "kscan_gpio.h" #include #include @@ -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 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 { const struct device *dev; @@ -50,6 +51,7 @@ struct kscan_direct_irq_callback { struct kscan_direct_data { const struct device *dev; + struct kscan_gpio_list inputs; kscan_callback_t callback; struct k_work_delayable work; #if USE_INTERRUPTS @@ -62,17 +64,7 @@ struct kscan_direct_data { 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_gpio_list inputs; struct debounce_config debounce_config; int32_t debounce_scan_period_ms; int32_t poll_period_ms; @@ -81,10 +73,10 @@ struct kscan_direct_config { #if USE_INTERRUPTS 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++) { - const struct gpio_dt_spec *gpio = &config->inputs.gpios[i]; + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; int err = gpio_pin_interrupt_configure_dt(gpio, flags); 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, const struct gpio_dt_spec *active_gpio) { - gpio_flags_t extra_flags; for (int i = 0; i < inputs->len; i++) { - extra_flags = GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i], - &inputs->gpios[i] == active_gpio); + const bool active = &inputs->gpios[i].spec == 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); - 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) { - LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].pin, - inputs->gpios[i].port->name); + LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].spec.pin, + inputs->gpios[i].spec.port->name); return err; } } @@ -179,28 +171,35 @@ static int kscan_direct_read(const struct device *dev) { const struct kscan_direct_config *config = dev->config; // Read the inputs. - for (int i = 0; i < config->inputs.len; i++) { - const struct gpio_dt_spec *gpio = &config->inputs.gpios[i]; + struct kscan_gpio_port_state state = {0}; - 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); } // Process the new state. bool continue_scan = false; - for (int i = 0; i < config->inputs.len; i++) { - struct debounce_state *state = &data->pin_state[i]; + for (int i = 0; i < data->inputs.len; i++) { + const struct kscan_gpio *gpio = &data->inputs.gpios[i]; + struct debounce_state *state = &data->pin_state[gpio->index]; if (debounce_get_changed(state)) { const bool pressed = debounce_is_pressed(state); - LOG_DBG("Sending event at 0,%i state %s", i, pressed ? "on" : "off"); - data->callback(dev, 0, i, pressed); + LOG_DBG("Sending event at 0,%i state %s", gpio->index, pressed ? "on" : "off"); + data->callback(dev, 0, gpio->index, 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) { + const struct kscan_direct_data *data = dev->data; const struct kscan_direct_config *config = dev->config; - for (int i = 0; i < config->inputs.len; i++) { - const struct gpio_dt_spec *gpio = &config->inputs.gpios[i]; + for (int i = 0; i < data->inputs.len; 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); if (err) { return err; @@ -307,6 +307,9 @@ static int kscan_direct_init(const struct device *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); 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, \ "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)}; \ \ 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_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 = { \ - .inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \ .debounce_config = \ { \ .debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \ diff --git a/app/drivers/kscan/kscan_gpio_matrix.c b/app/drivers/kscan/kscan_gpio_matrix.c index d2121273..b8e72044 100644 --- a/app/drivers/kscan/kscan_gpio_matrix.c +++ b/app/drivers/kscan/kscan_gpio_matrix.c @@ -5,6 +5,7 @@ */ #include "debounce.h" +#include "kscan_gpio.h" #include #include @@ -50,9 +51,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode) #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) \ - 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 { KSCAN_ROW2COL, @@ -66,6 +67,7 @@ struct kscan_matrix_irq_callback { struct kscan_matrix_data { const struct device *dev; + struct kscan_gpio_list inputs; kscan_callback_t callback; struct k_work_delayable work; #if USE_INTERRUPTS @@ -76,26 +78,16 @@ struct kscan_matrix_data { int64_t scan_time; /** * 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 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_gpio_list rows; - struct kscan_gpio_list cols; - struct kscan_gpio_list inputs; struct kscan_gpio_list outputs; struct debounce_config debounce_config; + size_t rows; + size_t cols; int32_t debounce_scan_period_ms; int32_t poll_period_ms; 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. */ 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); + __ASSERT(row < config->rows, "Invalid row %i", row); + __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; 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); if (err) { @@ -139,10 +131,10 @@ static int kscan_matrix_set_all_outputs(const struct device *dev, const int valu #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; + const struct kscan_matrix_data *data = dev->data; - for (int i = 0; i < config->inputs.len; i++) { - const struct gpio_dt_spec *gpio = &config->inputs.gpios[i]; + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; int err = gpio_pin_interrupt_configure_dt(gpio, flags); if (err) { @@ -226,32 +218,37 @@ static int kscan_matrix_read(const struct device *dev) { const struct kscan_matrix_config *config = dev->config; // Scan the matrix. - for (int o = 0; o < config->outputs.len; o++) { - const struct gpio_dt_spec *out_gpio = &config->outputs.gpios[o]; + for (int i = 0; i < config->outputs.len; i++) { + 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) { - 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; } #if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0 k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS); #endif + struct kscan_gpio_port_state state = {0}; - for (int i = 0; i < config->inputs.len; i++) { - const struct gpio_dt_spec *in_gpio = &config->inputs.gpios[i]; + for (int j = 0; j < data->inputs.len; j++) { + const struct kscan_gpio *in_gpio = &data->inputs.gpios[j]; - const int index = state_index_io(config, i, o); - const bool active = gpio_pin_get_dt(in_gpio); + const int index = state_index_io(config, in_gpio->index, out_gpio->index); + 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, &config->debounce_config); } - err = gpio_pin_set_dt(out_gpio, 0); + err = gpio_pin_set_dt(&out_gpio->spec, 0); 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; } @@ -263,8 +260,8 @@ static int kscan_matrix_read(const struct device *dev) { // Process the new state. bool continue_scan = false; - for (int r = 0; r < config->rows.len; r++) { - for (int c = 0; c < config->cols.len; c++) { + for (int r = 0; r < config->rows; r++) { + for (int c = 0; c < config->cols; c++) { const int index = state_index_rc(config, r, c); struct debounce_state *state = &data->matrix_state[index]; @@ -329,28 +326,28 @@ static int kscan_matrix_disable(const struct device *dev) { #endif } -static int kscan_matrix_init_input_inst(const struct device *dev, const struct gpio_dt_spec *gpio, - const int index) { - if (!device_is_ready(gpio->port)) { - LOG_ERR("GPIO is not ready: %s", gpio->port->name); +static int kscan_matrix_init_input_inst(const struct device *dev, const struct kscan_gpio *gpio) { + if (!device_is_ready(gpio->spec.port)) { + LOG_ERR("GPIO is not ready: %s", gpio->spec.port->name); return -ENODEV; } - int err = gpio_pin_configure_dt(gpio, GPIO_INPUT); + int err = gpio_pin_configure_dt(&gpio->spec, GPIO_INPUT); 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; } - 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 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; - gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin)); - err = gpio_add_callback(gpio->port, &irq->callback); + gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->spec.pin)); + err = gpio_add_callback(gpio->spec.port, &irq->callback); if (err) { LOG_ERR("Error adding the callback to the input device: %i", 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) { - 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++) { - const struct gpio_dt_spec *gpio = &config->inputs.gpios[i]; - int err = kscan_matrix_init_input_inst(dev, gpio, i); + for (int i = 0; i < data->inputs.len; i++) { + const struct kscan_gpio *gpio = &data->inputs.gpios[i]; + int err = kscan_matrix_init_input_inst(dev, gpio); if (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; 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); if (err) { return err; @@ -411,6 +408,9 @@ static int kscan_matrix_init(const struct device *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_outputs(dev); 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, \ "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)}; \ \ - 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)}; \ \ 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_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, \ COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \ \ static struct kscan_matrix_config kscan_matrix_config_##n = { \ - .rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##n), \ - .cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##n), \ - .inputs = \ - KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \ + .rows = ARRAY_SIZE(kscan_matrix_rows_##n), \ + .cols = ARRAY_SIZE(kscan_matrix_cols_##n), \ .outputs = \ KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \ .debounce_config = \