diff --git a/tests/drivers/input/kbd_matrix/CMakeLists.txt b/tests/drivers/input/kbd_matrix/CMakeLists.txt new file mode 100644 index 00000000000000..3b212ad34e4f9d --- /dev/null +++ b/tests/drivers/input/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_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/kbd_matrix/Kconfig b/tests/drivers/input/kbd_matrix/Kconfig new file mode 100644 index 00000000000000..83f78c3a5b70de --- /dev/null +++ b/tests/drivers/input/kbd_matrix/Kconfig @@ -0,0 +1,10 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_KBD_MATRIX + default y + +config INPUT_KBD_DRIVE_COLUMN_HOOK + default y + +source "Kconfig.zephyr" diff --git a/tests/drivers/input/kbd_matrix/actual-key-mask.overlay b/tests/drivers/input/kbd_matrix/actual-key-mask.overlay new file mode 100644 index 00000000000000..1ccb6425c0d882 --- /dev/null +++ b/tests/drivers/input/kbd_matrix/actual-key-mask.overlay @@ -0,0 +1,9 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&test_kbd_scan { + actual-key-mask = <0x07 0x07 0x03>; +}; diff --git a/tests/drivers/input/kbd_matrix/boards/native_sim.overlay b/tests/drivers/input/kbd_matrix/boards/native_sim.overlay new file mode 100644 index 00000000000000..9fb55ff3e46113 --- /dev/null +++ b/tests/drivers/input/kbd_matrix/boards/native_sim.overlay @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + test_kbd_scan: test-kbd-scan { + compatible = "test-kbd-scan"; + row-size = <3>; + col-size = <3>; + poll-period-ms = <5>; + debounce-down-ms = <40>; + debounce-up-ms = <80>; + poll-timeout-ms = <500>; + }; +}; diff --git a/tests/drivers/input/kbd_matrix/boards/native_sim_64.overlay b/tests/drivers/input/kbd_matrix/boards/native_sim_64.overlay new file mode 100644 index 00000000000000..1cf720283b3957 --- /dev/null +++ b/tests/drivers/input/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/kbd_matrix/dts/bindings/test-kbd-scan.yaml b/tests/drivers/input/kbd_matrix/dts/bindings/test-kbd-scan.yaml new file mode 100644 index 00000000000000..afa35b8b26d5fa --- /dev/null +++ b/tests/drivers/input/kbd_matrix/dts/bindings/test-kbd-scan.yaml @@ -0,0 +1,8 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Keyboard scan test binding + +compatible: "test-kbd-scan" + +include: kbd-matrix-common.yaml diff --git a/tests/drivers/input/kbd_matrix/no-ghostkey-check.overlay b/tests/drivers/input/kbd_matrix/no-ghostkey-check.overlay new file mode 100644 index 00000000000000..a23b37c84c44e7 --- /dev/null +++ b/tests/drivers/input/kbd_matrix/no-ghostkey-check.overlay @@ -0,0 +1,9 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&test_kbd_scan { + no-ghostkey-check; +}; diff --git a/tests/drivers/input/kbd_matrix/prj.conf b/tests/drivers/input/kbd_matrix/prj.conf new file mode 100644 index 00000000000000..bc713698bbfe2b --- /dev/null +++ b/tests/drivers/input/kbd_matrix/prj.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_INPUT=y +CONFIG_INPUT_MODE_SYNCHRONOUS=y diff --git a/tests/drivers/input/kbd_matrix/src/main.c b/tests/drivers/input/kbd_matrix/src/main.c new file mode 100644 index 00000000000000..609257c4c7a74b --- /dev/null +++ b/tests/drivers/input/kbd_matrix/src/main.c @@ -0,0 +1,408 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define TEST_KBD_SCAN_NODE DT_INST(0, test_kbd_scan) + +/* test driver */ + +/* Mock data for every valid column. */ +static struct { + kbd_row_t rows[3]; + int col; + bool detect_mode; +} state; + +static void test_drive_column(const struct device *dev, int col) +{ + state.col = col; +} + +static kbd_row_t test_read_row(const struct device *dev) +{ + if (state.col == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE || + state.col == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) { + return 0; + } + + return state.rows[state.col]; +} + +static void test_set_detect_mode(const struct device *dev, bool enabled) +{ + TC_PRINT("detect mode: enabled=%d\n", enabled); + state.detect_mode = enabled; +} + +static const struct input_kbd_matrix_api test_api = { + .drive_column = test_drive_column, + .read_row = test_read_row, + .set_detect_mode = test_set_detect_mode, +}; + +INPUT_KBD_MATRIX_DT_DEFINE(TEST_KBD_SCAN_NODE); + +static const struct input_kbd_matrix_common_config + test_cfg = INPUT_KBD_MATRIX_DT_COMMON_CONFIG_INIT( + TEST_KBD_SCAN_NODE, &test_api); + +static struct input_kbd_matrix_common_data test_data; + +DEVICE_DT_DEFINE(TEST_KBD_SCAN_NODE, input_kbd_matrix_common_init, NULL, + &test_data, &test_cfg, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL); + +static const struct device *const test_dev = DEVICE_DT_GET(TEST_KBD_SCAN_NODE); + +/* The test only supports a 3 column matrix */ +BUILD_ASSERT(DT_PROP(TEST_KBD_SCAN_NODE, col_size) == 3); + +/* support stuff */ + +static const struct device *column_hook_last_dev; +static int column_hook_last_col; + +void input_kbd_matrix_drive_column_hook(const struct device *dev, int col) +{ + column_hook_last_dev = dev; + column_hook_last_col = col; +} + +static void state_set_rows_by_column(kbd_row_t c0, kbd_row_t c1, kbd_row_t c2) +{ + memcpy(&state.rows, (kbd_row_t[]){c0, c1, c2}, sizeof(state.rows)); + TC_PRINT("set state [" PRIkbdrow " " PRIkbdrow " " PRIkbdrow "]\n", c0, c1, c2); +} + +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(test_dev, test_cb); + +#define WAIT_FOR_IDLE_TIMEOUT_US (5 * USEC_PER_SEC) + +static void kbd_scan_wait_for_idle(void) +{ + bool to; + + to = WAIT_FOR(state.detect_mode, + WAIT_FOR_IDLE_TIMEOUT_US, + k_sleep(K_MSEC(100))); + + zassert_true(to, "timeout waiting for idle state"); +} + +/* actual tests */ + +/* no event before debounce time, event after */ +ZTEST(kbd_scan, test_kbd_scan) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(0x00, BIT(2), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us / 2)); + assert_no_new_events(); + + k_sleep(K_USEC(cfg->debounce_down_us)); + assert_new_event(2, 1, 1); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us / 2)); + assert_no_new_events(); + + k_sleep(K_USEC(cfg->debounce_up_us)); + assert_new_event(2, 1, 0); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); + + zassert_equal(column_hook_last_dev, test_dev); + zassert_equal(column_hook_last_col, INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL); +} + +/* no event for short glitches */ +ZTEST(kbd_scan, test_kbd_scan_glitch) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(0x00, BIT(2), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us / 2)); + assert_no_new_events(); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_down_us)); + assert_no_new_events(); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); +} + +/* very bouncy key delays events indefinitely */ +ZTEST(kbd_scan, test_kbd_long_debounce) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(0x00, BIT(2), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us / 2)); + assert_no_new_events(); + + for (int i = 0; i < 10; i++) { + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_down_us / 2)); + assert_no_new_events(); + + state_set_rows_by_column(0x00, BIT(2), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us / 2)); + assert_no_new_events(); + } + + k_sleep(K_USEC(cfg->debounce_down_us)); + assert_new_event(2, 1, 1); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us / 2)); + assert_no_new_events(); + + for (int i = 0; i < 10; i++) { + state_set_rows_by_column(0x00, BIT(2), 0x00); + k_sleep(K_USEC(cfg->debounce_up_us / 2)); + assert_no_new_events(); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us / 2)); + assert_no_new_events(); + } + + k_sleep(K_USEC(cfg->debounce_up_us)); + assert_new_event(2, 1, 0); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); +} + +/* ghosting keys should not produce any event */ +ZTEST(kbd_scan, test_kbd_ghosting_check) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + if (cfg->ghostkey_check == false) { + ztest_test_skip(); + return; + } + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(BIT(0), 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + state_set_rows_by_column(BIT(0), BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 1, 1); + + /* ghosting */ + state_set_rows_by_column(BIT(0) | BIT(1), BIT(0) | BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 10)); + assert_no_new_events(); + + /* back to not ghosting anymore */ + state_set_rows_by_column(BIT(0), BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 10)); + assert_no_new_events(); + + state_set_rows_by_column(0x00, BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 1, 0); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); +} + +/* ghosting keys can be disabled */ +ZTEST(kbd_scan, test_kbd_no_ghosting_check) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + if (cfg->ghostkey_check == true) { + ztest_test_skip(); + return; + } + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(BIT(0), 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + state_set_rows_by_column(BIT(0), BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 1, 1); + + state_set_rows_by_column(BIT(0) | BIT(1), BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(1, 0, 1); + + state_set_rows_by_column(BIT(0) | BIT(1), BIT(0) | BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 1, 1); + + k_sleep(K_USEC(cfg->debounce_down_us * 10)); + assert_no_new_events(); + + state_set_rows_by_column(BIT(1), BIT(0) | BIT(1), 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + state_set_rows_by_column(BIT(1), BIT(0), 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 1, 0); + + state_set_rows_by_column(0x00, BIT(0), 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(1, 0, 0); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 1, 0); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); +} + +/* keymap is applied and can skip ghosting */ +ZTEST(kbd_scan, test_kbd_actual_keymap) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + if (cfg->actual_key_mask == NULL) { + ztest_test_skip(); + return; + } + + input_kbd_matrix_poll_start(test_dev); + + state_set_rows_by_column(BIT(0), 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 0, 1); + + state_set_rows_by_column(BIT(0), 0x00, BIT(0)); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(0, 2, 1); + + /* ghosting cleared by the keymap */ + state_set_rows_by_column(BIT(0) | BIT(2), 0x00, BIT(0) | BIT(2)); + k_sleep(K_USEC(cfg->debounce_down_us * 1.5)); + assert_new_event(2, 0, 1); + + state_set_rows_by_column(BIT(0) | BIT(2), 0x00, BIT(2)); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 2, 0); + + state_set_rows_by_column(BIT(2), 0x00, BIT(2)); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(0, 0, 0); + + state_set_rows_by_column(BIT(2), 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_no_new_events(); + + state_set_rows_by_column(0x00, 0x00, 0x00); + k_sleep(K_USEC(cfg->debounce_up_us * 1.5)); + assert_new_event(2, 0, 0); + + kbd_scan_wait_for_idle(); + assert_no_new_events(); +} +static void *kbd_scan_setup(void) +{ + const struct input_kbd_matrix_common_config *cfg = test_dev->config; + + TC_PRINT("actual kbd-matrix timing: poll_period_us=%d " + "debounce_down_us=%d debounce_up_us=%d\n", + cfg->poll_period_us, + cfg->debounce_down_us, + cfg->debounce_up_us); + + return NULL; +} + +static void kbd_scan_before(void *data) +{ + memset(&state, 0, sizeof(state)); + state.detect_mode = true; + + last_checked_event_count = 0; + memset(&test_event_data, 0, sizeof(test_event_data)); +} + +static void kbd_scan_after(void *data) +{ + /* Clear the test data so if a test fails early the testsuite does not + * hang indefinitely. + */ + state_set_rows_by_column(0x00, 0x00, 0x00); + kbd_scan_wait_for_idle(); +} + +ZTEST_SUITE(kbd_scan, NULL, kbd_scan_setup, kbd_scan_before, kbd_scan_after, NULL); diff --git a/tests/drivers/input/kbd_matrix/testcase.yaml b/tests/drivers/input/kbd_matrix/testcase.yaml new file mode 100644 index 00000000000000..8ee5a9aa7a6e20 --- /dev/null +++ b/tests/drivers/input/kbd_matrix/testcase.yaml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 + +common: + platform_allow: + - native_sim + - native_sim_64 + tags: + - drivers + - input + integration_platforms: + - native_sim +tests: + input.input_kbd_matrix: {} + input.input_kbd_matrix.no_ghostkey_check: + extra_args: + - EXTRA_DTC_OVERLAY_FILE=no-ghostkey-check.overlay + input.input_kbd_matrix.actual_key_mask: + extra_args: + - EXTRA_DTC_OVERLAY_FILE=actual-key-mask.overlay + input.input_kbd_matrix.row_16_bit: + extra_args: + - CONFIG_INPUT_KBD_MATRIX_16_BIT_ROW=y