You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
11 KiB
414 lines
11 KiB
/* |
|
* Copyright (c) 2019 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT microchip_xec_kscan |
|
|
|
#include <errno.h> |
|
#include <device.h> |
|
#include <drivers/kscan.h> |
|
#include <kernel.h> |
|
#include <soc.h> |
|
#include <sys/atomic.h> |
|
#include <logging/log.h> |
|
|
|
#define LOG_LEVEL CONFIG_KSCAN_LOG_LEVEL |
|
LOG_MODULE_REGISTER(kscan_mchp_xec); |
|
|
|
#define MAX_MATRIX_KEY_COLS CONFIG_KSCAN_XEC_COLUMN_SIZE |
|
#define MAX_MATRIX_KEY_ROWS CONFIG_KSCAN_XEC_ROW_SIZE |
|
|
|
#define KEYBOARD_COLUMN_DRIVE_ALL -2 |
|
#define KEYBOARD_COLUMN_DRIVE_NONE -1 |
|
|
|
/* Poll period/debouncing rely onthe 32KHz clock with 30 usec clock cycles */ |
|
#define CLOCK_32K_HW_CYCLES_TO_US(X) \ |
|
(uint32_t)((((uint64_t)(X) * 1000000U) / sys_clock_hw_cycles_per_sec())) |
|
/* Milliseconds in microseconds */ |
|
#define MSEC_PER_MS 1000U |
|
/* Number of tracked scan times */ |
|
#define SCAN_OCURRENCES 30U |
|
/* Thread stack size */ |
|
#define TASK_STACK_SIZE 1024 |
|
|
|
struct kscan_xec_data { |
|
/* variables in usec units */ |
|
uint32_t deb_time_press; |
|
uint32_t deb_time_rel; |
|
int64_t poll_timeout; |
|
uint32_t poll_period; |
|
uint8_t matrix_stable_state[MAX_MATRIX_KEY_COLS]; |
|
uint8_t matrix_unstable_state[MAX_MATRIX_KEY_COLS]; |
|
uint8_t matrix_previous_state[MAX_MATRIX_KEY_COLS]; |
|
/* Index in to the scan_clock_cycle to indicate start of debouncing */ |
|
uint8_t scan_cycle_idx[MAX_MATRIX_KEY_COLS][MAX_MATRIX_KEY_ROWS]; |
|
/* Track previous "elapsed clock cycles" per matrix scan. This |
|
* is used to calculate the debouncing time for every key |
|
*/ |
|
uint8_t scan_clk_cycle[SCAN_OCURRENCES]; |
|
struct k_sem poll_lock; |
|
uint8_t scan_cycles_idx; |
|
kscan_callback_t callback; |
|
struct k_thread thread; |
|
atomic_t enable_scan; |
|
|
|
K_KERNEL_STACK_MEMBER(thread_stack, TASK_STACK_SIZE); |
|
}; |
|
|
|
static KSCAN_Type *base = (KSCAN_Type *) |
|
(DT_INST_REG_ADDR(0)); |
|
|
|
static struct kscan_xec_data kbd_data; |
|
|
|
static void drive_keyboard_column(int data) |
|
{ |
|
if (data == KEYBOARD_COLUMN_DRIVE_ALL) { |
|
/* KSO output controlled by the KSO_SELECT field */ |
|
base->KSO_SEL = MCHP_KSCAN_KSO_ALL; |
|
} else if (data == KEYBOARD_COLUMN_DRIVE_NONE) { |
|
/* Keyboard scan disabled. All KSO output buffers disabled */ |
|
base->KSO_SEL = MCHP_KSCAN_KSO_EN; |
|
} else { |
|
/* It is assumed, KEYBOARD_COLUMN_DRIVE_ALL was |
|
* previously set |
|
*/ |
|
base->KSO_SEL = data; |
|
} |
|
} |
|
|
|
static uint8_t read_keyboard_row(void) |
|
{ |
|
/* In this implementation a 1 means key pressed */ |
|
return ~(base->KSI_IN & 0xFF); |
|
} |
|
|
|
static bool is_matrix_ghosting(const uint8_t *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 < MAX_MATRIX_KEY_COLS; c++) { |
|
if (!state[c]) |
|
continue; |
|
|
|
for (int c_n = c + 1; c_n < MAX_MATRIX_KEY_COLS; c_n++) { |
|
/* 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. |
|
*/ |
|
uint8_t common_row_bits = state[c] & state[c_n]; |
|
|
|
if (common_row_bits & (common_row_bits - 1)) |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static bool read_keyboard_matrix(uint8_t *new_state) |
|
{ |
|
uint8_t row; |
|
uint8_t key_event = 0U; |
|
|
|
for (int col = 0; col < MAX_MATRIX_KEY_COLS; col++) { |
|
drive_keyboard_column(col); |
|
|
|
/* Allow the matrix to stabilize before reading it */ |
|
k_busy_wait(50U); |
|
row = read_keyboard_row(); |
|
new_state[col] = row; |
|
key_event |= row; |
|
} |
|
|
|
drive_keyboard_column(KEYBOARD_COLUMN_DRIVE_NONE); |
|
|
|
return key_event != 0U ? true : false; |
|
} |
|
|
|
static void scan_matrix_xec_isr(const void *arg) |
|
{ |
|
ARG_UNUSED(arg); |
|
|
|
MCHP_GIRQ_SRC(MCHP_KSCAN_GIRQ) = BIT(MCHP_KSCAN_GIRQ_POS); |
|
irq_disable(DT_INST_IRQN(0)); |
|
k_sem_give(&kbd_data.poll_lock); |
|
LOG_DBG(" "); |
|
} |
|
|
|
static bool check_key_events(const struct device *dev) |
|
{ |
|
uint8_t matrix_new_state[MAX_MATRIX_KEY_COLS] = {0U}; |
|
bool key_pressed = false; |
|
uint32_t cycles_now = k_cycle_get_32(); |
|
|
|
if (++kbd_data.scan_cycles_idx >= SCAN_OCURRENCES) |
|
kbd_data.scan_cycles_idx = 0U; |
|
|
|
kbd_data.scan_clk_cycle[kbd_data.scan_cycles_idx] = cycles_now; |
|
|
|
/* Scan the matrix */ |
|
key_pressed = read_keyboard_matrix(matrix_new_state); |
|
|
|
/* Abort if ghosting is detected */ |
|
if (is_matrix_ghosting(matrix_new_state)) { |
|
return false; |
|
} |
|
|
|
uint8_t row_changed = 0U; |
|
uint8_t deb_col; |
|
|
|
/* The intent of this loop is to gather information related to key |
|
* changes. |
|
*/ |
|
for (int c = 0; c < MAX_MATRIX_KEY_COLS; c++) { |
|
/* Check if there was an update from the previous scan */ |
|
row_changed = matrix_new_state[c] ^ |
|
kbd_data.matrix_previous_state[c]; |
|
|
|
if (!row_changed) |
|
continue; |
|
|
|
for (int r = 0; r < MAX_MATRIX_KEY_ROWS; 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)) |
|
kbd_data.scan_cycle_idx[c][r] = |
|
kbd_data.scan_cycles_idx; |
|
} |
|
|
|
kbd_data.matrix_unstable_state[c] |= row_changed; |
|
kbd_data.matrix_previous_state[c] = matrix_new_state[c]; |
|
} |
|
|
|
for (int c = 0; c < MAX_MATRIX_KEY_COLS; c++) { |
|
deb_col = kbd_data.matrix_unstable_state[c]; |
|
|
|
if (!deb_col) |
|
continue; |
|
|
|
/* Debouncing for each row key occurs here */ |
|
for (int r = 0; r < MAX_MATRIX_KEY_ROWS; r++) { |
|
uint8_t mask = BIT(r); |
|
uint8_t row_bit = matrix_new_state[c] & mask; |
|
|
|
/* Continue if we already debounce a key */ |
|
if (!(deb_col & mask)) |
|
continue; |
|
|
|
/* Convert the clock cycle differences to usec */ |
|
uint32_t debt = CLOCK_32K_HW_CYCLES_TO_US(cycles_now - |
|
kbd_data.scan_clk_cycle[kbd_data.scan_cycle_idx[c][r]]); |
|
|
|
/* Does the key requires more time to be debounced? */ |
|
if (debt < (row_bit ? kbd_data.deb_time_press : |
|
kbd_data.deb_time_rel)) { |
|
/* Need more time to debounce */ |
|
continue; |
|
} |
|
|
|
kbd_data.matrix_unstable_state[c] &= ~row_bit; |
|
|
|
/* Check if there was a change in the stable state */ |
|
if ((kbd_data.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. |
|
*/ |
|
kbd_data.matrix_stable_state[c] ^= mask; |
|
if (atomic_get(&kbd_data.enable_scan) == 1U) { |
|
kbd_data.callback(dev, r, c, |
|
row_bit ? true : false); |
|
} |
|
} |
|
} |
|
|
|
return key_pressed; |
|
} |
|
|
|
static bool poll_expired(uint32_t start_cycles, int64_t *timeout) |
|
{ |
|
uint32_t stop_cycles; |
|
uint32_t cycles_spent; |
|
uint32_t microsecs_spent; |
|
|
|
stop_cycles = k_cycle_get_32(); |
|
cycles_spent = stop_cycles - start_cycles; |
|
microsecs_spent = CLOCK_32K_HW_CYCLES_TO_US(cycles_spent); |
|
|
|
/* Update the timeout value */ |
|
*timeout -= microsecs_spent; |
|
|
|
return *timeout >= 0; |
|
|
|
} |
|
|
|
void polling_task(void *dummy1, void *dummy2, void *dummy3) |
|
{ |
|
uint32_t current_cycles; |
|
uint32_t cycles_diff; |
|
uint32_t wait_period; |
|
int64_t local_poll_timeout = kbd_data.poll_timeout; |
|
|
|
ARG_UNUSED(dummy1); |
|
ARG_UNUSED(dummy2); |
|
ARG_UNUSED(dummy3); |
|
|
|
while (true) { |
|
base->KSI_STS = MCHP_KSCAN_KSO_SEL_REG_MASK; |
|
|
|
/* Ignore isr when releasing a key as we are polling */ |
|
MCHP_GIRQ_SRC(MCHP_KSCAN_GIRQ) = BIT(MCHP_KSCAN_GIRQ_POS); |
|
NVIC_ClearPendingIRQ(MCHP_KSAN_NVIC); |
|
irq_enable(MCHP_KSAN_NVIC); |
|
drive_keyboard_column(KEYBOARD_COLUMN_DRIVE_ALL); |
|
k_sem_take(&kbd_data.poll_lock, K_FOREVER); |
|
|
|
uint32_t start_poll_cycles = k_cycle_get_32(); |
|
|
|
while (atomic_get(&kbd_data.enable_scan) == 1U) { |
|
uint32_t start_period_cycles = k_cycle_get_32(); |
|
|
|
if (check_key_events(DEVICE_DT_INST_GET(0))) { |
|
local_poll_timeout = kbd_data.poll_timeout; |
|
start_poll_cycles = k_cycle_get_32(); |
|
} else if (!poll_expired(start_poll_cycles, |
|
&local_poll_timeout)) { |
|
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; |
|
wait_period = kbd_data.poll_period - |
|
CLOCK_32K_HW_CYCLES_TO_US(cycles_diff); |
|
|
|
/* Override wait_period in case it is less than 1 ms */ |
|
if (wait_period < MSEC_PER_MS) |
|
wait_period = MSEC_PER_MS; |
|
|
|
/* wait period results in a larger number when |
|
* current cycles counter wrap. In this case, the |
|
* whole poll period is used |
|
*/ |
|
if (wait_period > kbd_data.poll_period) { |
|
LOG_DBG("wait_period : %u", wait_period); |
|
|
|
wait_period = kbd_data.poll_period; |
|
} |
|
|
|
/* Allow other threads to run while we sleep */ |
|
k_usleep(wait_period); |
|
} |
|
} |
|
} |
|
|
|
static int kscan_xec_configure(const struct device *dev, |
|
kscan_callback_t callback) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
if (!callback) { |
|
return -EINVAL; |
|
} |
|
|
|
kbd_data.callback = callback; |
|
|
|
MCHP_GIRQ_ENSET(MCHP_KSCAN_GIRQ) = BIT(MCHP_KSCAN_GIRQ_POS); |
|
|
|
return 0; |
|
} |
|
|
|
static int kscan_xec_inhibit_interface(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
atomic_set(&kbd_data.enable_scan, 0); |
|
|
|
return 0; |
|
} |
|
|
|
static int kscan_xec_enable_interface(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
atomic_set(&kbd_data.enable_scan, 1); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct kscan_driver_api kscan_xec_driver_api = { |
|
.config = kscan_xec_configure, |
|
.disable_callback = kscan_xec_inhibit_interface, |
|
.enable_callback = kscan_xec_enable_interface, |
|
}; |
|
|
|
static int kscan_xec_init(const struct device *dev); |
|
|
|
DEVICE_DT_INST_DEFINE(0, |
|
&kscan_xec_init, |
|
NULL, |
|
NULL, NULL, |
|
POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, |
|
&kscan_xec_driver_api); |
|
|
|
|
|
static int kscan_xec_init(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
/* Enable predrive */ |
|
base->KSO_SEL |= BIT(MCHP_KSCAN_KSO_EN_POS); |
|
base->EXT_CTRL = MCHP_KSCAN_EXT_CTRL_PREDRV_EN; |
|
base->KSO_SEL &= ~BIT(MCHP_KSCAN_KSO_EN_POS); |
|
base->KSI_IEN = MCHP_KSCAN_KSI_IEN_REG_MASK; |
|
|
|
/* Time figures are transformed from msec to usec */ |
|
kbd_data.deb_time_press = (uint32_t) |
|
(CONFIG_KSCAN_XEC_DEBOUNCE_DOWN * MSEC_PER_MS); |
|
kbd_data.deb_time_rel = (uint32_t) |
|
(CONFIG_KSCAN_XEC_DEBOUNCE_UP * MSEC_PER_MS); |
|
kbd_data.poll_period = (uint32_t) |
|
(CONFIG_KSCAN_XEC_POLL_PERIOD * MSEC_PER_MS); |
|
kbd_data.poll_timeout = 100 * MSEC_PER_MS; |
|
|
|
k_sem_init(&kbd_data.poll_lock, 0, 1); |
|
atomic_set(&kbd_data.enable_scan, 1); |
|
|
|
k_thread_create(&kbd_data.thread, kbd_data.thread_stack, |
|
TASK_STACK_SIZE, |
|
polling_task, NULL, NULL, NULL, |
|
K_PRIO_COOP(4), 0, K_NO_WAIT); |
|
|
|
/* Interrupts are enabled in the thread function */ |
|
IRQ_CONNECT(MCHP_KSAN_NVIC, 0, scan_matrix_xec_isr, NULL, 0); |
|
|
|
return 0; |
|
}
|
|
|