| /* |
| * Copyright 2019 Intel Corporation |
| * Copyright 2022 Nuvoton Technology Corporation. |
| * 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/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/device_runtime.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/sys/util.h> |
| |
| LOG_MODULE_REGISTER(input_kbd_matrix, CONFIG_INPUT_LOG_LEVEL); |
| |
| void input_kbd_matrix_poll_start(const struct device *dev) |
| { |
| struct input_kbd_matrix_common_data *data = dev->data; |
| |
| k_sem_give(&data->poll_lock); |
| } |
| |
| static bool input_kbd_matrix_ghosting(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| const kbd_row_t *state = cfg->matrix_new_state; |
| |
| /* |
| * Matrix keyboard designs are suceptible to ghosting. |
| * An extra key appears to be pressed when 3 keys belonging to the same |
| * block are pressed. For example, in the following block: |
| * |
| * . . w . q . |
| * . . . . . . |
| * . . . . . . |
| * . . m . a . |
| * |
| * the key m would look as pressed if the user pressed keys w, q and a |
| * simultaneously. A block can also be formed, with not adjacent |
| * columns. |
| */ |
| for (int c = 0; c < cfg->col_size; c++) { |
| if (!state[c]) { |
| continue; |
| } |
| |
| for (int c_next = c + 1; c_next < cfg->col_size; c_next++) { |
| /* |
| * We AND the columns to detect a "block". This is an |
| * indication of ghosting, due to current flowing from |
| * a key which was never pressed. In our case, current |
| * flowing is a bit set to 1 as we flipped the bits |
| * when the matrix was scanned. Now we OR the colums |
| * using z&(z-1) which is non-zero only if z has more |
| * than one bit set. |
| */ |
| kbd_row_t common_row_bits = state[c] & state[c_next]; |
| |
| if (common_row_bits & (common_row_bits - 1)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| static void input_kbd_matrix_drive_column(const struct device *dev, int col) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| const struct input_kbd_matrix_api *api = cfg->api; |
| |
| api->drive_column(dev, col); |
| |
| #ifdef CONFIG_INPUT_KBD_DRIVE_COLUMN_HOOK |
| input_kbd_matrix_drive_column_hook(dev, col); |
| #endif |
| } |
| |
| static bool input_kbd_matrix_is_suspended(const struct device *dev) |
| { |
| #ifdef CONFIG_PM_DEVICE |
| struct input_kbd_matrix_common_data *data = dev->data; |
| |
| return atomic_get(&data->suspended) == 1; |
| #else |
| return false; |
| #endif |
| } |
| |
| static bool input_kbd_matrix_scan(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| const struct input_kbd_matrix_api *api = cfg->api; |
| kbd_row_t row; |
| kbd_row_t key_event = 0U; |
| |
| for (int col = 0; col < cfg->col_size; col++) { |
| if (cfg->actual_key_mask != NULL && |
| cfg->actual_key_mask[col] == 0) { |
| continue; |
| } |
| |
| if (input_kbd_matrix_is_suspended(dev)) { |
| cfg->matrix_new_state[col] = 0; |
| continue; |
| }; |
| |
| input_kbd_matrix_drive_column(dev, col); |
| |
| /* Allow the matrix to stabilize before reading it */ |
| k_busy_wait(cfg->settle_time_us); |
| |
| row = api->read_row(dev); |
| |
| if (cfg->actual_key_mask != NULL) { |
| row &= cfg->actual_key_mask[col]; |
| } |
| |
| cfg->matrix_new_state[col] = row; |
| key_event |= row; |
| } |
| |
| input_kbd_matrix_drive_column(dev, INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE); |
| |
| return key_event != 0U; |
| } |
| |
| static void input_kbd_matrix_update_state(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| struct input_kbd_matrix_common_data *data = dev->data; |
| kbd_row_t *matrix_new_state = cfg->matrix_new_state; |
| uint32_t cycles_now; |
| kbd_row_t row_changed; |
| kbd_row_t deb_col; |
| |
| cycles_now = k_cycle_get_32(); |
| |
| data->scan_clk_cycle[data->scan_cycles_idx] = cycles_now; |
| |
| /* |
| * The intent of this loop is to gather information related to key |
| * changes. |
| */ |
| for (int c = 0; c < cfg->col_size; c++) { |
| /* Check if there was an update from the previous scan */ |
| row_changed = matrix_new_state[c] ^ cfg->matrix_previous_state[c]; |
| |
| if (!row_changed) { |
| continue; |
| } |
| |
| for (int r = 0; r < cfg->row_size; r++) { |
| uint8_t cyc_idx = c * cfg->row_size + r; |
| |
| /* |
| * Index all they keys that changed for each row in |
| * order to debounce each key in terms of it |
| */ |
| if (row_changed & BIT(r)) { |
| cfg->scan_cycle_idx[cyc_idx] = data->scan_cycles_idx; |
| } |
| } |
| |
| cfg->matrix_unstable_state[c] |= row_changed; |
| cfg->matrix_previous_state[c] = matrix_new_state[c]; |
| } |
| |
| for (int c = 0; c < cfg->col_size; c++) { |
| deb_col = cfg->matrix_unstable_state[c]; |
| |
| if (!deb_col) { |
| continue; |
| } |
| |
| /* Debouncing for each row key occurs here */ |
| for (int r = 0; r < cfg->row_size; r++) { |
| kbd_row_t mask = BIT(r); |
| kbd_row_t row_bit = matrix_new_state[c] & mask; |
| |
| /* Continue if we already debounce a key */ |
| if (!(deb_col & mask)) { |
| continue; |
| } |
| |
| uint8_t cyc_idx = c * cfg->row_size + r; |
| uint8_t scan_cyc_idx = cfg->scan_cycle_idx[cyc_idx]; |
| uint32_t scan_clk_cycle = data->scan_clk_cycle[scan_cyc_idx]; |
| |
| /* Convert the clock cycle differences to usec */ |
| uint32_t deb_t_us = k_cyc_to_us_floor32(cycles_now - scan_clk_cycle); |
| |
| /* Does the key requires more time to be debounced? */ |
| if (deb_t_us < (row_bit ? cfg->debounce_down_us : cfg->debounce_up_us)) { |
| /* Need more time to debounce */ |
| continue; |
| } |
| |
| cfg->matrix_unstable_state[c] &= ~mask; |
| |
| /* Check if there was a change in the stable state */ |
| if ((cfg->matrix_stable_state[c] & mask) == row_bit) { |
| /* Key state did not change */ |
| continue; |
| } |
| |
| /* |
| * The current row has been debounced, therefore update |
| * the stable state. Then, proceed to notify the |
| * application about the keys pressed. |
| */ |
| cfg->matrix_stable_state[c] ^= mask; |
| |
| input_report_abs(dev, INPUT_ABS_X, c, false, K_FOREVER); |
| input_report_abs(dev, INPUT_ABS_Y, r, false, K_FOREVER); |
| input_report_key(dev, INPUT_BTN_TOUCH, row_bit, true, K_FOREVER); |
| } |
| } |
| |
| data->scan_cycles_idx = (data->scan_cycles_idx + 1) % INPUT_KBD_MATRIX_SCAN_OCURRENCES; |
| } |
| |
| static bool input_kbd_matrix_check_key_events(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| bool key_pressed; |
| |
| /* Scan the matrix */ |
| key_pressed = input_kbd_matrix_scan(dev); |
| |
| for (int c = 0; c < cfg->col_size; c++) { |
| LOG_DBG("c=%2d u=%" PRIkbdrow " p=%" PRIkbdrow " n=%" PRIkbdrow, |
| c, |
| cfg->matrix_unstable_state[c], |
| cfg->matrix_previous_state[c], |
| cfg->matrix_new_state[c]); |
| } |
| |
| /* Abort if ghosting is detected */ |
| if (cfg->ghostkey_check && input_kbd_matrix_ghosting(dev)) { |
| return key_pressed; |
| } |
| |
| input_kbd_matrix_update_state(dev); |
| |
| return key_pressed; |
| } |
| |
| static k_timepoint_t input_kbd_matrix_poll_timeout(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| |
| if (cfg->poll_timeout_ms == 0) { |
| return sys_timepoint_calc(K_FOREVER); |
| } |
| |
| return sys_timepoint_calc(K_MSEC(cfg->poll_timeout_ms)); |
| } |
| |
| static bool input_kbd_matrix_is_unstable(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| |
| for (uint8_t c = 0; c < cfg->col_size; c++) { |
| if (cfg->matrix_unstable_state[c] != 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void input_kbd_matrix_poll(const struct device *dev) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| k_timepoint_t poll_time_end; |
| uint32_t current_cycles; |
| uint32_t cycles_diff; |
| uint32_t wait_period_us; |
| uint32_t poll_period_us; |
| |
| poll_time_end = input_kbd_matrix_poll_timeout(dev); |
| |
| while (true) { |
| uint32_t start_period_cycles = k_cycle_get_32(); |
| |
| if (input_kbd_matrix_check_key_events(dev)) { |
| poll_time_end = input_kbd_matrix_poll_timeout(dev); |
| } else if (sys_timepoint_expired(poll_time_end)) { |
| break; |
| } |
| |
| /* |
| * Subtract the time invested from the sleep period in order to |
| * compensate for the time invested in debouncing a key |
| */ |
| current_cycles = k_cycle_get_32(); |
| cycles_diff = current_cycles - start_period_cycles; |
| |
| if (input_kbd_matrix_is_unstable(dev)) { |
| poll_period_us = cfg->poll_period_us; |
| } else { |
| poll_period_us = cfg->stable_poll_period_us; |
| } |
| wait_period_us = CLAMP(poll_period_us - k_cyc_to_us_floor32(cycles_diff), |
| USEC_PER_MSEC, poll_period_us); |
| |
| LOG_DBG("wait_period_us: %d", wait_period_us); |
| |
| /* Allow other threads to run while we sleep */ |
| k_usleep(wait_period_us); |
| } |
| } |
| |
| static void input_kbd_matrix_polling_thread(void *arg1, void *unused2, void *unused3) |
| { |
| const struct device *dev = arg1; |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| const struct input_kbd_matrix_api *api = cfg->api; |
| struct input_kbd_matrix_common_data *data = dev->data; |
| |
| ARG_UNUSED(unused2); |
| ARG_UNUSED(unused3); |
| |
| while (true) { |
| if (!input_kbd_matrix_is_suspended(dev)) { |
| input_kbd_matrix_drive_column(dev, INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL); |
| api->set_detect_mode(dev, true); |
| |
| /* Check the rows again after enabling the interrupt to catch |
| * any potential press since the last read. |
| */ |
| if (api->read_row(dev) != 0) { |
| input_kbd_matrix_poll_start(dev); |
| } |
| } |
| |
| k_sem_take(&data->poll_lock, K_FOREVER); |
| LOG_DBG("scan start"); |
| |
| /* Disable interrupt of KSI pins and start polling */ |
| api->set_detect_mode(dev, false); |
| |
| input_kbd_matrix_poll(dev); |
| } |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| int input_kbd_matrix_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| struct input_kbd_matrix_common_data *data = dev->data; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_SUSPEND: |
| atomic_set(&data->suspended, 1); |
| break; |
| case PM_DEVICE_ACTION_RESUME: |
| atomic_set(&data->suspended, 0); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| input_kbd_matrix_poll_start(dev); |
| |
| return 0; |
| } |
| #endif |
| |
| int input_kbd_matrix_common_init(const struct device *dev) |
| { |
| struct input_kbd_matrix_common_data *data = dev->data; |
| int ret; |
| |
| k_sem_init(&data->poll_lock, 0, 1); |
| |
| k_thread_create(&data->thread, data->thread_stack, |
| K_KERNEL_STACK_SIZEOF(data->thread_stack), |
| input_kbd_matrix_polling_thread, (void *)dev, NULL, NULL, |
| CONFIG_INPUT_KBD_MATRIX_THREAD_PRIORITY, 0, K_NO_WAIT); |
| |
| k_thread_name_set(&data->thread, dev->name); |
| |
| ret = pm_device_runtime_enable(dev); |
| if (ret < 0) { |
| LOG_ERR("Failed to enable runtime power management"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #if CONFIG_INPUT_KBD_ACTUAL_KEY_MASK_DYNAMIC |
| int input_kbd_matrix_actual_key_mask_set(const struct device *dev, |
| uint8_t row, uint8_t col, bool enabled) |
| { |
| const struct input_kbd_matrix_common_config *cfg = dev->config; |
| |
| if (row >= cfg->row_size || col >= cfg->col_size) { |
| return -EINVAL; |
| } |
| |
| if (cfg->actual_key_mask == NULL) { |
| LOG_WRN("actual-key-mask not defined for %s", dev->name); |
| return -EINVAL; |
| } |
| |
| WRITE_BIT(cfg->actual_key_mask[col], row, enabled); |
| |
| return 0; |
| } |
| #endif |