From 405192e16444bd3683eb173d9165ccaa0682c89f Mon Sep 17 00:00:00 2001 From: down Date: Fri, 15 Jul 2022 02:24:15 +0700 Subject: [PATCH] feat(kscan): add round robin matrix --- app/drivers/kscan/CMakeLists.txt | 1 + app/drivers/kscan/Kconfig | 6 + .../kscan/kscan_gpio_round_robin_matrix.c | 175 ++++++++++++++++++ .../zmk,kscan-gpio-round-robin-matrix.yaml | 28 +++ 4 files changed, 210 insertions(+) create mode 100644 app/drivers/kscan/kscan_gpio_round_robin_matrix.c create mode 100644 app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-round-robin-matrix.yaml diff --git a/app/drivers/kscan/CMakeLists.txt b/app/drivers/kscan/CMakeLists.txt index ced31e6f..8abef1da 100644 --- a/app/drivers/kscan/CMakeLists.txt +++ b/app/drivers/kscan/CMakeLists.txt @@ -10,3 +10,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_ROUND_ROBIN_MATRIX kscan_gpio_round_robin_matrix.c) diff --git a/app/drivers/kscan/Kconfig b/app/drivers/kscan/Kconfig index c9ace0a3..5cff73e0 100644 --- a/app/drivers/kscan/Kconfig +++ b/app/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_CONPAT_ZMK_KSCAN_GPIO_ROUND_ROBIN_MATRIX := zmk,kscan-gpio-round-robin-matrix DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock config ZMK_KSCAN_COMPOSITE_DRIVER @@ -30,6 +31,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_ROUND_ROBIN_MATRIX + bool + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_ROUND_ROBIN_MATRIX)) + select ZMK_KSCAN_GPIO_DRIVER + config ZMK_KSCAN_MOCK_DRIVER bool default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK)) diff --git a/app/drivers/kscan/kscan_gpio_round_robin_matrix.c b/app/drivers/kscan/kscan_gpio_round_robin_matrix.c new file mode 100644 index 00000000..6c0d31ef --- /dev/null +++ b/app/drivers/kscan/kscan_gpio_round_robin_matrix.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 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_round_robin_matrix + +#define INST_INPUT_LEN(n) DT_INST_PROP_LEN(n, input_gpios) +#define INST_OUTPUTS_LEN(n) DT_INST_PROP_LEN(n, output_gpios) +#define INST_MATRIX_LEN(n) (INST_INPUT_LEN(n) * INST_OUTPUTS_LEN(n)) + +#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 KSCAN_GPIO_INPUT_CFG_INIT(idx, inst_idx) \ + GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx), +#define KSCAN_GPIO_OUTPUT_CFG_INIT(idx, inst_idx) \ + GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), output_gpios, idx), + +struct kscan_round_robin_matrix_data { + const struct device *dev; + kscan_callback_t callback; + struct k_work_delayable work; + int64_t scan_time; + struct debounce_state *matrix_state; +}; + +struct kscan_gpio_list { + const struct gpio_dt_spec *gpios; + size_t len; +}; + +#define KSCAN_GPIO_LIST(gpio_array) \ + ((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)}) + +struct kscan_round_robin_matrix_config { + struct kscan_gpio_list outputs; + struct kscan_gpio_list inputs; + struct debounce_config debounce_config; + int32_t poll_period_us; +}; + +static int state_index_io(const struct kscan_round_robin_matrix *config, const int input_idx, + const int output_idx) { + __ASSERT(input_idx < config->inputs.len, "Invalid input %i", input_idx); + __ASSERT(output_idx < config->outputs.len, "Invalid output %i", output_idx); + + return (input_idx * config->outputs.len) + output_idx); +} + +static int kscan_round_robin_matrix_read(const struct device *dev) { + struct kscan_round_robin_matrix_data *data = dev->data; + const struct kscan_round_robin_matrix_config *config = dev->config; + + size_t len = config->outputs.len; + for (int o = 0; o < len; o++) { + const struct gpio_dt_spec *out_gpio = &config->outputs.gpios[o]; + + int err = gpio_pin_set_dt(out_gpio, 1); + if (err) { + LOG_ERR("Failed to set output %i active: %i", o, err); + return err; + } + + for (int i = (o + 1) % len; i != o; i = (i + 1) % len) { + const struct gpio_dt_spec *in_gpio = &config->inputs.gpios[i]; + + const int index = state_index_io(config, i, o); + const bool is_active = gpio_pin_get_dt(in_gpio); + struct debounce_state *state = &data->matrix_state[index]; + + debounce_update(state, is_active, config->debounce_scan_period_ms, + &config->debounce_config); + + if (debounce_get_changed(state)) { + const bool is_pressed = debounce_is_pressed(state); + + LOG_DBG("Sending event at %i,%i state %s", r, c, is_pressed ? "on" : "off"); + data->callback(dev, i, o, is_pressed); + } + } + + err = gpio_pin_set_dt(out_gpio, 0); + if (err) { + LOG_ERR("Failed to set output %i inactive: %i", o, err); + return err; + } + } + + data->scan_time += config->poll_period_ms; + + k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time)); + + return 0; +} + +static void kscan_matrix_work_handler(struct k_work *work) { + struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work); + struct kscan_round_robin_matrix_data *data = CONTAINER_OF(dwork, struct kscan_round_robin_matrix_data, work); + kscan_round_robin_matrix_read(data->dev); +} + +static int kscan_round_robin_matrix_init(const struct device *dev) { + struct kscan_round_robin_matrix_data *data = dev->data; + + data->dev = dev; + + k_work_init_delayable(&data->work, kscan_round_robin_matrix_work_handler); + + return 0; +} + +static const struct kscan_driver_api kscan_round_robin_matrix_api = { + .config = kscan_round_robin_matrix_configure, + .enable_callback = kscan_round_robin_matrix_enable, + .disable_callback = kscan_round_robin_matrix_disable, +}; + +#define KSCAN_ROUND_ROBIN_MATRIX_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 const struct gpio_dt_spec kscan_round_robin_matrix_outputs_##n[] = { \ + UTIL_LISTIFY(INST_OUTPUTS_LEN(n), KSCAN_GPIO_OUTPUT_CFG_INIT, n)}; \ + \ + static const struct gpio_dt_spec kscan_round_robin_matrix_inputs_##n[] = { \ + UTIL_LISTIFY(INST_INPUTS_LEN(n), KSCAN_GPIO_INPUT_CFG_INIT, n)}; \ + \ + static struct debounce_state kscan_round_robin_matrix_state_##n[INST_MATRIX_LEN(n)]; \ + \ + static struct kscan_round_robin_matrix_data kscan_round_robin_matrix_data_##n = { \ + .matrix_state = kscan_round_robin_matrix_state_##n, }; \ + \ + static struct kscan_round_robin_matrix_config kscan_round_robin_matrix_config_##n = { \ + .outputs = KSCAN_GPIO_LIST(kscan_round_robin_matrix_outputs_##n) \ + .inputs = KSCAN_GPIO_LIST(kscan_round_robin_matrix_inputs_##n) \ + .debounce_config = \ + { \ + .debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \ + .debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \ + }, \ + .poll_period_ms = DT_INST_PROP(n, poll_period_ms), \ + }; \ + DEVICE_DT_INST_DEFINE(n, &kscan_round_robin_matrix_init, NULL, &kscan_round_robin_matrix_data_##n, \ + &kscan_round_robin_matrix_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \ + &kscan_round_robin_matrix_api); + +DT_INST_FOREACH_STATUS_OKAY(KSCAN_ROUND_ROBIN_MATRIX_INIT); diff --git a/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-round-robin-matrix.yaml b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-round-robin-matrix.yaml new file mode 100644 index 00000000..686fb418 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-round-robin-matrix.yaml @@ -0,0 +1,28 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: GPIO keyboard improved square matrix controller + +compatible: "zmk,kscan-gpio-round-robin-matrix" + +include: kscan.yaml + +properties: + input-gpios: + type: phandle-array + required: true + output-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. + poll-period-ms: + type: int + default: 1 + description: Time between reads in milliseconds