From 256bc860cfd387e3b5cc7700bf81fbf55d507183 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Mon, 6 Nov 2023 23:31:44 +0000 Subject: [PATCH] input: add a gpio based keyboard matrix driver Add a GPIO based keyboard matrix driver using the generic keyboard matrix code. Signed-off-by: Fabio Baltieri --- drivers/input/CMakeLists.txt | 1 + drivers/input/Kconfig | 1 + drivers/input/Kconfig.gpio_kbd_matrix | 10 ++ drivers/input/input_gpio_kbd_matrix.c | 198 ++++++++++++++++++++++ dts/bindings/input/gpio-kbd-matrix.yaml | 43 +++++ tests/drivers/build_all/input/app.overlay | 9 + 6 files changed, 262 insertions(+) create mode 100644 drivers/input/Kconfig.gpio_kbd_matrix create mode 100644 drivers/input/input_gpio_kbd_matrix.c create mode 100644 dts/bindings/input/gpio-kbd-matrix.yaml diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 96f85ba0e7..d63dc2ae79 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -7,6 +7,7 @@ zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_sources_ifdef(CONFIG_INPUT_CAP1203 input_cap1203.c) zephyr_library_sources_ifdef(CONFIG_INPUT_CST816S input_cst816s.c) zephyr_library_sources_ifdef(CONFIG_INPUT_FT5336 input_ft5336.c) +zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_KBD_MATRIX input_gpio_kbd_matrix.c) zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_KEYS input_gpio_keys.c) zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_QDEC input_gpio_qdec.c) zephyr_library_sources_ifdef(CONFIG_INPUT_GT911 input_gt911.c) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 2a0f8ac852..afab00d425 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -9,6 +9,7 @@ menu "Input drivers" source "drivers/input/Kconfig.cap1203" source "drivers/input/Kconfig.cst816s" source "drivers/input/Kconfig.ft5336" +source "drivers/input/Kconfig.gpio_kbd_matrix" source "drivers/input/Kconfig.gpio_keys" source "drivers/input/Kconfig.gpio_qdec" source "drivers/input/Kconfig.gt911" diff --git a/drivers/input/Kconfig.gpio_kbd_matrix b/drivers/input/Kconfig.gpio_kbd_matrix new file mode 100644 index 0000000000..1ded27c125 --- /dev/null +++ b/drivers/input/Kconfig.gpio_kbd_matrix @@ -0,0 +1,10 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_GPIO_KBD_MATRIX + bool "GPIO based keyboard matrix input driver" + default y + depends on DT_HAS_GPIO_KBD_MATRIX_ENABLED + select INPUT_KBD_MATRIX + help + GPIO keyboard matrix input driver. diff --git a/drivers/input/input_gpio_kbd_matrix.c b/drivers/input/input_gpio_kbd_matrix.c new file mode 100644 index 0000000000..9d532bfd0a --- /dev/null +++ b/drivers/input/input_gpio_kbd_matrix.c @@ -0,0 +1,198 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT gpio_kbd_matrix + +#include +#include + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(input_gpio_kbd_matrix, CONFIG_INPUT_LOG_LEVEL); + +struct gpio_kbd_matrix_config { + struct input_kbd_matrix_common_config common; + const struct gpio_dt_spec *row_gpio; + const struct gpio_dt_spec *col_gpio; + struct gpio_callback *gpio_cb; + gpio_callback_handler_t handler; +}; + +struct gpio_kbd_matrix_data { + struct input_kbd_matrix_common_data common; + uint32_t last_col_state; +}; + +INPUT_KBD_STRUCT_CHECK(struct gpio_kbd_matrix_config, + struct gpio_kbd_matrix_data); + +static void gpio_kbd_matrix_drive_column(const struct device *dev, int col) +{ + const struct gpio_kbd_matrix_config *cfg = dev->config; + const struct input_kbd_matrix_common_config *common = &cfg->common; + struct gpio_kbd_matrix_data *data = dev->data; + int state; + + if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) { + state = 0; + } else if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) { + state = BIT_MASK(common->col_size); + } else { + state = BIT(col); + } + + for (int i = 0; i < common->col_size; i++) { + const struct gpio_dt_spec *gpio = &cfg->col_gpio[i]; + + if ((data->last_col_state ^ state) & BIT(i)) { + if (state & BIT(i)) { + gpio_pin_configure_dt(gpio, GPIO_OUTPUT_ACTIVE); + } else { + gpio_pin_configure_dt(gpio, GPIO_INPUT); + } + } + } + + data->last_col_state = state; +} + +static int gpio_kbd_matrix_read_row(const struct device *dev) +{ + const struct gpio_kbd_matrix_config *cfg = dev->config; + const struct input_kbd_matrix_common_config *common = &cfg->common; + int val = 0; + + for (int i = 0; i < common->row_size; i++) { + const struct gpio_dt_spec *gpio = &cfg->row_gpio[i]; + + if (gpio_pin_get_dt(gpio)) { + val |= BIT(i); + } + } + + return val; +} + +static void gpio_kbd_matrix_set_detect_mode(const struct device *dev, bool enabled) +{ + const struct gpio_kbd_matrix_config *cfg = dev->config; + const struct input_kbd_matrix_common_config *common = &cfg->common; + unsigned int flags = enabled ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE; + int ret; + + for (int i = 0; i < common->row_size; i++) { + const struct gpio_dt_spec *gpio = &cfg->row_gpio[i]; + + ret = gpio_pin_interrupt_configure_dt(gpio, flags); + if (ret != 0) { + LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret); + return; + } + } +} + +static int gpio_kbd_matrix_init(const struct device *dev) +{ + const struct gpio_kbd_matrix_config *cfg = dev->config; + const struct input_kbd_matrix_common_config *common = &cfg->common; + int ret; + int i; + + for (i = 0; i < common->col_size; i++) { + const struct gpio_dt_spec *gpio = &cfg->col_gpio[i]; + + if (!gpio_is_ready_dt(gpio)) { + LOG_ERR("%s is not ready", gpio->port->name); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(gpio, GPIO_INPUT); + if (ret != 0) { + LOG_ERR("Pin %d configuration failed: %d", i, ret); + return ret; + } + } + + for (i = 0; i < common->row_size; i++) { + const struct gpio_dt_spec *gpio = &cfg->row_gpio[i]; + struct gpio_callback *gpio_cb = &cfg->gpio_cb[i]; + + if (!gpio_is_ready_dt(gpio)) { + LOG_ERR("%s is not ready", gpio->port->name); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(gpio, GPIO_INPUT); + if (ret != 0) { + LOG_ERR("Pin %d configuration failed: %d", i, ret); + return ret; + } + + gpio_init_callback(gpio_cb, cfg->handler, BIT(gpio->pin)); + + ret = gpio_add_callback_dt(gpio, gpio_cb); + if (ret < 0) { + LOG_ERR("Could not set gpio callback"); + return ret; + } + } + + gpio_kbd_matrix_set_detect_mode(dev, true); + + return input_kbd_matrix_common_init(dev); +} + +static const struct input_kbd_matrix_api gpio_kbd_matrix_api = { + .drive_column = gpio_kbd_matrix_drive_column, + .read_row = gpio_kbd_matrix_read_row, + .set_detect_mode = gpio_kbd_matrix_set_detect_mode, +}; + +#define INPUT_GPIO_KBD_MATRIX_INIT(n) \ + BUILD_ASSERT(DT_INST_PROP_LEN(n, col_gpios) <= 32, "invalid col-size"); \ + \ + INPUT_KBD_MATRIX_DT_INST_DEFINE_ROW_COL( \ + n, DT_INST_PROP_LEN(n, row_gpios), DT_INST_PROP_LEN(n, col_gpios)); \ + \ + static void gpio_kbd_matrix_cb_##n(const struct device *gpio_dev, \ + struct gpio_callback *cb, uint32_t pins) \ + { \ + input_kbd_matrix_poll_start(DEVICE_DT_INST_GET(n)); \ + } \ + \ + static const struct gpio_dt_spec gpio_kbd_matrix_row_gpio_##n[DT_INST_PROP_LEN( \ + n, row_gpios)] = { \ + DT_INST_FOREACH_PROP_ELEM_SEP(n, row_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)) \ + }; \ + static const struct gpio_dt_spec gpio_kbd_matrix_col_gpio_##n[DT_INST_PROP_LEN( \ + n, col_gpios)] = { \ + DT_INST_FOREACH_PROP_ELEM_SEP(n, col_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)) \ + }; \ + static struct gpio_callback gpio_kbd_matrix_gpio_cb_##n[DT_INST_PROP_LEN(n, row_gpios)];\ + \ + static const struct gpio_kbd_matrix_config gpio_kbd_matrix_cfg_##n = { \ + .common = INPUT_KBD_MATRIX_DT_INST_COMMON_CONFIG_INIT_ROW_COL( \ + n, &gpio_kbd_matrix_api, \ + DT_INST_PROP_LEN(n, row_gpios), DT_INST_PROP_LEN(n, col_gpios)), \ + .row_gpio = gpio_kbd_matrix_row_gpio_##n, \ + .col_gpio = gpio_kbd_matrix_col_gpio_##n, \ + .gpio_cb = gpio_kbd_matrix_gpio_cb_##n, \ + .handler = gpio_kbd_matrix_cb_##n, \ + }; \ + \ + static struct gpio_kbd_matrix_data gpio_kbd_matrix_data_##n; \ + \ + DEVICE_DT_INST_DEFINE(n, gpio_kbd_matrix_init, NULL, \ + &gpio_kbd_matrix_data_##n, &gpio_kbd_matrix_cfg_##n, \ + POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(INPUT_GPIO_KBD_MATRIX_INIT) diff --git a/dts/bindings/input/gpio-kbd-matrix.yaml b/dts/bindings/input/gpio-kbd-matrix.yaml new file mode 100644 index 0000000000..ef08ebaa44 --- /dev/null +++ b/dts/bindings/input/gpio-kbd-matrix.yaml @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: | + GPIO based keyboard matrix input device + + Implement an input device for a GPIO based keyboard matrix. + + Example configuration: + + kbd-matrix { + compatible = "gpio-kbd-matrix"; + row-gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>, + <&gpio0 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + col-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>, + <&gpio0 3 GPIO_ACTIVE_LOW>, + <&gpio0 4 GPIO_ACTIVE_LOW>; + no-ghostkey-check; + }; + +compatible: "gpio-kbd-matrix" + +include: + - name: kbd-matrix-common.yaml + property-blocklist: + - row-size + - col-size + +properties: + row-gpios: + type: phandle-array + required: true + description: | + GPIO for the keyboard matrix rows, up to 8 different GPIOs. All row GPIO + pins must have interrupt support. + + col-gpios: + type: phandle-array + required: true + description: | + GPIO for the keyboard matrix columns, supports up to 32 different GPIOs. + The pins will be driven according to the GPIO_ACTIVE_HIGH or + GPIO_ACTIVE_LOW flags when selected, high impedance when not selected. diff --git a/tests/drivers/build_all/input/app.overlay b/tests/drivers/build_all/input/app.overlay index 71f1d9a212..f065aadd5f 100644 --- a/tests/drivers/build_all/input/app.overlay +++ b/tests/drivers/build_all/input/app.overlay @@ -26,6 +26,15 @@ }; }; + kbd-matrix { + compatible = "gpio-kbd-matrix"; + row-gpios = <&test_gpio 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>, + <&test_gpio 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + col-gpios = <&test_gpio 2 GPIO_ACTIVE_LOW>, + <&test_gpio 3 GPIO_ACTIVE_LOW>, + <&test_gpio 4 GPIO_ACTIVE_LOW>; + }; + qdec-gpio { compatible = "gpio-qdec"; gpios = <&test_gpio 0 0>, <&test_gpio 1 0>;