drivers: kscan: Add driver for Microchip XEC family
Add the Keyboard Scan matrix driver for XEC Signed-off-by: Francisco Munoz <francisco.munoz.ruiz@intel.com>
This commit is contained in:
parent
edf24998b0
commit
0d85074d9a
|
@ -1,3 +1,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_KSCAN_XEC kscan_mchp_xec.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_USERSPACE kscan_handlers.c)
|
||||
|
|
49
drivers/kscan/Kconfig.xec
Normal file
49
drivers/kscan/Kconfig.xec
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Kconfig.xec - Microchip XEC Keyboard Scan Matrix configuration options
|
||||
#
|
||||
# Copyright (c) 2019 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menuconfig KSCAN_XEC
|
||||
bool "XEC Microchip KSCAN driver"
|
||||
depends on SOC_FAMILY_MEC
|
||||
select MULTITHREADING
|
||||
help
|
||||
Enable the Microchip XEC Kscan IO driver.
|
||||
|
||||
if KSCAN_XEC
|
||||
|
||||
config KSCAN_XEC_COLUMN_SIZE
|
||||
int "KSCAN_XEC_COLUMN_SIZE"
|
||||
default 16
|
||||
help
|
||||
Adjust the value to your keyboard columns. The maximum
|
||||
colum size for the Microchip XEC family is 18 (from 0 to 17).
|
||||
|
||||
config KSCAN_XEC_ROW_SIZE
|
||||
int "KSCAN_XEC_ROW_SIZE"
|
||||
default 8
|
||||
help
|
||||
Adjust the value to your keyboard rows. The maximum
|
||||
colum size for the Microchip XEC family is 8 (from 0 to 7).
|
||||
|
||||
config KSCAN_XEC_DEBOUNCE_DOWN
|
||||
int "KSCAN_XEC_DEBOUNCE_DOWN"
|
||||
default 10
|
||||
help
|
||||
Determines the time in msecs for debouncing a key press.
|
||||
|
||||
config KSCAN_XEC_DEBOUNCE_UP
|
||||
int "KSCAN_XEC_DEBOUNCE_UP"
|
||||
default 20
|
||||
help
|
||||
Determines the time in msecs for debouncing a key release.
|
||||
|
||||
config KSCAN_XEC_POLL_PERIOD
|
||||
int "KSCAN_XEC_POLL_PERIOD"
|
||||
default 5
|
||||
help
|
||||
Defines the poll period in msecs between between matrix scans.
|
||||
|
||||
endif #KSCAN_XEC
|
408
drivers/kscan/kscan_mchp_xec.c
Normal file
408
drivers/kscan/kscan_mchp_xec.c
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#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) \
|
||||
(u32_t)((((u64_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 */
|
||||
u32_t deb_time_press;
|
||||
u32_t deb_time_rel;
|
||||
s64_t poll_timeout;
|
||||
u32_t poll_period;
|
||||
u8_t matrix_stable_state[MAX_MATRIX_KEY_COLS];
|
||||
u8_t matrix_unstable_state[MAX_MATRIX_KEY_COLS];
|
||||
u8_t matrix_previous_state[MAX_MATRIX_KEY_COLS];
|
||||
/* Index in to the scan_clock_cycle to indicate start of debouncing */
|
||||
u8_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
|
||||
*/
|
||||
u8_t scan_clk_cycle[SCAN_OCURRENCES];
|
||||
struct k_sem poll_lock;
|
||||
u8_t scan_cycles_idx;
|
||||
kscan_callback_t callback;
|
||||
struct k_thread thread;
|
||||
atomic_t enable_scan;
|
||||
|
||||
K_THREAD_STACK_MEMBER(thread_stack, TASK_STACK_SIZE);
|
||||
};
|
||||
|
||||
static KSCAN_Type *base = (KSCAN_Type *)
|
||||
(DT_INST_0_MICROCHIP_XEC_KSCAN_BASE_ADDRESS);
|
||||
|
||||
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 u8_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.
|
||||
*/
|
||||
u8_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(u8_t *new_state)
|
||||
{
|
||||
u8_t row;
|
||||
u8_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(void *arg)
|
||||
{
|
||||
ARG_UNUSED(arg);
|
||||
|
||||
MCHP_GIRQ_SRC(MCHP_KSCAN_GIRQ) = BIT(MCHP_KSCAN_GIRQ_POS);
|
||||
irq_disable(DT_INST_0_MICROCHIP_XEC_KSCAN_IRQ_0);
|
||||
k_sem_give(&kbd_data.poll_lock);
|
||||
LOG_DBG(" ");
|
||||
}
|
||||
|
||||
static bool check_key_events(void *dev)
|
||||
{
|
||||
u8_t matrix_new_state[MAX_MATRIX_KEY_COLS] = {0U};
|
||||
bool key_pressed = false;
|
||||
u32_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;
|
||||
}
|
||||
|
||||
u8_t row_changed = 0U;
|
||||
u8_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++) {
|
||||
u8_t mask = BIT(r);
|
||||
u8_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 */
|
||||
u32_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(u32_t start_cycles, s64_t *timeout)
|
||||
{
|
||||
u32_t stop_cycles;
|
||||
u32_t cycles_spent;
|
||||
u32_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 *dev, void *dummy2, void *dummy3)
|
||||
{
|
||||
u32_t current_cycles;
|
||||
u32_t cycles_diff;
|
||||
u32_t wait_period;
|
||||
s64_t local_poll_timeout = kbd_data.poll_timeout;
|
||||
|
||||
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);
|
||||
|
||||
u32_t start_poll_cycles = k_cycle_get_32();
|
||||
|
||||
while (atomic_get(&kbd_data.enable_scan) == 1U) {
|
||||
u32_t start_period_cycles = k_cycle_get_32();
|
||||
|
||||
if (check_key_events(dev)) {
|
||||
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(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(struct device *dev)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
atomic_set(&kbd_data.enable_scan, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_xec_enable_interface(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(struct device *dev);
|
||||
|
||||
DEVICE_AND_API_INIT(kscan_xec, DT_INST_0_MICROCHIP_XEC_KSCAN_LABEL,
|
||||
&kscan_xec_init,
|
||||
NULL, NULL,
|
||||
POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY,
|
||||
&kscan_xec_driver_api);
|
||||
|
||||
|
||||
static int kscan_xec_init(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 = (u32_t)
|
||||
(CONFIG_KSCAN_XEC_DEBOUNCE_DOWN * MSEC_PER_MS);
|
||||
kbd_data.deb_time_rel = (u32_t)
|
||||
(CONFIG_KSCAN_XEC_DEBOUNCE_UP * MSEC_PER_MS);
|
||||
kbd_data.poll_period = (u32_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, dev, 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;
|
||||
}
|
||||
|
Loading…
Reference in a new issue