From 3ea866791a3e68bda9c76b6c90357d8a42d872bc Mon Sep 17 00:00:00 2001 From: Hooky Date: Sat, 4 Mar 2023 12:45:38 +0800 Subject: [PATCH 01/14] Add and enable Multiplex kscan code Multiplex handler (all->all) with single pin for interrupt handling. For wired boards/shields, the interrupt can be ignored to simplify the electronics greatly. --- app/drivers/kscan/kscan_gpio_multiplex.c | 412 ++++++++++++++++++ .../kscan/zmk,kscan-gpio-multiplex.yaml | 32 ++ app/module/drivers/kscan/CMakeLists.txt | 1 + app/module/drivers/kscan/Kconfig | 33 ++ 4 files changed, 478 insertions(+) create mode 100644 app/drivers/kscan/kscan_gpio_multiplex.c create mode 100644 app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml diff --git a/app/drivers/kscan/kscan_gpio_multiplex.c b/app/drivers/kscan/kscan_gpio_multiplex.c new file mode 100644 index 00000000000..59920e9fb9c --- /dev/null +++ b/app/drivers/kscan/kscan_gpio_multiplex.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020-2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "debounce.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define DT_DRV_COMPAT zmk_kscan_gpio_multiplex + +#define INST_LEN(n) DT_INST_PROP_LEN(n, gpios) +#define INST_MULTIPLEX_LEN(n) (INST_LEN(n) * (INST_LEN(n) - 1)) + +#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0 +#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS +#else +#define INST_DEBOUNCE_PRESS_MS(n) \ + DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms)) +#endif + +#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0 +#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS +#else +#define INST_DEBOUNCE_RELEASE_MS(n) \ + DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms)) +#endif + +#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING) +#define USE_INTERRUPT (!USE_POLLING) + +#define COND_INTERRUPT(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING, (), code) + +#define KSCAN_GPIO_CFG_INIT(idx, inst_idx) \ + GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx), + +#define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios) + +struct kscan_multiplex_data { + const struct device *dev; + kscan_callback_t callback; + struct k_work_delayable work; + int64_t scan_time; /* Timestamp of the current or scheduled scan. */ +#if USE_INTERRUPT + struct gpio_callback irq_callback; +#endif + /** + * Current state of the matrix as a flattened 2D array of length + * (config->cells.length ^2) + */ + struct debounce_state *multiplex_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_multiplex_config { + struct kscan_gpio_list cells; + struct debounce_config debounce_config; + int32_t debounce_scan_period_ms; + int32_t poll_period_ms; +#if USE_INTERRUPT + const struct gpio_dt_spec interrupt; +#endif +}; + +/** + * Get the index into a matrix state array from a row and column. + * There are effectively (n) cols and (n-1) rows, but we use the full col x row space + * as a safety measure against someone accidentally defining a transform RC at (p,p) + */ +static int state_index(const struct kscan_multiplex_config *config, const int row, const int col) { + __ASSERT(row < config->cells.len, "Invalid row %i", row); + __ASSERT(col < config->cells.len, "Invalid column %i", col); + __ASSERT(col != row, "Invalid column row pair %i, %i", col, row); + + return (col * config->cells.len) + row; +} + +static int kscan_multiplex_set_as_input(const struct 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_dt(gpio, GPIO_INPUT); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name); + return err; + } + return 0; +} + +static int kscan_multiplex_set_as_output(const struct 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_dt(gpio, GPIO_OUTPUT); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name); + return err; + } +#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS); +#endif + err = gpio_pin_set_dt(gpio, 1); + if (err) { + LOG_ERR("Failed to set output pin %u active: %i", gpio->pin, err); + } + return err; +} + +static int kscan_multiplex_set_all_as_input(const struct device *dev) { + const struct kscan_multiplex_config *config = dev->config; + int err = 0; + for (int i = 0; i < config->cells.len; i++) { + err = kscan_multiplex_set_as_input(&config->cells.gpios[i]); + if (err) { + return err; + } + } + + return 0; +} + +static int kscan_multiplex_set_all_outputs(const struct device *dev, const int value) { + const struct kscan_multiplex_config *config = dev->config; + + for (int i = 0; i < config->cells.len; i++) { + const struct gpio_dt_spec *gpio = &config->cells.gpios[i]; + int err = gpio_pin_configure_dt(gpio, GPIO_OUTPUT); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name); + return err; + } + + err = gpio_pin_set_dt(gpio, value); + if (err) { + LOG_ERR("Failed to set output %i to %i: %i", i, value, err); + return err; + } + } + + return 0; +} + +#if USE_INTERRUPT +static int kscan_multiplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) { + const struct kscan_multiplex_config *config = dev->config; + const struct gpio_dt_spec *gpio = &config->interrupt; + + int err = gpio_pin_interrupt_configure_dt(gpio, flags); + if (err) { + LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name); + return err; + } + + return 0; +} +#endif + +#if USE_INTERRUPT +static int kscan_multiplex_interrupt_enable(const struct device *dev) { + int err = kscan_multiplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); + if (err) { + return err; + } + + // While interrupts are enabled, set all outputs active so an pressed key will trigger + return kscan_multiplex_set_all_outputs(dev, 1); +} +#endif + +#if USE_INTERRUPT +static void kscan_multiplex_irq_callback(const struct device *port, struct gpio_callback *cb, + const gpio_port_pins_t _pin) { + struct kscan_multiplex_data *data = CONTAINER_OF(cb, struct kscan_multiplex_data, irq_callback); + + // Disable our interrupt to avoid re-entry while we scan. + kscan_multiplex_interrupt_configure(data->dev, GPIO_INT_DISABLE); + data->scan_time = k_uptime_get(); + k_work_reschedule(&data->work, K_NO_WAIT); +} +#endif + +static void kscan_multiplex_read_continue(const struct device *dev) { + const struct kscan_multiplex_config *config = dev->config; + struct kscan_multiplex_data *data = dev->data; + + data->scan_time += config->debounce_scan_period_ms; + + k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); +} + +static void kscan_multiplex_read_end(const struct device *dev) { +#if USE_INTERRUPT + // Return to waiting for an interrupt. + kscan_multiplex_interrupt_enable(dev); +#else + struct kscan_multiplex_data *data = dev->data; + const struct kscan_multiplex_config *config = dev->config; + + data->scan_time += config->poll_period_ms; + + // Return to polling slowly. + k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); +#endif +} + +static int kscan_multiplex_read(const struct device *dev) { + struct kscan_multiplex_data *data = dev->data; + const struct kscan_multiplex_config *config = dev->config; + bool continue_scan = false; + + // NOTE: MULTI vs MATRIX: set all pins as input, in case there was a failure on a + // previous scan, and one of the pins is still set as output + int err = kscan_multiplex_set_all_as_input(dev); + if (err) { + return err; + } + + // Scan the matrix. + for (int row = 0; row < config->cells.len; row++) { + const struct gpio_dt_spec *out_gpio = &config->cells.gpios[row]; + err = kscan_multiplex_set_as_output(out_gpio); + if (err) { + return err; + } + +#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS); +#endif + + for (int col = 0; col < config->cells.len; col++) { + if (col == row) { + continue; // pin can't drive itself + } + const struct gpio_dt_spec *in_gpio = &config->cells.gpios[col]; + const int index = state_index(config, row, col); + + struct debounce_state *state = &data->multiplex_state[index]; + debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms, + &config->debounce_config); + + // NOTE: MULTI vs MATRIX: because we don't need an input/output => row/column + // setup, we can update in the same loop. + if (debounce_get_changed(state)) { + const bool pressed = debounce_is_pressed(state); + + LOG_DBG("Sending event at %i,%i state %s", row, col, pressed ? "on" : "off"); + data->callback(dev, row, col, pressed); + } + continue_scan = continue_scan || debounce_is_active(state); + } + + err = kscan_multiplex_set_as_input(out_gpio); + if (err) { + return err; + } +#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS); +#endif + } + + if (continue_scan) { + // At least one key is pressed or the debouncer has not yet decided if + // it is pressed. Poll quickly until everything is released. + kscan_multiplex_read_continue(dev); + } else { + // All keys are released. Return to normal. + kscan_multiplex_read_end(dev); + } + + return 0; +} + +static void kscan_multiplex_work_handler(struct k_work *work) { + struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work); + struct kscan_multiplex_data *data = CONTAINER_OF(dwork, struct kscan_multiplex_data, work); + kscan_multiplex_read(data->dev); +} + +static int kscan_multiplex_configure(const struct device *dev, const kscan_callback_t callback) { + if (!callback) { + return -EINVAL; + } + + struct kscan_multiplex_data *data = dev->data; + data->callback = callback; + return 0; +} + +static int kscan_multiplex_enable(const struct device *dev) { + struct kscan_multiplex_data *data = dev->data; + data->scan_time = k_uptime_get(); + + // Read will automatically start interrupts/polling once done. + return kscan_multiplex_read(dev); +} + +static int kscan_multiplex_disable(const struct device *dev) { + struct kscan_multiplex_data *data = dev->data; + k_work_cancel_delayable(&data->work); + +#if USE_INTERRUPT + return kscan_multiplex_interrupt_configure(dev, GPIO_INT_DISABLE); +#else + return 0; +#endif +} + +static int kscan_multiplex_init_inputs(const struct device *dev) { + const struct kscan_multiplex_config *config = dev->config; + + for (int i = 0; i < config->cells.len; i++) { + int err = kscan_multiplex_set_as_input(&config->cells.gpios[i]); + if (err) { + return err; + } + } + + return 0; +} + +#if USE_INTERRUPT +static int kscan_multiplex_init_interrupt(const struct device *dev) { + struct kscan_multiplex_data *data = dev->data; + + const struct kscan_multiplex_config *config = dev->config; + const struct gpio_dt_spec *gpio = &config->interrupt; + int err = kscan_multiplex_set_as_input(gpio); + if (err) { + return err; + } + + gpio_init_callback(&data->irq_callback, kscan_multiplex_irq_callback, BIT(gpio->pin)); + err = gpio_add_callback(gpio->port, &data->irq_callback); + if (err) { + LOG_ERR("Error adding the callback to the input device: %i", err); + } + return err; +} +#endif + +static int kscan_multiplex_init(const struct device *dev) { + struct kscan_multiplex_data *data = dev->data; + + data->dev = dev; + + kscan_multiplex_init_inputs(dev); + kscan_multiplex_set_all_outputs(dev, 0); +#if USE_INTERRUPT + kscan_multiplex_init_interrupt(dev); +#endif + + k_work_init_delayable(&data->work, kscan_multiplex_work_handler); + return 0; +} + +static const struct kscan_driver_api kscan_multiplex_api = { + .config = kscan_multiplex_configure, + .enable_callback = kscan_multiplex_enable, + .disable_callback = kscan_multiplex_disable, +}; + +#define KSCAN_MULTIPLEX_INIT(n) \ + BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \ + "ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \ + BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \ + "ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \ + \ + static struct debounce_state kscan_multiplex_state_##n[INST_MULTIPLEX_LEN(n)]; \ + static const struct gpio_dt_spec kscan_multiplex_cells_##n[] = { \ + UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \ + static struct kscan_multiplex_data kscan_multiplex_data_##n = { \ + .multiplex_state = kscan_multiplex_state_##n, \ + }; \ + \ + static struct kscan_multiplex_config kscan_multiplex_config_##n = { \ + .cells = KSCAN_GPIO_LIST(kscan_multiplex_cells_##n), \ + .debounce_config = \ + { \ + .debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \ + .debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \ + }, \ + .debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \ + .poll_period_ms = DT_INST_PROP(n, poll_period_ms), \ + COND_INTERRUPT((.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ + \ + DEVICE_DT_INST_DEFINE(n, &kscan_multiplex_init, NULL, &kscan_multiplex_data_##n, \ + &kscan_multiplex_config_##n, APPLICATION, \ + CONFIG_APPLICATION_INIT_PRIORITY, &kscan_multiplex_api); + +DT_INST_FOREACH_STATUS_OKAY(KSCAN_MULTIPLEX_INIT); diff --git a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml new file mode 100644 index 00000000000..aced5d50f97 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml @@ -0,0 +1,32 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: GPIO keyboard full multiplexed matrix controller + +compatible: "zmk,kscan-gpio-multiplex" + +include: kscan.yaml + +properties: + gpios: + type: phandle-array + required: true + interrupt-gpios: + type: phandle-array + required: true + debounce-press-ms: + type: int + default: 5 + description: Debounce time for key press in milliseconds. Use 0 for eager debouncing. + debounce-release-ms: + type: int + default: 5 + description: Debounce time for key release in milliseconds. + debounce-scan-period-ms: + type: int + default: 1 + description: Time between reads in milliseconds when any key is pressed. + poll-period-ms: + type: int + default: 1 + description: Time between reads in milliseconds diff --git a/app/module/drivers/kscan/CMakeLists.txt b/app/module/drivers/kscan/CMakeLists.txt index 7ae9524c75f..08b0603e864 100644 --- a/app/module/drivers/kscan/CMakeLists.txt +++ b/app/module/drivers/kscan/CMakeLists.txt @@ -9,3 +9,4 @@ zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c) +zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MULTIPLEX kscan_gpio_multiplex.c) diff --git a/app/module/drivers/kscan/Kconfig b/app/module/drivers/kscan/Kconfig index 6f60b3f919f..0da0588d668 100644 --- a/app/module/drivers/kscan/Kconfig +++ b/app/module/drivers/kscan/Kconfig @@ -5,6 +5,7 @@ DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix +DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX := zmk,kscan-gpio-multiplex DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock if KSCAN @@ -33,6 +34,11 @@ config ZMK_KSCAN_GPIO_MATRIX default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX)) select ZMK_KSCAN_GPIO_DRIVER +config ZMK_KSCAN_GPIO_MULTIPLEX + bool + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX)) + select ZMK_KSCAN_GPIO_DRIVER + if ZMK_KSCAN_GPIO_MATRIX config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS @@ -58,6 +64,30 @@ config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS endif # ZMK_KSCAN_GPIO_MATRIX +if ZMK_KSCAN_GPIO_MULTIPLEX + +config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS + int "Ticks to wait before reading inputs after an output set active" + default 0 + help + When iterating over each output to drive it active, read inputs, then set + inactive again, some boards may take time for output to propagate to the + inputs. In that scenario, set this value to a positive value to configure + the number of ticks to wait after setting an output active before reading + the inputs for their active state. + +config ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS + int "Ticks to wait between each output when scanning round robin matrix" + default 0 + help + When iterating over each output to drive it active, read inputs, then set + inactive again, some boards may take time for the previous output to + "settle" before reading inputs for the next active output column. In that + scenario, set this value to a positive value to configure the number of + usecs to wait after reading each column of keys. + +endif # ZMK_KSCAN_GPIO_MULTIPLEX + config ZMK_KSCAN_MOCK_DRIVER bool default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK)) @@ -67,6 +97,9 @@ if ZMK_KSCAN_GPIO_DRIVER config ZMK_KSCAN_MATRIX_POLLING bool "Poll for key event triggers instead of using interrupts on matrix boards." +config ZMK_KSCAN_MULTIPLEX_POLLING + bool "Poll for key event triggers instead of using interrupts on multiplex boards." + config ZMK_KSCAN_DIRECT_POLLING bool "Poll for key event triggers instead of using interrupts on direct wired boards." From 555a806a0d11a42a94ec1154f02455296314c67f Mon Sep 17 00:00:00 2001 From: Hooky Date: Sat, 4 Mar 2023 12:47:46 +0800 Subject: [PATCH 02/14] Add docs for Multiplex kscan Per the other docs, this lays out how to implement it in the settings files, not how to implement it in hardware. --- docs/docs/config/kscan.md | 89 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 96a483ff902..f05a0c55e99 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -149,6 +149,41 @@ The output pins (e.g. columns for `col2row`) should have the flag `GPIO_ACTIVE_H }; ``` +## Multiplex Driver + +Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output. This driver enables n pins to drive up to n\*(n-1) keys. + +Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` | bool | Poll for key presses instead of using interrupts | n | +| `CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | +| `CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | + +- With `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` enabled this allows n pins to drive n\*(n-1) keys. +- With `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` disabled n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. + +### Devicetree + +Applies to: `compatible = "zmk,kscan-gpio-multiplex"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-multiplex.yaml) + +| Property | Type | Description | Default | +| ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `gpios` | GPIO array | GPIOs used, listed in order | | +| `interrupt-gpios` | GPIO array | A single GPIO to use for interrupt | | +| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | +| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | +| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | +| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_MATRIX_POLLING` is enabled. | 10 | + +Define the transform with a [matrix transform](#matrix-transform). The row is always the driven pin, and the column always the receiving pin (input to the controller). +For example, in `RC(5,0)` power flows from the 6th pin in `gpios` to the 1st pin in `gpios`. +Exclude all positions where the row and column are the same as these pairs will never be triggered, since no pin can be both input and output at the same time. + ## Composite Driver Keyboard scan driver which combines multiple other keyboard scan drivers. @@ -393,12 +428,54 @@ Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-desig // Shift Z X C ... // Ctrl Alt ... map = < - RC(0,0) RC(1,0) RC(0,1) RC(1,1) // ... - RC(2,0) RC(3,0) RC(2,1) RC(3,1) // ... - RC(4,0) RC(5,0) RC(4,1) RC(5,1) // ... - RC(6,0) RC(7,0) RC(6,1) RC(7,1) // ... - RC(8,0) RC(8,1) RC(9,1) // ... - RC(10,0) RC(11,0) // ... + RC(0,0) RC(1,0) RC(0,1) RC(1,1) // ... + RC(2,0) RC(3,0) RC(2,1) RC(3,1) // ... + RC(4,0) RC(5,0) RC(4,1) RC(5,1) // ... + RC(6,0) RC(7,0) RC(6,1) RC(7,1) // ... + RC(8,0) RC(9,0) RC(8,1) RC(9,1) // ... + RC(10,0) RC(11,0) // ... + >; + }; +}; +``` + +### Example: Multiplex + +Since a multiplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys. +Note that the entire addressable space does not need to be mapped. + +```devicetree +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-multiplex"; + label = "KSCAN"; + + interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) >; + gpios + = <&pro_micro 16 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + , <&pro_micro 17 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + , <&pro_micro 18 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + , <&pro_micro 19 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + , <&pro_micro 20 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + ; // addressable space is 5x5, (minus paired values) + }; + + default_transform: matrix_transform { + compatible = "zmk,matrix-transform"; + rows = <3>; + columns = <5>; + // Q W E R + // A S D F + // Z X C V + map = < + RC(0,1) RC(0,2) RC(0,3) RC(0,4) + RC(1,0) RC(1,2) RC(1,3) RC(1,4) + RC(2,0) RC(2,1) RC(2,3) RC(2,4) >; }; }; From a25505a75ae19e47fa036800af71a6cd38e14f15 Mon Sep 17 00:00:00 2001 From: Hooky Date: Sat, 4 Mar 2023 12:48:32 +0800 Subject: [PATCH 03/14] Fix array overflow The _used_ size was correct, but the addressing requires n*n. --- app/drivers/kscan/kscan_gpio_multiplex.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/drivers/kscan/kscan_gpio_multiplex.c b/app/drivers/kscan/kscan_gpio_multiplex.c index 59920e9fb9c..3824942b098 100644 --- a/app/drivers/kscan/kscan_gpio_multiplex.c +++ b/app/drivers/kscan/kscan_gpio_multiplex.c @@ -20,7 +20,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define DT_DRV_COMPAT zmk_kscan_gpio_multiplex #define INST_LEN(n) DT_INST_PROP_LEN(n, gpios) -#define INST_MULTIPLEX_LEN(n) (INST_LEN(n) * (INST_LEN(n) - 1)) +#define INST_MULTIPLEX_LEN(n) (INST_LEN(n) * INST_LEN(n)) #if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0 #define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS @@ -118,9 +118,7 @@ static int kscan_multiplex_set_as_output(const struct gpio_dt_spec *gpio) { LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name); return err; } -#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0 - k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS); -#endif + err = gpio_pin_set_dt(gpio, 1); if (err) { LOG_ERR("Failed to set output pin %u active: %i", gpio->pin, err); From 087774c7831321d15cbc5da2f60afacb61eb6623 Mon Sep 17 00:00:00 2001 From: Hooky Date: Sat, 4 Mar 2023 12:49:32 +0800 Subject: [PATCH 04/14] Switch naming to charliplex --- app/module/drivers/kscan/CMakeLists.txt | 2 +- app/module/drivers/kscan/Kconfig | 16 +- .../drivers/kscan/kscan_gpio_charliplex.c} | 174 +++++++++--------- .../kscan/zmk,kscan-gpio-charliplex.yaml} | 4 +- docs/docs/config/kscan.md | 26 +-- 5 files changed, 112 insertions(+), 110 deletions(-) rename app/{drivers/kscan/kscan_gpio_multiplex.c => module/drivers/kscan/kscan_gpio_charliplex.c} (62%) rename app/{drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml => module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml} (87%) diff --git a/app/module/drivers/kscan/CMakeLists.txt b/app/module/drivers/kscan/CMakeLists.txt index 08b0603e864..4e562a1bc8c 100644 --- a/app/module/drivers/kscan/CMakeLists.txt +++ b/app/module/drivers/kscan/CMakeLists.txt @@ -5,8 +5,8 @@ zephyr_library_amend() 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_CHARLIPLEX kscan_gpio_charliplex.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c) -zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MULTIPLEX kscan_gpio_multiplex.c) diff --git a/app/module/drivers/kscan/Kconfig b/app/module/drivers/kscan/Kconfig index 0da0588d668..23cdb59ec13 100644 --- a/app/module/drivers/kscan/Kconfig +++ b/app/module/drivers/kscan/Kconfig @@ -5,7 +5,7 @@ DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix -DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX := zmk,kscan-gpio-multiplex +DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIPLEX := zmk,kscan-gpio-charliplex DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock if KSCAN @@ -34,9 +34,9 @@ config ZMK_KSCAN_GPIO_MATRIX default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX)) select ZMK_KSCAN_GPIO_DRIVER -config ZMK_KSCAN_GPIO_MULTIPLEX +config ZMK_KSCAN_GPIO_CHARLIPLEX bool - default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MULTIPLEX)) + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIPLEX)) select ZMK_KSCAN_GPIO_DRIVER if ZMK_KSCAN_GPIO_MATRIX @@ -64,7 +64,7 @@ config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS endif # ZMK_KSCAN_GPIO_MATRIX -if ZMK_KSCAN_GPIO_MULTIPLEX +if ZMK_KSCAN_GPIO_CHARLIPLEX config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS int "Ticks to wait before reading inputs after an output set active" @@ -76,8 +76,8 @@ config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS the number of ticks to wait after setting an output active before reading the inputs for their active state. -config ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS - int "Ticks to wait between each output when scanning round robin matrix" +config ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS + int "Ticks to wait between each output when scanning charliplex matrix" default 0 help When iterating over each output to drive it active, read inputs, then set @@ -86,7 +86,7 @@ config ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS scenario, set this value to a positive value to configure the number of usecs to wait after reading each column of keys. -endif # ZMK_KSCAN_GPIO_MULTIPLEX +endif # ZMK_KSCAN_GPIO_CHARLIPLEX config ZMK_KSCAN_MOCK_DRIVER bool @@ -97,7 +97,7 @@ if ZMK_KSCAN_GPIO_DRIVER config ZMK_KSCAN_MATRIX_POLLING bool "Poll for key event triggers instead of using interrupts on matrix boards." -config ZMK_KSCAN_MULTIPLEX_POLLING +config ZMK_KSCAN_CHARLIPLEX_POLLING bool "Poll for key event triggers instead of using interrupts on multiplex boards." config ZMK_KSCAN_DIRECT_POLLING diff --git a/app/drivers/kscan/kscan_gpio_multiplex.c b/app/module/drivers/kscan/kscan_gpio_charliplex.c similarity index 62% rename from app/drivers/kscan/kscan_gpio_multiplex.c rename to app/module/drivers/kscan/kscan_gpio_charliplex.c index 3824942b098..43dc3a81be2 100644 --- a/app/drivers/kscan/kscan_gpio_multiplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charliplex.c @@ -17,10 +17,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#define DT_DRV_COMPAT zmk_kscan_gpio_multiplex +#define DT_DRV_COMPAT zmk_kscan_gpio_charliplex #define INST_LEN(n) DT_INST_PROP_LEN(n, gpios) -#define INST_MULTIPLEX_LEN(n) (INST_LEN(n) * INST_LEN(n)) +#define INST_CHARLIPLEX_LEN(n) (INST_LEN(n) * INST_LEN(n)) #if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0 #define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS @@ -36,17 +36,17 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms)) #endif -#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING) +#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING) #define USE_INTERRUPT (!USE_POLLING) -#define COND_INTERRUPT(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING, (), code) +#define COND_INTERRUPT(code) COND_CODE_1(CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING, (), code) #define KSCAN_GPIO_CFG_INIT(idx, inst_idx) \ GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx), #define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios) -struct kscan_multiplex_data { +struct kscan_charliplex_data { const struct device *dev; kscan_callback_t callback; struct k_work_delayable work; @@ -58,7 +58,7 @@ struct kscan_multiplex_data { * Current state of the matrix as a flattened 2D array of length * (config->cells.length ^2) */ - struct debounce_state *multiplex_state; + struct debounce_state *charliplex_state; }; struct kscan_gpio_list { @@ -70,7 +70,7 @@ struct kscan_gpio_list { #define KSCAN_GPIO_LIST(gpio_array) \ ((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)}) -struct kscan_multiplex_config { +struct kscan_charliplex_config { struct kscan_gpio_list cells; struct debounce_config debounce_config; int32_t debounce_scan_period_ms; @@ -85,7 +85,7 @@ struct kscan_multiplex_config { * There are effectively (n) cols and (n-1) rows, but we use the full col x row space * as a safety measure against someone accidentally defining a transform RC at (p,p) */ -static int state_index(const struct kscan_multiplex_config *config, const int row, const int col) { +static int state_index(const struct kscan_charliplex_config *config, const int row, const int col) { __ASSERT(row < config->cells.len, "Invalid row %i", row); __ASSERT(col < config->cells.len, "Invalid column %i", col); __ASSERT(col != row, "Invalid column row pair %i, %i", col, row); @@ -93,7 +93,7 @@ static int state_index(const struct kscan_multiplex_config *config, const int ro return (col * config->cells.len) + row; } -static int kscan_multiplex_set_as_input(const struct gpio_dt_spec *gpio) { +static int kscan_charliplex_set_as_input(const struct gpio_dt_spec *gpio) { if (!device_is_ready(gpio->port)) { LOG_ERR("GPIO is not ready: %s", gpio->port->name); return -ENODEV; @@ -107,7 +107,7 @@ static int kscan_multiplex_set_as_input(const struct gpio_dt_spec *gpio) { return 0; } -static int kscan_multiplex_set_as_output(const struct gpio_dt_spec *gpio) { +static int kscan_charliplex_set_as_output(const struct gpio_dt_spec *gpio) { if (!device_is_ready(gpio->port)) { LOG_ERR("GPIO is not ready: %s", gpio->port->name); return -ENODEV; @@ -126,11 +126,11 @@ static int kscan_multiplex_set_as_output(const struct gpio_dt_spec *gpio) { return err; } -static int kscan_multiplex_set_all_as_input(const struct device *dev) { - const struct kscan_multiplex_config *config = dev->config; +static int kscan_charliplex_set_all_as_input(const struct device *dev) { + const struct kscan_charliplex_config *config = dev->config; int err = 0; for (int i = 0; i < config->cells.len; i++) { - err = kscan_multiplex_set_as_input(&config->cells.gpios[i]); + err = kscan_charliplex_set_as_input(&config->cells.gpios[i]); if (err) { return err; } @@ -139,8 +139,8 @@ static int kscan_multiplex_set_all_as_input(const struct device *dev) { return 0; } -static int kscan_multiplex_set_all_outputs(const struct device *dev, const int value) { - const struct kscan_multiplex_config *config = dev->config; +static int kscan_charliplex_set_all_outputs(const struct device *dev, const int value) { + const struct kscan_charliplex_config *config = dev->config; for (int i = 0; i < config->cells.len; i++) { const struct gpio_dt_spec *gpio = &config->cells.gpios[i]; @@ -161,8 +161,9 @@ static int kscan_multiplex_set_all_outputs(const struct device *dev, const int v } #if USE_INTERRUPT -static int kscan_multiplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) { - const struct kscan_multiplex_config *config = dev->config; +static int kscan_charliplex_interrupt_configure(const struct device *dev, + const gpio_flags_t flags) { + const struct kscan_charliplex_config *config = dev->config; const struct gpio_dt_spec *gpio = &config->interrupt; int err = gpio_pin_interrupt_configure_dt(gpio, flags); @@ -176,45 +177,46 @@ static int kscan_multiplex_interrupt_configure(const struct device *dev, const g #endif #if USE_INTERRUPT -static int kscan_multiplex_interrupt_enable(const struct device *dev) { - int err = kscan_multiplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); +static int kscan_charliplex_interrupt_enable(const struct device *dev) { + int err = kscan_charliplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); if (err) { return err; } // While interrupts are enabled, set all outputs active so an pressed key will trigger - return kscan_multiplex_set_all_outputs(dev, 1); + return kscan_charliplex_set_all_outputs(dev, 1); } #endif #if USE_INTERRUPT -static void kscan_multiplex_irq_callback(const struct device *port, struct gpio_callback *cb, - const gpio_port_pins_t _pin) { - struct kscan_multiplex_data *data = CONTAINER_OF(cb, struct kscan_multiplex_data, irq_callback); +static void kscan_charliplex_irq_callback(const struct device *port, struct gpio_callback *cb, + const gpio_port_pins_t _pin) { + struct kscan_charliplex_data *data = + CONTAINER_OF(cb, struct kscan_charliplex_data, irq_callback); // Disable our interrupt to avoid re-entry while we scan. - kscan_multiplex_interrupt_configure(data->dev, GPIO_INT_DISABLE); + kscan_charliplex_interrupt_configure(data->dev, GPIO_INT_DISABLE); data->scan_time = k_uptime_get(); k_work_reschedule(&data->work, K_NO_WAIT); } #endif -static void kscan_multiplex_read_continue(const struct device *dev) { - const struct kscan_multiplex_config *config = dev->config; - struct kscan_multiplex_data *data = dev->data; +static void kscan_charliplex_read_continue(const struct device *dev) { + const struct kscan_charliplex_config *config = dev->config; + struct kscan_charliplex_data *data = dev->data; data->scan_time += config->debounce_scan_period_ms; k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); } -static void kscan_multiplex_read_end(const struct device *dev) { +static void kscan_charliplex_read_end(const struct device *dev) { #if USE_INTERRUPT // Return to waiting for an interrupt. - kscan_multiplex_interrupt_enable(dev); + kscan_charliplex_interrupt_enable(dev); #else - struct kscan_multiplex_data *data = dev->data; - const struct kscan_multiplex_config *config = dev->config; + struct kscan_charliplex_data *data = dev->data; + const struct kscan_charliplex_config *config = dev->config; data->scan_time += config->poll_period_ms; @@ -223,14 +225,14 @@ static void kscan_multiplex_read_end(const struct device *dev) { #endif } -static int kscan_multiplex_read(const struct device *dev) { - struct kscan_multiplex_data *data = dev->data; - const struct kscan_multiplex_config *config = dev->config; +static int kscan_charliplex_read(const struct device *dev) { + struct kscan_charliplex_data *data = dev->data; + const struct kscan_charliplex_config *config = dev->config; bool continue_scan = false; - // NOTE: MULTI vs MATRIX: set all pins as input, in case there was a failure on a + // NOTE: RR vs MATRIX: set all pins as input, in case there was a failure on a // previous scan, and one of the pins is still set as output - int err = kscan_multiplex_set_all_as_input(dev); + int err = kscan_charliplex_set_all_as_input(dev); if (err) { return err; } @@ -238,13 +240,13 @@ static int kscan_multiplex_read(const struct device *dev) { // Scan the matrix. for (int row = 0; row < config->cells.len; row++) { const struct gpio_dt_spec *out_gpio = &config->cells.gpios[row]; - err = kscan_multiplex_set_as_output(out_gpio); + err = kscan_charliplex_set_as_output(out_gpio); if (err) { return err; } -#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS > 0 - k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS); +#if CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS); #endif for (int col = 0; col < config->cells.len; col++) { @@ -254,11 +256,11 @@ static int kscan_multiplex_read(const struct device *dev) { const struct gpio_dt_spec *in_gpio = &config->cells.gpios[col]; const int index = state_index(config, row, col); - struct debounce_state *state = &data->multiplex_state[index]; + struct debounce_state *state = &data->charliplex_state[index]; debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms, &config->debounce_config); - // NOTE: MULTI vs MATRIX: because we don't need an input/output => row/column + // NOTE: RR vs MATRIX: because we don't need an input/output => row/column // setup, we can update in the same loop. if (debounce_get_changed(state)) { const bool pressed = debounce_is_pressed(state); @@ -269,67 +271,67 @@ static int kscan_multiplex_read(const struct device *dev) { continue_scan = continue_scan || debounce_is_active(state); } - err = kscan_multiplex_set_as_input(out_gpio); + err = kscan_charliplex_set_as_input(out_gpio); if (err) { return err; } -#if CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS > 0 - k_busy_wait(CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS); +#if CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS); #endif } if (continue_scan) { // At least one key is pressed or the debouncer has not yet decided if // it is pressed. Poll quickly until everything is released. - kscan_multiplex_read_continue(dev); + kscan_charliplex_read_continue(dev); } else { // All keys are released. Return to normal. - kscan_multiplex_read_end(dev); + kscan_charliplex_read_end(dev); } return 0; } -static void kscan_multiplex_work_handler(struct k_work *work) { +static void kscan_charliplex_work_handler(struct k_work *work) { struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work); - struct kscan_multiplex_data *data = CONTAINER_OF(dwork, struct kscan_multiplex_data, work); - kscan_multiplex_read(data->dev); + struct kscan_charliplex_data *data = CONTAINER_OF(dwork, struct kscan_charliplex_data, work); + kscan_charliplex_read(data->dev); } -static int kscan_multiplex_configure(const struct device *dev, const kscan_callback_t callback) { +static int kscan_charliplex_configure(const struct device *dev, const kscan_callback_t callback) { if (!callback) { return -EINVAL; } - struct kscan_multiplex_data *data = dev->data; + struct kscan_charliplex_data *data = dev->data; data->callback = callback; return 0; } -static int kscan_multiplex_enable(const struct device *dev) { - struct kscan_multiplex_data *data = dev->data; +static int kscan_charliplex_enable(const struct device *dev) { + struct kscan_charliplex_data *data = dev->data; data->scan_time = k_uptime_get(); // Read will automatically start interrupts/polling once done. - return kscan_multiplex_read(dev); + return kscan_charliplex_read(dev); } -static int kscan_multiplex_disable(const struct device *dev) { - struct kscan_multiplex_data *data = dev->data; +static int kscan_charliplex_disable(const struct device *dev) { + struct kscan_charliplex_data *data = dev->data; k_work_cancel_delayable(&data->work); #if USE_INTERRUPT - return kscan_multiplex_interrupt_configure(dev, GPIO_INT_DISABLE); + return kscan_charliplex_interrupt_configure(dev, GPIO_INT_DISABLE); #else return 0; #endif } -static int kscan_multiplex_init_inputs(const struct device *dev) { - const struct kscan_multiplex_config *config = dev->config; +static int kscan_charliplex_init_inputs(const struct device *dev) { + const struct kscan_charliplex_config *config = dev->config; for (int i = 0; i < config->cells.len; i++) { - int err = kscan_multiplex_set_as_input(&config->cells.gpios[i]); + int err = kscan_charliplex_set_as_input(&config->cells.gpios[i]); if (err) { return err; } @@ -339,17 +341,17 @@ static int kscan_multiplex_init_inputs(const struct device *dev) { } #if USE_INTERRUPT -static int kscan_multiplex_init_interrupt(const struct device *dev) { - struct kscan_multiplex_data *data = dev->data; +static int kscan_charliplex_init_interrupt(const struct device *dev) { + struct kscan_charliplex_data *data = dev->data; - const struct kscan_multiplex_config *config = dev->config; + const struct kscan_charliplex_config *config = dev->config; const struct gpio_dt_spec *gpio = &config->interrupt; - int err = kscan_multiplex_set_as_input(gpio); + int err = kscan_charliplex_set_as_input(gpio); if (err) { return err; } - gpio_init_callback(&data->irq_callback, kscan_multiplex_irq_callback, BIT(gpio->pin)); + gpio_init_callback(&data->irq_callback, kscan_charliplex_irq_callback, BIT(gpio->pin)); err = gpio_add_callback(gpio->port, &data->irq_callback); if (err) { LOG_ERR("Error adding the callback to the input device: %i", err); @@ -358,42 +360,42 @@ static int kscan_multiplex_init_interrupt(const struct device *dev) { } #endif -static int kscan_multiplex_init(const struct device *dev) { - struct kscan_multiplex_data *data = dev->data; +static int kscan_charliplex_init(const struct device *dev) { + struct kscan_charliplex_data *data = dev->data; data->dev = dev; - kscan_multiplex_init_inputs(dev); - kscan_multiplex_set_all_outputs(dev, 0); + kscan_charliplex_init_inputs(dev); + kscan_charliplex_set_all_outputs(dev, 0); #if USE_INTERRUPT - kscan_multiplex_init_interrupt(dev); + kscan_charliplex_init_interrupt(dev); #endif - k_work_init_delayable(&data->work, kscan_multiplex_work_handler); + k_work_init_delayable(&data->work, kscan_charliplex_work_handler); return 0; } -static const struct kscan_driver_api kscan_multiplex_api = { - .config = kscan_multiplex_configure, - .enable_callback = kscan_multiplex_enable, - .disable_callback = kscan_multiplex_disable, +static const struct kscan_driver_api kscan_charliplex_api = { + .config = kscan_charliplex_configure, + .enable_callback = kscan_charliplex_enable, + .disable_callback = kscan_charliplex_disable, }; -#define KSCAN_MULTIPLEX_INIT(n) \ +#define KSCAN_CHARLIPLEX_INIT(n) \ BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \ "ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \ BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \ "ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \ \ - static struct debounce_state kscan_multiplex_state_##n[INST_MULTIPLEX_LEN(n)]; \ - static const struct gpio_dt_spec kscan_multiplex_cells_##n[] = { \ + static struct debounce_state kscan_charliplex_state_##n[INST_CHARLIPLEX_LEN(n)]; \ + static const struct gpio_dt_spec kscan_charliplex_cells_##n[] = { \ UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \ - static struct kscan_multiplex_data kscan_multiplex_data_##n = { \ - .multiplex_state = kscan_multiplex_state_##n, \ + static struct kscan_charliplex_data kscan_charliplex_data_##n = { \ + .charliplex_state = kscan_charliplex_state_##n, \ }; \ \ - static struct kscan_multiplex_config kscan_multiplex_config_##n = { \ - .cells = KSCAN_GPIO_LIST(kscan_multiplex_cells_##n), \ + static struct kscan_charliplex_config kscan_charliplex_config_##n = { \ + .cells = KSCAN_GPIO_LIST(kscan_charliplex_cells_##n), \ .debounce_config = \ { \ .debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \ @@ -403,8 +405,8 @@ static const struct kscan_driver_api kscan_multiplex_api = { .poll_period_ms = DT_INST_PROP(n, poll_period_ms), \ COND_INTERRUPT((.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ \ - DEVICE_DT_INST_DEFINE(n, &kscan_multiplex_init, NULL, &kscan_multiplex_data_##n, \ - &kscan_multiplex_config_##n, APPLICATION, \ - CONFIG_APPLICATION_INIT_PRIORITY, &kscan_multiplex_api); + DEVICE_DT_INST_DEFINE(n, &kscan_charliplex_init, NULL, &kscan_charliplex_data_##n, \ + &kscan_charliplex_config_##n, APPLICATION, \ + CONFIG_APPLICATION_INIT_PRIORITY, &kscan_charliplex_api); -DT_INST_FOREACH_STATUS_OKAY(KSCAN_MULTIPLEX_INIT); +DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIPLEX_INIT); diff --git a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml similarity index 87% rename from app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml rename to app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml index aced5d50f97..e2eed813903 100644 --- a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml +++ b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml @@ -1,9 +1,9 @@ # Copyright (c) 2022 The ZMK Contributors # SPDX-License-Identifier: MIT -description: GPIO keyboard full multiplexed matrix controller +description: GPIO keyboard charliplex matrix controller -compatible: "zmk,kscan-gpio-multiplex" +compatible: "zmk,kscan-gpio-charliplex" include: kscan.yaml diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index f05a0c55e99..e98d21f9f53 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -149,26 +149,26 @@ The output pins (e.g. columns for `col2row`) should have the flag `GPIO_ACTIVE_H }; ``` -## Multiplex Driver +## Charliplex Driver Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output. This driver enables n pins to drive up to n\*(n-1) keys. Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) -| Config | Type | Description | Default | -| ------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` | bool | Poll for key presses instead of using interrupts | n | -| `CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | -| `CONFIG_ZMK_KSCAN_MULTIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | +| Config | Type | Description | Default | +| --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` | bool | Poll for key presses instead of using interrupts | n | +| `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | +| `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | -- With `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` enabled this allows n pins to drive n\*(n-1) keys. -- With `CONFIG_ZMK_KSCAN_MULTIPLEX_POLLING` disabled n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. +- With `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` enabled this allows n pins to drive n\*(n-1) keys. +- With `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` disabled n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. ### Devicetree -Applies to: `compatible = "zmk,kscan-gpio-multiplex"` +Applies to: `compatible = "zmk,kscan-gpio-charliplex"` -Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-multiplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-multiplex.yaml) +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-charliplex.yaml) | Property | Type | Description | Default | | ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ------- | @@ -439,9 +439,9 @@ Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-desig }; ``` -### Example: Multiplex +### Example: Charliplex -Since a multiplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys. +Since a charliplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys. Note that the entire addressable space does not need to be mapped. ```devicetree @@ -452,7 +452,7 @@ Note that the entire addressable space does not need to be mapped. }; kscan0: kscan { - compatible = "zmk,kscan-gpio-multiplex"; + compatible = "zmk,kscan-gpio-charliplex"; label = "KSCAN"; interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) >; From 48dfbaf48f4b81e83e23187f4da6a2a045134f78 Mon Sep 17 00:00:00 2001 From: HookyKB Date: Wed, 22 Mar 2023 06:55:22 +0000 Subject: [PATCH 05/14] Remove interrupt config/rely on pin set If the interrupt pin is set, use the interrupt code, else poll. This change results in a slightly larger executable in both cases, with unreachable code. More so in the case of polling, bet the difference is not great. --- app/module/drivers/kscan/CMakeLists.txt | 2 +- app/module/drivers/kscan/Kconfig | 5 +--- .../kscan/zmk,kscan-gpio-charliplex.yaml | 3 +-- docs/docs/config/kscan.md | 24 ++++++++++--------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/app/module/drivers/kscan/CMakeLists.txt b/app/module/drivers/kscan/CMakeLists.txt index 4e562a1bc8c..d8c78a5c906 100644 --- a/app/module/drivers/kscan/CMakeLists.txt +++ b/app/module/drivers/kscan/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2020 The ZMK Contributors +# Copyright (c) 2020-2023 The ZMK Contributors # SPDX-License-Identifier: MIT zephyr_library_amend() diff --git a/app/module/drivers/kscan/Kconfig b/app/module/drivers/kscan/Kconfig index 23cdb59ec13..fa2bde42d14 100644 --- a/app/module/drivers/kscan/Kconfig +++ b/app/module/drivers/kscan/Kconfig @@ -66,7 +66,7 @@ endif # ZMK_KSCAN_GPIO_MATRIX if ZMK_KSCAN_GPIO_CHARLIPLEX -config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS +config ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS int "Ticks to wait before reading inputs after an output set active" default 0 help @@ -97,9 +97,6 @@ if ZMK_KSCAN_GPIO_DRIVER config ZMK_KSCAN_MATRIX_POLLING bool "Poll for key event triggers instead of using interrupts on matrix boards." -config ZMK_KSCAN_CHARLIPLEX_POLLING - bool "Poll for key event triggers instead of using interrupts on multiplex boards." - config ZMK_KSCAN_DIRECT_POLLING bool "Poll for key event triggers instead of using interrupts on direct wired boards." diff --git a/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml index e2eed813903..033c852fabf 100644 --- a/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml +++ b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2022 The ZMK Contributors +# Copyright (c) 2023 The ZMK Contributors # SPDX-License-Identifier: MIT description: GPIO keyboard charliplex matrix controller @@ -13,7 +13,6 @@ properties: required: true interrupt-gpios: type: phandle-array - required: true debounce-press-ms: type: int default: 5 diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index e98d21f9f53..775c53037f4 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -151,13 +151,15 @@ The output pins (e.g. columns for `col2row`) should have the flag `GPIO_ACTIVE_H ## Charliplex Driver -Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output. This driver enables n pins to drive up to n\*(n-1) keys. +Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output. + +- With `interrupt-gpios` unset, this allows n pins to drive n\*(n-1) keys. +- With `interrupt-gpios` set, n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) | Config | Type | Description | Default | | --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` | bool | Poll for key presses instead of using interrupts | n | | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | @@ -170,15 +172,15 @@ Applies to: `compatible = "zmk,kscan-gpio-charliplex"` Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-charliplex.yaml) -| Property | Type | Description | Default | -| ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ------- | -| `label` | string | Unique label for the node | | -| `gpios` | GPIO array | GPIOs used, listed in order | | -| `interrupt-gpios` | GPIO array | A single GPIO to use for interrupt | | -| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | -| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | -| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | -| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_MATRIX_POLLING` is enabled. | 10 | +| Property | Type | Description | Default | +| ------------------------- | ---------- | ------------------------------------------------------------------------------------------- | ------- | +| `label` | string | Unique label for the node. | | +| `gpios` | GPIO array | GPIOs used, listed in order. | | +| `interrupt-gpios` | GPIO array | A single GPIO to use for interrupt. Leaving this empty will enable continuous polling. | | +| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | +| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | +| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | +| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and `interrupt-gpois` is not set. | 10 | Define the transform with a [matrix transform](#matrix-transform). The row is always the driven pin, and the column always the receiving pin (input to the controller). For example, in `RC(5,0)` power flows from the 6th pin in `gpios` to the 1st pin in `gpios`. From 94b871bb13f81abd648a597b4b78319c6911ed3e Mon Sep 17 00:00:00 2001 From: HookyKB Date: Mon, 3 Apr 2023 13:20:10 +0000 Subject: [PATCH 06/14] Reduce exe size if only polling or interrupt in use --- .../drivers/kscan/kscan_gpio_charliplex.c | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/app/module/drivers/kscan/kscan_gpio_charliplex.c b/app/module/drivers/kscan/kscan_gpio_charliplex.c index 43dc3a81be2..1574d1323d2 100644 --- a/app/module/drivers/kscan/kscan_gpio_charliplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charliplex.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 The ZMK Contributors + * Copyright (c) 2020-2023 The ZMK Contributors * * SPDX-License-Identifier: MIT */ @@ -36,14 +36,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms)) #endif -#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING) -#define USE_INTERRUPT (!USE_POLLING) - -#define COND_INTERRUPT(code) COND_CODE_1(CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING, (), code) - #define KSCAN_GPIO_CFG_INIT(idx, inst_idx) \ GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx), +#define INST_INTR_DEFINED(n) DT_INST_NODE_HAS_PROP(n, interrupt_gpios) #define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios) struct kscan_charliplex_data { @@ -51,9 +47,7 @@ struct kscan_charliplex_data { kscan_callback_t callback; struct k_work_delayable work; int64_t scan_time; /* Timestamp of the current or scheduled scan. */ -#if USE_INTERRUPT struct gpio_callback irq_callback; -#endif /** * Current state of the matrix as a flattened 2D array of length * (config->cells.length ^2) @@ -75,9 +69,8 @@ struct kscan_charliplex_config { struct debounce_config debounce_config; int32_t debounce_scan_period_ms; int32_t poll_period_ms; -#if USE_INTERRUPT + bool use_interrupt; const struct gpio_dt_spec interrupt; -#endif }; /** @@ -160,7 +153,6 @@ static int kscan_charliplex_set_all_outputs(const struct device *dev, const int return 0; } -#if USE_INTERRUPT static int kscan_charliplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) { const struct kscan_charliplex_config *config = dev->config; @@ -174,9 +166,7 @@ static int kscan_charliplex_interrupt_configure(const struct device *dev, return 0; } -#endif -#if USE_INTERRUPT static int kscan_charliplex_interrupt_enable(const struct device *dev) { int err = kscan_charliplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); if (err) { @@ -186,9 +176,7 @@ static int kscan_charliplex_interrupt_enable(const struct device *dev) { // While interrupts are enabled, set all outputs active so an pressed key will trigger return kscan_charliplex_set_all_outputs(dev, 1); } -#endif -#if USE_INTERRUPT static void kscan_charliplex_irq_callback(const struct device *port, struct gpio_callback *cb, const gpio_port_pins_t _pin) { struct kscan_charliplex_data *data = @@ -199,7 +187,6 @@ static void kscan_charliplex_irq_callback(const struct device *port, struct gpio data->scan_time = k_uptime_get(); k_work_reschedule(&data->work, K_NO_WAIT); } -#endif static void kscan_charliplex_read_continue(const struct device *dev) { const struct kscan_charliplex_config *config = dev->config; @@ -211,18 +198,18 @@ static void kscan_charliplex_read_continue(const struct device *dev) { } static void kscan_charliplex_read_end(const struct device *dev) { -#if USE_INTERRUPT - // Return to waiting for an interrupt. - kscan_charliplex_interrupt_enable(dev); -#else struct kscan_charliplex_data *data = dev->data; const struct kscan_charliplex_config *config = dev->config; - data->scan_time += config->poll_period_ms; + if (config->use_interrupt) { + // Return to waiting for an interrupt. + kscan_charliplex_interrupt_enable(dev); + } else { + data->scan_time += config->poll_period_ms; - // Return to polling slowly. - k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); -#endif + // Return to polling slowly. + k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); + } } static int kscan_charliplex_read(const struct device *dev) { @@ -320,11 +307,11 @@ static int kscan_charliplex_disable(const struct device *dev) { struct kscan_charliplex_data *data = dev->data; k_work_cancel_delayable(&data->work); -#if USE_INTERRUPT - return kscan_charliplex_interrupt_configure(dev, GPIO_INT_DISABLE); -#else + const struct kscan_charliplex_config *config = dev->config; + if (config->use_interrupt) { + return kscan_charliplex_interrupt_configure(dev, GPIO_INT_DISABLE); + } return 0; -#endif } static int kscan_charliplex_init_inputs(const struct device *dev) { @@ -340,7 +327,6 @@ static int kscan_charliplex_init_inputs(const struct device *dev) { return 0; } -#if USE_INTERRUPT static int kscan_charliplex_init_interrupt(const struct device *dev) { struct kscan_charliplex_data *data = dev->data; @@ -358,7 +344,6 @@ static int kscan_charliplex_init_interrupt(const struct device *dev) { } return err; } -#endif static int kscan_charliplex_init(const struct device *dev) { struct kscan_charliplex_data *data = dev->data; @@ -367,10 +352,11 @@ static int kscan_charliplex_init(const struct device *dev) { kscan_charliplex_init_inputs(dev); kscan_charliplex_set_all_outputs(dev, 0); -#if USE_INTERRUPT - kscan_charliplex_init_interrupt(dev); -#endif + const struct kscan_charliplex_config *config = dev->config; + if (config->use_interrupt) { + kscan_charliplex_init_interrupt(dev); + } k_work_init_delayable(&data->work, kscan_charliplex_work_handler); return 0; } @@ -403,7 +389,8 @@ static const struct kscan_driver_api kscan_charliplex_api = { }, \ .debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \ .poll_period_ms = DT_INST_PROP(n, poll_period_ms), \ - COND_INTERRUPT((.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ + .use_interrupt = INST_INTR_DEFINED(n), \ + COND_CODE_1(INST_INTR_DEFINED(n), (.interrupt = KSCAN_INTR_CFG_INIT(n)), ())}; \ \ DEVICE_DT_INST_DEFINE(n, &kscan_charliplex_init, NULL, &kscan_charliplex_data_##n, \ &kscan_charliplex_config_##n, APPLICATION, \ From 40f1b473043798134793234df432930a07e42eb3 Mon Sep 17 00:00:00 2001 From: HookyKB Date: Fri, 7 Apr 2023 09:48:21 +0000 Subject: [PATCH 07/14] Update for Zephyr 3.2 change --- .../drivers/kscan/kscan_gpio_charliplex.c | 59 ++++++++++++------- docs/docs/config/kscan.md | 4 +- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/app/module/drivers/kscan/kscan_gpio_charliplex.c b/app/module/drivers/kscan/kscan_gpio_charliplex.c index 1574d1323d2..63f847fb3cb 100644 --- a/app/module/drivers/kscan/kscan_gpio_charliplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charliplex.c @@ -4,16 +4,16 @@ * SPDX-License-Identifier: MIT */ -#include "debounce.h" +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -40,6 +40,23 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx), #define INST_INTR_DEFINED(n) DT_INST_NODE_HAS_PROP(n, interrupt_gpios) + +#define WITH_INTR(n) COND_CODE_1(INST_INTR_DEFINED(n), (+1), (+0)) +#define WITHOUT_INTR(n) COND_CODE_0(INST_INTR_DEFINED(n), (+1), (+0)) + +#define USES_POLLING DT_INST_FOREACH_STATUS_OKAY(WITHOUT_INTR) > 0 +#define USES_INTERRUPT DT_INST_FOREACH_STATUS_OKAY(WITH_INTR) > 0 + +#if USES_POLLING && USES_INTERRUPT +#define USES_POLL_AND_INTR 1 +#else +#define USES_POLL_AND_INTR 0 +#endif + +#define COND_ANY_POLLING(code) COND_CODE_1(USES_POLLING, code, ()) +#define COND_POLL_AND_INTR(code) COND_CODE_1(USES_POLL_AND_INTR, code, ()) +#define COND_THIS_INTERRUPT(n, code) COND_CODE_1(INST_INTR_DEFINED(n), code, ()) + #define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios) struct kscan_charliplex_data { @@ -52,7 +69,7 @@ struct kscan_charliplex_data { * Current state of the matrix as a flattened 2D array of length * (config->cells.length ^2) */ - struct debounce_state *charliplex_state; + struct zmk_debounce_state *charliplex_state; }; struct kscan_gpio_list { @@ -66,7 +83,7 @@ struct kscan_gpio_list { struct kscan_charliplex_config { struct kscan_gpio_list cells; - struct debounce_config debounce_config; + struct zmk_debounce_config debounce_config; int32_t debounce_scan_period_ms; int32_t poll_period_ms; bool use_interrupt; @@ -243,19 +260,19 @@ static int kscan_charliplex_read(const struct device *dev) { const struct gpio_dt_spec *in_gpio = &config->cells.gpios[col]; const int index = state_index(config, row, col); - struct debounce_state *state = &data->charliplex_state[index]; - debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms, - &config->debounce_config); + struct zmk_debounce_state *state = &data->charliplex_state[index]; + zmk_debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms, + &config->debounce_config); // NOTE: RR vs MATRIX: because we don't need an input/output => row/column // setup, we can update in the same loop. - if (debounce_get_changed(state)) { - const bool pressed = debounce_is_pressed(state); + if (zmk_debounce_get_changed(state)) { + const bool pressed = zmk_debounce_is_pressed(state); LOG_DBG("Sending event at %i,%i state %s", row, col, pressed ? "on" : "off"); data->callback(dev, row, col, pressed); } - continue_scan = continue_scan || debounce_is_active(state); + continue_scan = continue_scan || zmk_debounce_is_active(state); } err = kscan_charliplex_set_as_input(out_gpio); @@ -373,7 +390,7 @@ static const struct kscan_driver_api kscan_charliplex_api = { BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \ "ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \ \ - static struct debounce_state kscan_charliplex_state_##n[INST_CHARLIPLEX_LEN(n)]; \ + static struct zmk_debounce_state kscan_charliplex_state_##n[INST_CHARLIPLEX_LEN(n)]; \ static const struct gpio_dt_spec kscan_charliplex_cells_##n[] = { \ UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \ static struct kscan_charliplex_data kscan_charliplex_data_##n = { \ @@ -388,9 +405,9 @@ static const struct kscan_driver_api kscan_charliplex_api = { .debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \ }, \ .debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \ - .poll_period_ms = DT_INST_PROP(n, poll_period_ms), \ - .use_interrupt = INST_INTR_DEFINED(n), \ - COND_CODE_1(INST_INTR_DEFINED(n), (.interrupt = KSCAN_INTR_CFG_INIT(n)), ())}; \ + COND_ANY_POLLING((.poll_period_ms = DT_INST_PROP(n, poll_period_ms), )) \ + COND_POLL_AND_INTR((.use_interrupt = INST_INTR_DEFINED(n), )) \ + COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ \ DEVICE_DT_INST_DEFINE(n, &kscan_charliplex_init, NULL, &kscan_charliplex_data_##n, \ &kscan_charliplex_config_##n, APPLICATION, \ diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 775c53037f4..2c6cab4be28 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -158,8 +158,8 @@ Keyboard scan driver where keys are arranged on a matrix with each GPIO used as Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) -| Config | Type | Description | Default | -| --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | +| Config | Type | Description | Default | +| -------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | From 2503670bf3fefce3d5c3df2bb40fe38f608cef6f Mon Sep 17 00:00:00 2001 From: HookyKB Date: Sat, 21 Oct 2023 08:02:12 +0800 Subject: [PATCH 08/14] Docs cleanup --- docs/docs/config/kscan.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 2c6cab4be28..67482d35356 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -163,9 +163,6 @@ Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/ | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | | `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | -- With `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` enabled this allows n pins to drive n\*(n-1) keys. -- With `CONFIG_ZMK_KSCAN_CHARLIPLEX_POLLING` disabled n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. - ### Devicetree Applies to: `compatible = "zmk,kscan-gpio-charliplex"` @@ -457,13 +454,13 @@ Note that the entire addressable space does not need to be mapped. compatible = "zmk,kscan-gpio-charliplex"; label = "KSCAN"; - interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) >; + interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) >; gpios - = <&pro_micro 16 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > - , <&pro_micro 17 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > - , <&pro_micro 18 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > - , <&pro_micro 19 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > - , <&pro_micro 20 (GPIO_ACTIVE_HIGH|GPIO_PULL_DOWN) > + = <&pro_micro 16 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) > + , <&pro_micro 17 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) > + , <&pro_micro 18 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) > + , <&pro_micro 19 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) > + , <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) > ; // addressable space is 5x5, (minus paired values) }; From e9c75448b58f9663aca3444163f5597678ce8469 Mon Sep 17 00:00:00 2001 From: HookyKB Date: Tue, 24 Oct 2023 12:30:17 +0000 Subject: [PATCH 09/14] Make `Charlie` real I'd dropped the `e` from `charlieplex` everywhere. Bring it back to make the world a safe place again. --- app/module/drivers/kscan/CMakeLists.txt | 2 +- app/module/drivers/kscan/Kconfig | 16 +- ..._charliplex.c => kscan_gpio_charlieplex.c} | 173 +++++++++--------- ...x.yaml => zmk,kscan-gpio-charlieplex.yaml} | 4 +- docs/docs/config/kscan.md | 20 +- 5 files changed, 108 insertions(+), 107 deletions(-) rename app/module/drivers/kscan/{kscan_gpio_charliplex.c => kscan_gpio_charlieplex.c} (65%) rename app/module/dts/bindings/kscan/{zmk,kscan-gpio-charliplex.yaml => zmk,kscan-gpio-charlieplex.yaml} (87%) diff --git a/app/module/drivers/kscan/CMakeLists.txt b/app/module/drivers/kscan/CMakeLists.txt index d8c78a5c906..5b05af767e2 100644 --- a/app/module/drivers/kscan/CMakeLists.txt +++ b/app/module/drivers/kscan/CMakeLists.txt @@ -5,7 +5,7 @@ zephyr_library_amend() 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_CHARLIPLEX kscan_gpio_charliplex.c) +zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_CHARLIEPLEX kscan_gpio_charlieplex.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c) diff --git a/app/module/drivers/kscan/Kconfig b/app/module/drivers/kscan/Kconfig index fa2bde42d14..6b701936d4f 100644 --- a/app/module/drivers/kscan/Kconfig +++ b/app/module/drivers/kscan/Kconfig @@ -5,7 +5,7 @@ DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix -DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIPLEX := zmk,kscan-gpio-charliplex +DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX := zmk,kscan-gpio-charlieplex DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock if KSCAN @@ -34,9 +34,9 @@ config ZMK_KSCAN_GPIO_MATRIX default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX)) select ZMK_KSCAN_GPIO_DRIVER -config ZMK_KSCAN_GPIO_CHARLIPLEX +config ZMK_KSCAN_GPIO_CHARLIEPLEX bool - default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIPLEX)) + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX)) select ZMK_KSCAN_GPIO_DRIVER if ZMK_KSCAN_GPIO_MATRIX @@ -64,9 +64,9 @@ config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS endif # ZMK_KSCAN_GPIO_MATRIX -if ZMK_KSCAN_GPIO_CHARLIPLEX +if ZMK_KSCAN_GPIO_CHARLIEPLEX -config ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS +config ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS int "Ticks to wait before reading inputs after an output set active" default 0 help @@ -76,8 +76,8 @@ config ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS the number of ticks to wait after setting an output active before reading the inputs for their active state. -config ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS - int "Ticks to wait between each output when scanning charliplex matrix" +config ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS + int "Ticks to wait between each output when scanning charlieplex matrix" default 0 help When iterating over each output to drive it active, read inputs, then set @@ -86,7 +86,7 @@ config ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS scenario, set this value to a positive value to configure the number of usecs to wait after reading each column of keys. -endif # ZMK_KSCAN_GPIO_CHARLIPLEX +endif # ZMK_KSCAN_GPIO_CHARLIEPLEX config ZMK_KSCAN_MOCK_DRIVER bool diff --git a/app/module/drivers/kscan/kscan_gpio_charliplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c similarity index 65% rename from app/module/drivers/kscan/kscan_gpio_charliplex.c rename to app/module/drivers/kscan/kscan_gpio_charlieplex.c index 63f847fb3cb..02c029cdd10 100644 --- a/app/module/drivers/kscan/kscan_gpio_charliplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -17,10 +17,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#define DT_DRV_COMPAT zmk_kscan_gpio_charliplex +#define DT_DRV_COMPAT zmk_kscan_gpio_charlieplex #define INST_LEN(n) DT_INST_PROP_LEN(n, gpios) -#define INST_CHARLIPLEX_LEN(n) (INST_LEN(n) * INST_LEN(n)) +#define INST_CHARLIEPLEX_LEN(n) (INST_LEN(n) * INST_LEN(n)) #if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0 #define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS @@ -59,7 +59,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define KSCAN_INTR_CFG_INIT(inst_idx) GPIO_DT_SPEC_GET(DT_DRV_INST(inst_idx), interrupt_gpios) -struct kscan_charliplex_data { +struct kscan_charlieplex_data { const struct device *dev; kscan_callback_t callback; struct k_work_delayable work; @@ -69,7 +69,7 @@ struct kscan_charliplex_data { * Current state of the matrix as a flattened 2D array of length * (config->cells.length ^2) */ - struct zmk_debounce_state *charliplex_state; + struct zmk_debounce_state *charlieplex_state; }; struct kscan_gpio_list { @@ -81,7 +81,7 @@ struct kscan_gpio_list { #define KSCAN_GPIO_LIST(gpio_array) \ ((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)}) -struct kscan_charliplex_config { +struct kscan_charlieplex_config { struct kscan_gpio_list cells; struct zmk_debounce_config debounce_config; int32_t debounce_scan_period_ms; @@ -95,7 +95,8 @@ struct kscan_charliplex_config { * There are effectively (n) cols and (n-1) rows, but we use the full col x row space * as a safety measure against someone accidentally defining a transform RC at (p,p) */ -static int state_index(const struct kscan_charliplex_config *config, const int row, const int col) { +static int state_index(const struct kscan_charlieplex_config *config, const int row, + const int col) { __ASSERT(row < config->cells.len, "Invalid row %i", row); __ASSERT(col < config->cells.len, "Invalid column %i", col); __ASSERT(col != row, "Invalid column row pair %i, %i", col, row); @@ -103,7 +104,7 @@ static int state_index(const struct kscan_charliplex_config *config, const int r return (col * config->cells.len) + row; } -static int kscan_charliplex_set_as_input(const struct gpio_dt_spec *gpio) { +static int kscan_charlieplex_set_as_input(const struct gpio_dt_spec *gpio) { if (!device_is_ready(gpio->port)) { LOG_ERR("GPIO is not ready: %s", gpio->port->name); return -ENODEV; @@ -117,7 +118,7 @@ static int kscan_charliplex_set_as_input(const struct gpio_dt_spec *gpio) { return 0; } -static int kscan_charliplex_set_as_output(const struct gpio_dt_spec *gpio) { +static int kscan_charlieplex_set_as_output(const struct gpio_dt_spec *gpio) { if (!device_is_ready(gpio->port)) { LOG_ERR("GPIO is not ready: %s", gpio->port->name); return -ENODEV; @@ -136,11 +137,11 @@ static int kscan_charliplex_set_as_output(const struct gpio_dt_spec *gpio) { return err; } -static int kscan_charliplex_set_all_as_input(const struct device *dev) { - const struct kscan_charliplex_config *config = dev->config; +static int kscan_charlieplex_set_all_as_input(const struct device *dev) { + const struct kscan_charlieplex_config *config = dev->config; int err = 0; for (int i = 0; i < config->cells.len; i++) { - err = kscan_charliplex_set_as_input(&config->cells.gpios[i]); + err = kscan_charlieplex_set_as_input(&config->cells.gpios[i]); if (err) { return err; } @@ -149,8 +150,8 @@ static int kscan_charliplex_set_all_as_input(const struct device *dev) { return 0; } -static int kscan_charliplex_set_all_outputs(const struct device *dev, const int value) { - const struct kscan_charliplex_config *config = dev->config; +static int kscan_charlieplex_set_all_outputs(const struct device *dev, const int value) { + const struct kscan_charlieplex_config *config = dev->config; for (int i = 0; i < config->cells.len; i++) { const struct gpio_dt_spec *gpio = &config->cells.gpios[i]; @@ -170,9 +171,9 @@ static int kscan_charliplex_set_all_outputs(const struct device *dev, const int return 0; } -static int kscan_charliplex_interrupt_configure(const struct device *dev, - const gpio_flags_t flags) { - const struct kscan_charliplex_config *config = dev->config; +static int kscan_charlieplex_interrupt_configure(const struct device *dev, + const gpio_flags_t flags) { + const struct kscan_charlieplex_config *config = dev->config; const struct gpio_dt_spec *gpio = &config->interrupt; int err = gpio_pin_interrupt_configure_dt(gpio, flags); @@ -184,43 +185,43 @@ static int kscan_charliplex_interrupt_configure(const struct device *dev, return 0; } -static int kscan_charliplex_interrupt_enable(const struct device *dev) { - int err = kscan_charliplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); +static int kscan_charlieplex_interrupt_enable(const struct device *dev) { + int err = kscan_charlieplex_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE); if (err) { return err; } // While interrupts are enabled, set all outputs active so an pressed key will trigger - return kscan_charliplex_set_all_outputs(dev, 1); + return kscan_charlieplex_set_all_outputs(dev, 1); } -static void kscan_charliplex_irq_callback(const struct device *port, struct gpio_callback *cb, - const gpio_port_pins_t _pin) { - struct kscan_charliplex_data *data = - CONTAINER_OF(cb, struct kscan_charliplex_data, irq_callback); +static void kscan_charlieplex_irq_callback(const struct device *port, struct gpio_callback *cb, + const gpio_port_pins_t _pin) { + struct kscan_charlieplex_data *data = + CONTAINER_OF(cb, struct kscan_charlieplex_data, irq_callback); // Disable our interrupt to avoid re-entry while we scan. - kscan_charliplex_interrupt_configure(data->dev, GPIO_INT_DISABLE); + kscan_charlieplex_interrupt_configure(data->dev, GPIO_INT_DISABLE); data->scan_time = k_uptime_get(); k_work_reschedule(&data->work, K_NO_WAIT); } -static void kscan_charliplex_read_continue(const struct device *dev) { - const struct kscan_charliplex_config *config = dev->config; - struct kscan_charliplex_data *data = dev->data; +static void kscan_charlieplex_read_continue(const struct device *dev) { + const struct kscan_charlieplex_config *config = dev->config; + struct kscan_charlieplex_data *data = dev->data; data->scan_time += config->debounce_scan_period_ms; k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); } -static void kscan_charliplex_read_end(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; - const struct kscan_charliplex_config *config = dev->config; +static void kscan_charlieplex_read_end(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; + const struct kscan_charlieplex_config *config = dev->config; if (config->use_interrupt) { // Return to waiting for an interrupt. - kscan_charliplex_interrupt_enable(dev); + kscan_charlieplex_interrupt_enable(dev); } else { data->scan_time += config->poll_period_ms; @@ -229,14 +230,14 @@ static void kscan_charliplex_read_end(const struct device *dev) { } } -static int kscan_charliplex_read(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; - const struct kscan_charliplex_config *config = dev->config; +static int kscan_charlieplex_read(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; + const struct kscan_charlieplex_config *config = dev->config; bool continue_scan = false; // NOTE: RR vs MATRIX: set all pins as input, in case there was a failure on a // previous scan, and one of the pins is still set as output - int err = kscan_charliplex_set_all_as_input(dev); + int err = kscan_charlieplex_set_all_as_input(dev); if (err) { return err; } @@ -244,13 +245,13 @@ static int kscan_charliplex_read(const struct device *dev) { // Scan the matrix. for (int row = 0; row < config->cells.len; row++) { const struct gpio_dt_spec *out_gpio = &config->cells.gpios[row]; - err = kscan_charliplex_set_as_output(out_gpio); + err = kscan_charlieplex_set_as_output(out_gpio); if (err) { return err; } -#if CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS > 0 - k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS); +#if CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS); #endif for (int col = 0; col < config->cells.len; col++) { @@ -260,7 +261,7 @@ static int kscan_charliplex_read(const struct device *dev) { const struct gpio_dt_spec *in_gpio = &config->cells.gpios[col]; const int index = state_index(config, row, col); - struct zmk_debounce_state *state = &data->charliplex_state[index]; + struct zmk_debounce_state *state = &data->charlieplex_state[index]; zmk_debounce_update(state, gpio_pin_get_dt(in_gpio), config->debounce_scan_period_ms, &config->debounce_config); @@ -275,67 +276,67 @@ static int kscan_charliplex_read(const struct device *dev) { continue_scan = continue_scan || zmk_debounce_is_active(state); } - err = kscan_charliplex_set_as_input(out_gpio); + err = kscan_charlieplex_set_as_input(out_gpio); if (err) { return err; } -#if CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS > 0 - k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS); +#if CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS); #endif } if (continue_scan) { // At least one key is pressed or the debouncer has not yet decided if // it is pressed. Poll quickly until everything is released. - kscan_charliplex_read_continue(dev); + kscan_charlieplex_read_continue(dev); } else { // All keys are released. Return to normal. - kscan_charliplex_read_end(dev); + kscan_charlieplex_read_end(dev); } return 0; } -static void kscan_charliplex_work_handler(struct k_work *work) { +static void kscan_charlieplex_work_handler(struct k_work *work) { struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work); - struct kscan_charliplex_data *data = CONTAINER_OF(dwork, struct kscan_charliplex_data, work); - kscan_charliplex_read(data->dev); + struct kscan_charlieplex_data *data = CONTAINER_OF(dwork, struct kscan_charlieplex_data, work); + kscan_charlieplex_read(data->dev); } -static int kscan_charliplex_configure(const struct device *dev, const kscan_callback_t callback) { +static int kscan_charlieplex_configure(const struct device *dev, const kscan_callback_t callback) { if (!callback) { return -EINVAL; } - struct kscan_charliplex_data *data = dev->data; + struct kscan_charlieplex_data *data = dev->data; data->callback = callback; return 0; } -static int kscan_charliplex_enable(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; +static int kscan_charlieplex_enable(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; data->scan_time = k_uptime_get(); // Read will automatically start interrupts/polling once done. - return kscan_charliplex_read(dev); + return kscan_charlieplex_read(dev); } -static int kscan_charliplex_disable(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; +static int kscan_charlieplex_disable(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; k_work_cancel_delayable(&data->work); - const struct kscan_charliplex_config *config = dev->config; + const struct kscan_charlieplex_config *config = dev->config; if (config->use_interrupt) { - return kscan_charliplex_interrupt_configure(dev, GPIO_INT_DISABLE); + return kscan_charlieplex_interrupt_configure(dev, GPIO_INT_DISABLE); } return 0; } -static int kscan_charliplex_init_inputs(const struct device *dev) { - const struct kscan_charliplex_config *config = dev->config; +static int kscan_charlieplex_init_inputs(const struct device *dev) { + const struct kscan_charlieplex_config *config = dev->config; for (int i = 0; i < config->cells.len; i++) { - int err = kscan_charliplex_set_as_input(&config->cells.gpios[i]); + int err = kscan_charlieplex_set_as_input(&config->cells.gpios[i]); if (err) { return err; } @@ -344,17 +345,17 @@ static int kscan_charliplex_init_inputs(const struct device *dev) { return 0; } -static int kscan_charliplex_init_interrupt(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; +static int kscan_charlieplex_init_interrupt(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; - const struct kscan_charliplex_config *config = dev->config; + const struct kscan_charlieplex_config *config = dev->config; const struct gpio_dt_spec *gpio = &config->interrupt; - int err = kscan_charliplex_set_as_input(gpio); + int err = kscan_charlieplex_set_as_input(gpio); if (err) { return err; } - gpio_init_callback(&data->irq_callback, kscan_charliplex_irq_callback, BIT(gpio->pin)); + gpio_init_callback(&data->irq_callback, kscan_charlieplex_irq_callback, BIT(gpio->pin)); err = gpio_add_callback(gpio->port, &data->irq_callback); if (err) { LOG_ERR("Error adding the callback to the input device: %i", err); @@ -362,43 +363,43 @@ static int kscan_charliplex_init_interrupt(const struct device *dev) { return err; } -static int kscan_charliplex_init(const struct device *dev) { - struct kscan_charliplex_data *data = dev->data; +static int kscan_charlieplex_init(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; data->dev = dev; - kscan_charliplex_init_inputs(dev); - kscan_charliplex_set_all_outputs(dev, 0); + kscan_charlieplex_init_inputs(dev); + kscan_charlieplex_set_all_outputs(dev, 0); - const struct kscan_charliplex_config *config = dev->config; + const struct kscan_charlieplex_config *config = dev->config; if (config->use_interrupt) { - kscan_charliplex_init_interrupt(dev); + kscan_charlieplex_init_interrupt(dev); } - k_work_init_delayable(&data->work, kscan_charliplex_work_handler); + k_work_init_delayable(&data->work, kscan_charlieplex_work_handler); return 0; } -static const struct kscan_driver_api kscan_charliplex_api = { - .config = kscan_charliplex_configure, - .enable_callback = kscan_charliplex_enable, - .disable_callback = kscan_charliplex_disable, +static const struct kscan_driver_api kscan_charlieplex_api = { + .config = kscan_charlieplex_configure, + .enable_callback = kscan_charlieplex_enable, + .disable_callback = kscan_charlieplex_disable, }; -#define KSCAN_CHARLIPLEX_INIT(n) \ +#define KSCAN_CHARLIEPLEX_INIT(n) \ BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \ "ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \ BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \ "ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \ \ - static struct zmk_debounce_state kscan_charliplex_state_##n[INST_CHARLIPLEX_LEN(n)]; \ - static const struct gpio_dt_spec kscan_charliplex_cells_##n[] = { \ + static struct zmk_debounce_state kscan_charlieplex_state_##n[INST_CHARLIEPLEX_LEN(n)]; \ + static const struct gpio_dt_spec kscan_charlieplex_cells_##n[] = { \ UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \ - static struct kscan_charliplex_data kscan_charliplex_data_##n = { \ - .charliplex_state = kscan_charliplex_state_##n, \ + static struct kscan_charlieplex_data kscan_charlieplex_data_##n = { \ + .charlieplex_state = kscan_charlieplex_state_##n, \ }; \ \ - static struct kscan_charliplex_config kscan_charliplex_config_##n = { \ - .cells = KSCAN_GPIO_LIST(kscan_charliplex_cells_##n), \ + static struct kscan_charlieplex_config kscan_charlieplex_config_##n = { \ + .cells = KSCAN_GPIO_LIST(kscan_charlieplex_cells_##n), \ .debounce_config = \ { \ .debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \ @@ -409,8 +410,8 @@ static const struct kscan_driver_api kscan_charliplex_api = { COND_POLL_AND_INTR((.use_interrupt = INST_INTR_DEFINED(n), )) \ COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ \ - DEVICE_DT_INST_DEFINE(n, &kscan_charliplex_init, NULL, &kscan_charliplex_data_##n, \ - &kscan_charliplex_config_##n, APPLICATION, \ - CONFIG_APPLICATION_INIT_PRIORITY, &kscan_charliplex_api); + DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, NULL, &kscan_charlieplex_data_##n, \ + &kscan_charlieplex_config_##n, APPLICATION, \ + CONFIG_APPLICATION_INIT_PRIORITY, &kscan_charlieplex_api); -DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIPLEX_INIT); +DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIEPLEX_INIT); diff --git a/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml similarity index 87% rename from app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml rename to app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml index 033c852fabf..f8da1d27bb9 100644 --- a/app/module/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml +++ b/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml @@ -1,9 +1,9 @@ # Copyright (c) 2023 The ZMK Contributors # SPDX-License-Identifier: MIT -description: GPIO keyboard charliplex matrix controller +description: GPIO keyboard charlieplex matrix controller -compatible: "zmk,kscan-gpio-charliplex" +compatible: "zmk,kscan-gpio-charlieplex" include: kscan.yaml diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 67482d35356..18be58af1a7 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -149,7 +149,7 @@ The output pins (e.g. columns for `col2row`) should have the flag `GPIO_ACTIVE_H }; ``` -## Charliplex Driver +## Charlieplex Driver Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output. @@ -158,16 +158,16 @@ Keyboard scan driver where keys are arranged on a matrix with each GPIO used as Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) -| Config | Type | Description | Default | -| -------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | -| `CONFIG_ZMK_KSCAN_CHARLIPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | +| Config | Type | Description | Default | +| --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTS` | int (ticks) | How long to wait before reading input pins after setting output active | 0 | +| `CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTS` | int (ticks) | How long to wait between each output to allow previous output to "settle" | 0 | ### Devicetree -Applies to: `compatible = "zmk,kscan-gpio-charliplex"` +Applies to: `compatible = "zmk,kscan-gpio-charlieplex"` -Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-charliplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-charliplex.yaml) +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-charlieplex.yaml) | Property | Type | Description | Default | | ------------------------- | ---------- | ------------------------------------------------------------------------------------------- | ------- | @@ -438,9 +438,9 @@ Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-desig }; ``` -### Example: Charliplex +### Example: Charlieplex -Since a charliplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys. +Since a charlieplex driver will never align with a keyboard directly due to the un-addressable positions, a matrix transform should be used to map the pairs to the layout of the keys. Note that the entire addressable space does not need to be mapped. ```devicetree @@ -451,7 +451,7 @@ Note that the entire addressable space does not need to be mapped. }; kscan0: kscan { - compatible = "zmk,kscan-gpio-charliplex"; + compatible = "zmk,kscan-gpio-charlieplex"; label = "KSCAN"; interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) >; From 90e18c2456013263c741b491dedbf5906c91a8bf Mon Sep 17 00:00:00 2001 From: HookyKB Date: Mon, 30 Oct 2023 13:06:17 +0800 Subject: [PATCH 10/14] Add `module/` to docs where needed --- docs/docs/config/kscan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 18be58af1a7..68c5b161ba4 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -156,7 +156,7 @@ Keyboard scan driver where keys are arranged on a matrix with each GPIO used as - With `interrupt-gpios` unset, this allows n pins to drive n\*(n-1) keys. - With `interrupt-gpios` set, n pins will drive (n-1)\*(n-2) keys, but provide much improved power handling. -Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) +Definition file: [zmk/app/module/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/kscan/Kconfig) | Config | Type | Description | Default | | --------------------------------------------------- | ----------- | ------------------------------------------------------------------------- | ------- | @@ -167,7 +167,7 @@ Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/ Applies to: `compatible = "zmk,kscan-gpio-charlieplex"` -Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-charlieplex.yaml) +Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/kscan/zmk%2Ckscan-gpio-charlieplex.yaml) | Property | Type | Description | Default | | ------------------------- | ---------- | ------------------------------------------------------------------------------------------- | ------- | From 03e663a56ac01041285ba16bb9b3bd7b79dfc7f5 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Fri, 24 Nov 2023 05:45:42 +0000 Subject: [PATCH 11/14] refactor(kscan): Infer pull flags, use LISTIFY. * To avoid issues with platforms that enforce no pull flags when pins are used as outputs, infer the pull flags in code instead of setting them in the DTS for charlieplex kscan driver. * Use `LISTIFY` macro instead of deprecated `UTIL_LISTIFY`. --- app/module/drivers/kscan/kscan_gpio_charlieplex.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/module/drivers/kscan/kscan_gpio_charlieplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c index 02c029cdd10..19ba9d2915f 100644 --- a/app/module/drivers/kscan/kscan_gpio_charlieplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -110,7 +110,9 @@ static int kscan_charlieplex_set_as_input(const struct gpio_dt_spec *gpio) { return -ENODEV; } - int err = gpio_pin_configure_dt(gpio, GPIO_INPUT); + gpio_flags_t pull_flag = (gpio->dt_flags & GPIO_ACTIVE_HIGH) ? GPIO_PULL_DOWN : GPIO_PULL_UP; + + int err = gpio_pin_configure_dt(gpio, GPIO_INPUT | pull_flag); if (err) { LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name); return err; @@ -393,7 +395,7 @@ static const struct kscan_driver_api kscan_charlieplex_api = { \ static struct zmk_debounce_state kscan_charlieplex_state_##n[INST_CHARLIEPLEX_LEN(n)]; \ static const struct gpio_dt_spec kscan_charlieplex_cells_##n[] = { \ - UTIL_LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, n)}; \ + LISTIFY(INST_LEN(n), KSCAN_GPIO_CFG_INIT, (, ), n)}; \ static struct kscan_charlieplex_data kscan_charlieplex_data_##n = { \ .charlieplex_state = kscan_charlieplex_state_##n, \ }; \ From 32e7ac478226a01f22f2af478304429888f0fd77 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Fri, 24 Nov 2023 06:56:01 +0000 Subject: [PATCH 12/14] fix(kscan): Compile fix for LISTIFY refactor. --- app/module/drivers/kscan/kscan_gpio_charlieplex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/module/drivers/kscan/kscan_gpio_charlieplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c index 19ba9d2915f..806455d64e3 100644 --- a/app/module/drivers/kscan/kscan_gpio_charlieplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -37,7 +37,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif #define KSCAN_GPIO_CFG_INIT(idx, inst_idx) \ - GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx), + GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), gpios, idx) #define INST_INTR_DEFINED(n) DT_INST_NODE_HAS_PROP(n, interrupt_gpios) From 903ad462baf2daf334314124b303363cf7b493e9 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Fri, 24 Nov 2023 19:30:29 +0000 Subject: [PATCH 13/14] fix(kscan): Proper pull flag detection for inputs. * Check against `GPIO_ACTIVE_LOW` properly which has a non-zero value. --- app/module/drivers/kscan/kscan_gpio_charlieplex.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/module/drivers/kscan/kscan_gpio_charlieplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c index 806455d64e3..f450af2b59c 100644 --- a/app/module/drivers/kscan/kscan_gpio_charlieplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -110,7 +110,8 @@ static int kscan_charlieplex_set_as_input(const struct gpio_dt_spec *gpio) { return -ENODEV; } - gpio_flags_t pull_flag = (gpio->dt_flags & GPIO_ACTIVE_HIGH) ? GPIO_PULL_DOWN : GPIO_PULL_UP; + gpio_flags_t pull_flag = + ((gpio->dt_flags & GPIO_ACTIVE_LOW) == GPIO_ACTIVE_LOW) ? GPIO_PULL_UP : GPIO_PULL_DOWN; int err = gpio_pin_configure_dt(gpio, GPIO_INPUT | pull_flag); if (err) { From c85b75e856f5ed4e8ad856088751951472ff4a3e Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sat, 9 Dec 2023 13:50:16 -0800 Subject: [PATCH 14/14] Remove usage of label property. --- docs/docs/config/kscan.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 68c5b161ba4..65ea63ec8b7 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -171,7 +171,6 @@ Definition file: [zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.y | Property | Type | Description | Default | | ------------------------- | ---------- | ------------------------------------------------------------------------------------------- | ------- | -| `label` | string | Unique label for the node. | | | `gpios` | GPIO array | GPIOs used, listed in order. | | | `interrupt-gpios` | GPIO array | A single GPIO to use for interrupt. Leaving this empty will enable continuous polling. | | | `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | @@ -452,7 +451,6 @@ Note that the entire addressable space does not need to be mapped. kscan0: kscan { compatible = "zmk,kscan-gpio-charlieplex"; - label = "KSCAN"; interrupt-gpios = <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN) >; gpios