diff --git a/tests/drivers/input/gpio_kbd_matrix/CMakeLists.txt b/tests/drivers/input/gpio_kbd_matrix/CMakeLists.txt new file mode 100644 index 00000000000000..fa79887347fb84 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(input_gpio_kbd_matrix) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/drivers/input) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/input/gpio_kbd_matrix/Kconfig b/tests/drivers/input/gpio_kbd_matrix/Kconfig new file mode 100644 index 00000000000000..1a3f4a7c4f3345 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/Kconfig @@ -0,0 +1,7 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_KBD_DRIVE_COLUMN_HOOK + default y + +source "Kconfig.zephyr" diff --git a/tests/drivers/input/gpio_kbd_matrix/boards/native_sim.overlay b/tests/drivers/input/gpio_kbd_matrix/boards/native_sim.overlay new file mode 100644 index 00000000000000..3554df0c732d53 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/boards/native_sim.overlay @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&gpio0 { + ngpios = <12>; +}; + +/ { + kbd_matrix_interrupt: kbd-matrix-interrupt { + 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>; + debounce-down-ms = <80>; + debounce-up-ms = <40>; + poll-timeout-ms = <500>; + }; + + kbd_matrix_poll: kbd-matrix-poll { + compatible = "gpio-kbd-matrix"; + row-gpios = <&gpio0 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>, + <&gpio0 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + col-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>, + <&gpio0 7 GPIO_ACTIVE_LOW>; + debounce-down-ms = <40>; + debounce-up-ms = <80>; + poll-timeout-ms = <500>; + idle-mode = "poll"; + col-drive-inactive; + }; + + kbd_matrix_scan: kbd-matrix-scan { + compatible = "gpio-kbd-matrix"; + /* pins out of sequence to test non direct read */ + row-gpios = <&gpio0 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>, + <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + col-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>, + <&gpio0 10 GPIO_ACTIVE_LOW>; + debounce-down-ms = <80>; + debounce-up-ms = <40>; + poll-timeout-ms = <0>; + col-drive-inactive; + idle-mode = "scan"; + }; +}; diff --git a/tests/drivers/input/gpio_kbd_matrix/boards/native_sim_64.overlay b/tests/drivers/input/gpio_kbd_matrix/boards/native_sim_64.overlay new file mode 100644 index 00000000000000..1cf720283b3957 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/boards/native_sim_64.overlay @@ -0,0 +1,7 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "native_sim.overlay" diff --git a/tests/drivers/input/gpio_kbd_matrix/prj.conf b/tests/drivers/input/gpio_kbd_matrix/prj.conf new file mode 100644 index 00000000000000..5cd6cb6ceb3dd4 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/prj.conf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_GPIO=y +CONFIG_INPUT=y +CONFIG_INPUT_MODE_SYNCHRONOUS=y diff --git a/tests/drivers/input/gpio_kbd_matrix/src/main.c b/tests/drivers/input/gpio_kbd_matrix/src/main.c new file mode 100644 index 00000000000000..9600b510f0a174 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/src/main.c @@ -0,0 +1,414 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define INTERRUPT_NODE DT_NODELABEL(kbd_matrix_interrupt) +#define POLL_NODE DT_NODELABEL(kbd_matrix_poll) +#define SCAN_NODE DT_NODELABEL(kbd_matrix_scan) + +static const struct device *dev_interrupt = DEVICE_DT_GET_OR_NULL(INTERRUPT_NODE); +static const struct device *dev_poll = DEVICE_DT_GET_OR_NULL(POLL_NODE); +static const struct device *dev_scan = DEVICE_DT_GET_OR_NULL(SCAN_NODE); + +#define INTERRUPT_R0_PIN DT_GPIO_PIN_BY_IDX(INTERRUPT_NODE, row_gpios, 0) +#define INTERRUPT_R1_PIN DT_GPIO_PIN_BY_IDX(INTERRUPT_NODE, row_gpios, 1) +#define INTERRUPT_C0_PIN DT_GPIO_PIN_BY_IDX(INTERRUPT_NODE, col_gpios, 0) +#define INTERRUPT_C1_PIN DT_GPIO_PIN_BY_IDX(INTERRUPT_NODE, col_gpios, 1) + +#define POLL_R0_PIN DT_GPIO_PIN_BY_IDX(POLL_NODE, row_gpios, 0) +#define POLL_R1_PIN DT_GPIO_PIN_BY_IDX(POLL_NODE, row_gpios, 1) +#define POLL_C0_PIN DT_GPIO_PIN_BY_IDX(POLL_NODE, col_gpios, 0) +#define POLL_C1_PIN DT_GPIO_PIN_BY_IDX(POLL_NODE, col_gpios, 1) + +#define SCAN_R0_PIN DT_GPIO_PIN_BY_IDX(SCAN_NODE, row_gpios, 0) +#define SCAN_R1_PIN DT_GPIO_PIN_BY_IDX(SCAN_NODE, row_gpios, 1) +#define SCAN_C0_PIN DT_GPIO_PIN_BY_IDX(SCAN_NODE, col_gpios, 0) +#define SCAN_C1_PIN DT_GPIO_PIN_BY_IDX(SCAN_NODE, col_gpios, 1) + +static const struct device *dev_gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0)); + +enum { + KBD_DEV_INTERRUPT, + KBD_DEV_POLL, + KBD_DEV_SCAN, +}; + +#define KBD_DEV_COUNT (KBD_DEV_SCAN + 1) + +#define COL_COUNT 2 + +BUILD_ASSERT(DT_PROP_LEN(INTERRUPT_NODE, col_gpios) == COL_COUNT); +BUILD_ASSERT(DT_PROP_LEN(POLL_NODE, col_gpios) == COL_COUNT); +BUILD_ASSERT(DT_PROP_LEN(SCAN_NODE, col_gpios) == COL_COUNT); + +static uint8_t test_rows[KBD_DEV_COUNT][COL_COUNT]; +static int scan_set_count[KBD_DEV_COUNT]; + +static void gpio_kbd_scan_set_row(const struct device *dev, uint8_t row) +{ + if (dev == dev_interrupt) { + gpio_emul_input_set(dev_gpio, INTERRUPT_R0_PIN, !(row & BIT(0))); + gpio_emul_input_set(dev_gpio, INTERRUPT_R1_PIN, !(row & BIT(1))); + return; + } else if (dev == dev_poll) { + gpio_emul_input_set(dev_gpio, POLL_R0_PIN, !(row & BIT(0))); + gpio_emul_input_set(dev_gpio, POLL_R1_PIN, !(row & BIT(1))); + return; + } else if (dev == dev_scan) { + gpio_emul_input_set(dev_gpio, SCAN_R0_PIN, !(row & BIT(0))); + gpio_emul_input_set(dev_gpio, SCAN_R1_PIN, !(row & BIT(1))); + return; + } + + TC_PRINT("unknown device: %s\n", dev->name); +} + +void input_kbd_matrix_drive_column_hook(const struct device *dev, int col) +{ + gpio_flags_t flags0, flags1; + + if (col >= COL_COUNT) { + TC_PRINT("invalid column: %d\n", col); + return; + } + + if (dev == dev_interrupt) { + scan_set_count[KBD_DEV_INTERRUPT]++; + gpio_kbd_scan_set_row(dev, test_rows[KBD_DEV_INTERRUPT][col]); + + /* Verify that columns are NOT driven. */ + gpio_emul_flags_get(dev_gpio, INTERRUPT_C0_PIN, &flags0); + gpio_emul_flags_get(dev_gpio, INTERRUPT_C1_PIN, &flags1); + switch (col) { + case 0: + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_OUTPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_INPUT); + break; + case 1: + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_INPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_OUTPUT); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE: + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_INPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_INPUT); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL: + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_OUTPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_OUTPUT); + break; + } + + return; + } else if (dev == dev_poll) { + scan_set_count[KBD_DEV_POLL]++; + gpio_kbd_scan_set_row(dev, test_rows[KBD_DEV_POLL][col]); + + /* Verify that columns are always driven */ + gpio_emul_flags_get(dev_gpio, POLL_C0_PIN, &flags0); + gpio_emul_flags_get(dev_gpio, POLL_C1_PIN, &flags1); + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_OUTPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_OUTPUT); + + switch (col) { + case 0: + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C0_PIN), 0); + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C1_PIN), 1); + break; + case 1: + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C0_PIN), 1); + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C1_PIN), 0); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE: + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C0_PIN), 1); + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C1_PIN), 1); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL: + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C0_PIN), 0); + zassert_equal(gpio_emul_output_get(dev_gpio, POLL_C1_PIN), 0); + break; + } + + return; + } else if (dev == dev_scan) { + scan_set_count[KBD_DEV_SCAN]++; + gpio_kbd_scan_set_row(dev, test_rows[KBD_DEV_SCAN][col]); + + /* Verify that columns are always driven */ + gpio_emul_flags_get(dev_gpio, SCAN_C0_PIN, &flags0); + gpio_emul_flags_get(dev_gpio, SCAN_C1_PIN, &flags1); + zassert_equal(flags0 & GPIO_DIR_MASK, GPIO_OUTPUT); + zassert_equal(flags1 & GPIO_DIR_MASK, GPIO_OUTPUT); + + switch (col) { + case 0: + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C0_PIN), 0); + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C1_PIN), 1); + break; + case 1: + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C0_PIN), 1); + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C1_PIN), 0); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE: + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C0_PIN), 1); + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C1_PIN), 1); + break; + case INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL: + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C0_PIN), 0); + zassert_equal(gpio_emul_output_get(dev_gpio, SCAN_C1_PIN), 0); + break; + } + + return; + } + + TC_PRINT("unknown device: %s\n", dev->name); +} + +/* support stuff */ + +static struct { + int row; + int col; + int val; + int event_count; +} test_event_data; + +static int last_checked_event_count; + +#define assert_no_new_events() \ + zassert_equal(last_checked_event_count, test_event_data.event_count); + +#define assert_new_event(_row, _col, _val) { \ + last_checked_event_count++; \ + zassert_equal(last_checked_event_count, test_event_data.event_count); \ + zassert_equal(_row, test_event_data.row); \ + zassert_equal(_col, test_event_data.col); \ + zassert_equal(_val, test_event_data.val); \ +} + +static void test_cb(struct input_event *evt) +{ + static int row, col, val; + + switch (evt->code) { + case INPUT_ABS_X: + col = evt->value; + break; + case INPUT_ABS_Y: + row = evt->value; + break; + case INPUT_BTN_TOUCH: + val = evt->value; + break; + } + + if (evt->sync) { + test_event_data.row = row; + test_event_data.col = col; + test_event_data.val = val; + test_event_data.event_count++; + TC_PRINT("input event: count=%d row=%d col=%d val=%d\n", + test_event_data.event_count, row, col, val); + } +} +INPUT_CALLBACK_DEFINE(NULL, test_cb); + +/* actual tests */ + +ZTEST(gpio_kbd_scan, test_gpio_kbd_scan_interrupt) +{ + const struct device *dev = dev_interrupt; + + if (dev == NULL) { + ztest_test_skip(); + return; + } + + const struct input_kbd_matrix_common_config *cfg = dev->config; + uint8_t *rows = test_rows[KBD_DEV_INTERRUPT]; + int *set_count = &scan_set_count[KBD_DEV_INTERRUPT]; + int prev_count; + gpio_flags_t flags; + + k_sleep(K_SECONDS(1)); + assert_no_new_events(); + zassert_equal(*set_count, 1); + + /* Verify that interrupts are enabled. */ + gpio_emul_flags_get(dev_gpio, INTERRUPT_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_ENABLE, GPIO_INT_ENABLE); + gpio_emul_flags_get(dev_gpio, INTERRUPT_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_ENABLE, GPIO_INT_ENABLE); + + rows[0] = BIT(0); + gpio_kbd_scan_set_row(dev, 0x01); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + rows[1] = BIT(1); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 1, 1); + + rows[0] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + rows[1] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 1, 0); + + k_sleep(K_MSEC(cfg->poll_timeout_ms * 1.5)); + + /* Check that scanning is NOT running */ + prev_count = *set_count; + k_sleep(K_MSEC(cfg->poll_timeout_ms * 10)); + assert_no_new_events(); + TC_PRINT("scan_set_count=%d, prev_count=%d\n", *set_count, prev_count); + zassert_equal(*set_count, prev_count); + + /* Verify that interrupts are still enabled. */ + gpio_emul_flags_get(dev_gpio, INTERRUPT_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_ENABLE, GPIO_INT_ENABLE); + gpio_emul_flags_get(dev_gpio, INTERRUPT_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_ENABLE, GPIO_INT_ENABLE); +} + +ZTEST(gpio_kbd_scan, test_gpio_kbd_scan_poll) +{ + const struct device *dev = dev_poll; + + if (dev == NULL) { + ztest_test_skip(); + return; + } + + const struct input_kbd_matrix_common_config *cfg = dev->config; + uint8_t *rows = test_rows[KBD_DEV_POLL]; + int *set_count = &scan_set_count[KBD_DEV_POLL]; + int prev_count; + gpio_flags_t flags; + + k_sleep(K_SECONDS(1)); + assert_no_new_events(); + zassert_equal(*set_count, 0); + + /* Verify that interrupts are NOT enabled. */ + gpio_emul_flags_get(dev_gpio, POLL_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + gpio_emul_flags_get(dev_gpio, POLL_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + + rows[0] = BIT(0); + gpio_kbd_scan_set_row(dev, 0x01); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + rows[1] = BIT(1); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 1, 1); + + rows[0] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + rows[1] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 1, 0); + + k_sleep(K_MSEC(cfg->poll_timeout_ms * 1.5)); + + /* Check that scanning is NOT running */ + prev_count = *set_count; + k_sleep(K_MSEC(cfg->poll_timeout_ms * 10)); + assert_no_new_events(); + TC_PRINT("scan_set_count=%d, prev_count=%d\n", *set_count, prev_count); + zassert_equal(*set_count, prev_count); + + /* Verify that interrupts are still NOT enabled. */ + gpio_emul_flags_get(dev_gpio, POLL_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + gpio_emul_flags_get(dev_gpio, POLL_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); +} + +ZTEST(gpio_kbd_scan, test_gpio_kbd_scan_scan) +{ + const struct device *dev = dev_scan; + + if (dev == NULL) { + ztest_test_skip(); + return; + } + + const struct input_kbd_matrix_common_config *cfg = dev->config; + uint8_t *rows = test_rows[KBD_DEV_SCAN]; + int *set_count = &scan_set_count[KBD_DEV_SCAN]; + int prev_count; + int delta_count; + gpio_flags_t flags; + + /* check that scanning is already running */ + prev_count = *set_count; + k_sleep(K_SECONDS(1)); + assert_no_new_events(); + delta_count = *set_count - prev_count; + TC_PRINT("scan_set_count=%d, delta=%d\n", *set_count, delta_count); + zassert_true(delta_count > 100); + + /* Verify that interrupts are NOT enabled. */ + gpio_emul_flags_get(dev_gpio, SCAN_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + gpio_emul_flags_get(dev_gpio, SCAN_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + + rows[0] = BIT(0); + gpio_kbd_scan_set_row(dev, 0x01); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + rows[1] = BIT(1); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 1, 1); + + rows[0] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + rows[1] = 0x00; + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 1, 0); + + k_sleep(K_MSEC(cfg->poll_timeout_ms * 1.5)); + + /* Check that scanning is still running */ + prev_count = *set_count; + k_sleep(K_SECONDS(1)); + assert_no_new_events(); + delta_count = *set_count - prev_count; + TC_PRINT("scan_set_count=%d, delta=%d\n", *set_count, delta_count); + zassert_true(delta_count > 100); + + /* Verify that interrupts are still NOT enabled. */ + gpio_emul_flags_get(dev_gpio, SCAN_R0_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); + gpio_emul_flags_get(dev_gpio, SCAN_R1_PIN, &flags); + zassert_equal(flags & GPIO_INT_MASK, 0); +} + +static void gpio_kbd_scan_before(void *data) +{ + last_checked_event_count = 0; + memset(&test_event_data, 0, sizeof(test_event_data)); + memset(&scan_set_count, 0, sizeof(scan_set_count)); +} + +ZTEST_SUITE(gpio_kbd_scan, NULL, NULL, gpio_kbd_scan_before, NULL, NULL); diff --git a/tests/drivers/input/gpio_kbd_matrix/testcase.yaml b/tests/drivers/input/gpio_kbd_matrix/testcase.yaml new file mode 100644 index 00000000000000..a92532de3a0af3 --- /dev/null +++ b/tests/drivers/input/gpio_kbd_matrix/testcase.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +common: + platform_allow: + - native_sim + - native_sim_64 + tags: + - drivers + - input + integration_platforms: + - native_sim +tests: + input.input_gpio_kbd_matrix: {}