| /* |
| * Copyright 2023 Google LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/input/input.h> |
| #include <zephyr/input/input_kbd_matrix.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/ztest.h> |
| |
| #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(); |
| } |
| |
| ZTEST(kbd_scan, test_kbd_actual_key_map_set) |
| { |
| #if CONFIG_INPUT_KBD_ACTUAL_KEY_MASK_DYNAMIC |
| kbd_row_t mask[4] = {0x00, 0xff, 0x00, 0x00}; |
| const struct input_kbd_matrix_common_config cfg = { |
| .row_size = 3, |
| .col_size = 4, |
| .actual_key_mask = mask, |
| }; |
| const struct device fake_dev = { |
| .config = &cfg, |
| }; |
| int ret; |
| |
| ret = input_kbd_matrix_actual_key_mask_set(&fake_dev, 0, 0, true); |
| zassert_equal(ret, 0); |
| zassert_equal(mask[0], 0x01); |
| |
| ret = input_kbd_matrix_actual_key_mask_set(&fake_dev, 2, 1, false); |
| zassert_equal(ret, 0); |
| zassert_equal(mask[1], 0xfb); |
| |
| ret = input_kbd_matrix_actual_key_mask_set(&fake_dev, 2, 3, true); |
| zassert_equal(ret, 0); |
| zassert_equal(mask[3], 0x04); |
| |
| ret = input_kbd_matrix_actual_key_mask_set(&fake_dev, 3, 0, true); |
| zassert_equal(ret, -EINVAL); |
| |
| ret = input_kbd_matrix_actual_key_mask_set(&fake_dev, 0, 4, true); |
| zassert_equal(ret, -EINVAL); |
| |
| zassert_equal(memcmp(mask, (uint8_t[]){0x01, 0xfb, 0x00, 0x04}, 4), 0); |
| #else |
| ztest_test_skip(); |
| #endif |
| } |
| |
| 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); |