Merge lm4 and stm32 implementations of keyboard_scan

Scanning is now performed identically on all platforms.  keyboard_scan
talks to chip-specific keyboard_raw on the bottom end, and 8042 or mkbp
keyboard protocol on the top end.

8042 can now take advantage of CONFIG_KEYBOARD_TEST to simulate scan results.

BUG=chrome-os-partner:18360
BRANCH=none
TEST=compile all boards; test keyboard on spring and link

1) Type on keyboard.  Should produce keystrokes.

2) At EC console:
    kbpress 3 7 1
    kbpress 3 7 0
Should produce 'r' keystroke(s); key repeat should kick in if you wait
a while between the commands.

3) Hold power button while typing.  Should not produce keystrokes.

4) Reboot with power+refresh+esc.  Should go to recovery mode.

5) While the system is up, alt+volup+R should reboot the AP.

Change-Id: I48e0bca15b869162672b5f54ffcb561f6fcf0f45
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/46666
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
This commit is contained in:
Randall Spangler
2013-03-22 09:35:00 -07:00
committed by ChromeBot
parent 4311bc5ccf
commit fe3ccdf70a
5 changed files with 385 additions and 636 deletions

View File

@@ -21,10 +21,7 @@ common-$(CONFIG_FLASH)+=flash_common.o fmap.o
common-$(CONFIG_I2C)+=i2c_commands.o
common-$(CONFIG_I2C_ARBITRATION)+=i2c_arbitration.o
common-$(CONFIG_IR357x)+=ir357x.o
# TODO: combine common bits of keyboard_scan into one file
# (coming in a follow-up CL)
common-$(CONFIG_KEYBOARD_PROTOCOL_MKBP)+=keyboard_mkbp.o
common-$(CONFIG_KEYBOARD_PROTOCOL_8042)+=keyboard_scan.o
common-$(CONFIG_KEYBOARD_TEST)+=keyboard_test.o
common-$(CONFIG_LP5562)+=lp5562.o lp5562_battery_led.o
common-$(CONFIG_LPC)+=port80.o
@@ -35,6 +32,7 @@ common-$(CONFIG_TASK_CHARGER)+=charge_state.o battery_precharge.o
common-$(CONFIG_TASK_CONSOLE)+=console.o
common-$(CONFIG_TASK_HOSTCMD)+=host_command.o host_event_commands.o
common-$(CONFIG_TASK_I8042CMD)+=i8042.o keyboard_8042.o
common-$(CONFIG_TASK_KEYSCAN)+=keyboard_scan.o
common-$(CONFIG_TASK_LIGHTBAR)+=lightbar.o
common-$(CONFIG_TASK_THERMAL)+=thermal.o
common-$(CONFIG_TASK_VBOOTHASH)+=sha256.o vboot_hash.o

View File

@@ -146,11 +146,6 @@ static const uint16_t scancode_set2[KEYBOARD_ROWS][KEYBOARD_COLS] = {
0x0044, 0x0000, 0xe075, 0xe06b},
};
/* Recording which key is being simulated pressed. */
static uint8_t simulated_key[KEYBOARD_COLS];
/* Log the traffic between EC and host -- for debug only */
#define MAX_KBLOG 512 /* Max events in keyboard log */
struct kblog_t {
@@ -834,53 +829,6 @@ DECLARE_CONSOLE_COMMAND(ctrlram, command_controller_ram,
"Get/set keyboard controller RAM",
NULL);
static int command_keyboard_press(int argc, char **argv)
{
if (argc == 1) {
int i, j;
ccputs("Simulated key:\n");
for (i = 0; i < KEYBOARD_COLS; ++i) {
if (simulated_key[i] == 0)
continue;
for (j = 0; j < KEYBOARD_ROWS; ++j)
if (simulated_key[i] & (1 << j))
ccprintf("\t%d %d\n", i, j);
}
} else if (argc == 4) {
int r, c, p;
char *e;
c = strtoi(argv[1], &e, 0);
if (*e || c < 0 || c >= KEYBOARD_COLS)
return EC_ERROR_PARAM1;
r = strtoi(argv[2], &e, 0);
if (*e || r < 0 || r >= KEYBOARD_ROWS)
return EC_ERROR_PARAM2;
p = strtoi(argv[3], &e, 0);
if (*e || p < 0 || p > 1)
return EC_ERROR_PARAM3;
if ((simulated_key[c] & (1 << r)) == (p << r))
return EC_SUCCESS;
simulated_key[c] = (simulated_key[c] & ~(1 << r)) | (p << r);
keyboard_state_changed(r, c, p);
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(kbpress, command_keyboard_press,
"[col] [row] [0 | 1]",
"Simulate keypress",
NULL);
static int command_keyboard_log(int argc, char **argv)
{
int i;
@@ -939,30 +887,6 @@ DECLARE_CONSOLE_COMMAND(kbd, command_keyboard,
"Print or toggle keyboard info",
NULL);
/*****************************************************************************/
/* Host commands */
static int mkbp_command_simulate_key(struct host_cmd_handler_args *args)
{
const struct ec_params_mkbp_simulate_key *p = args->params;
/* Only available on unlocked systems */
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
if (p->col >= ARRAY_SIZE(simulated_key))
return EC_RES_INVALID_PARAM;
simulated_key[p->col] = (simulated_key[p->col] & ~(1 << p->row)) |
(p->pressed << p->row);
keyboard_state_changed(p->row, p->col, p->pressed);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MKBP_SIMULATE_KEY,
mkbp_command_simulate_key,
EC_VER_MASK(0));
/*****************************************************************************/
/* Hooks */

View File

@@ -2,7 +2,7 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Keyboard scanner module for Chrome EC STM32
* MKBP keyboard protocol
*/
#include "atomic.h"
@@ -21,86 +21,49 @@
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_KEYSCAN, outstr)
#define CPRINTF(format, args...) cprintf(CC_KEYSCAN, format, ## args)
#define SCAN_TIME_COUNT 32
static struct mutex scanning_enabled;
static uint8_t debounced_state[KEYBOARD_COLS]; /* Debounced key matrix */
static uint8_t prev_state[KEYBOARD_COLS]; /* Matrix from previous scan */
static uint8_t debouncing[KEYBOARD_COLS]; /* Mask of keys being debounced */
static uint32_t scan_time[SCAN_TIME_COUNT]; /* Times of last scans */
static int scan_time_index; /* Current scan_time[] index */
/* Index into scan_time[] when each key started debouncing */
static uint8_t scan_edge_index[KEYBOARD_COLS][KEYBOARD_ROWS];
/*****************************************************************************/
#define CPUTS(outstr) cputs(CC_KEYBOARD, outstr)
#define CPRINTF(format, args...) cprintf(CC_KEYBOARD, format, ## args)
#define KB_FIFO_DEPTH 16 /* FIXME: this is pretty huge */
/* Changes to col,row here need to also be reflected in kernel.
* drivers/input/mkbp.c ... see KEY_BATTERY.
*/
#define BATTERY_KEY_COL 0
#define BATTERY_KEY_ROW 7
#define BATTERY_KEY_ROW_MASK (1 << BATTERY_KEY_ROW)
static uint32_t kb_fifo_start; /* first entry */
static uint32_t kb_fifo_end; /* last entry */
static uint32_t kb_fifo_end; /* last entry */
static uint32_t kb_fifo_entries; /* number of existing entries */
static uint8_t kb_fifo[KB_FIFO_DEPTH][KEYBOARD_COLS];
static struct mutex fifo_mutex;
/*
* Our configuration. The debounce parameters are not yet supported.
*/
static struct ec_mkbp_config config = {
/* Config for mkbp protocol; does not include fields from scan config */
struct ec_mkbp_protocol_config {
uint32_t valid_mask; /* valid fields */
uint8_t flags; /* some flags (enum mkbp_config_flags) */
uint8_t valid_flags; /* which flags are valid */
/* maximum depth to allow for fifo (0 = no keyscan output) */
uint8_t fifo_max_depth;
} __packed;
static struct ec_mkbp_protocol_config config = {
.valid_mask = EC_MKBP_VALID_SCAN_PERIOD | EC_MKBP_VALID_POLL_TIMEOUT |
EC_MKBP_VALID_MIN_POST_SCAN_DELAY |
EC_MKBP_VALID_OUTPUT_SETTLE | EC_MKBP_VALID_DEBOUNCE_DOWN |
EC_MKBP_VALID_DEBOUNCE_UP | EC_MKBP_VALID_FIFO_MAX_DEPTH,
.valid_flags = EC_MKBP_FLAGS_ENABLE,
.flags = EC_MKBP_FLAGS_ENABLE,
.scan_period_us = 3000,
.poll_timeout_us = 100 * 1000,
.min_post_scan_delay_us = 1000,
.output_settle_us = 50,
.debounce_down_us = 9000,
.debounce_up_us = 30000,
.fifo_max_depth = KB_FIFO_DEPTH,
};
void keyboard_clear_state(void)
{
int i;
CPRINTF("clearing keyboard fifo\n");
kb_fifo_start = 0;
kb_fifo_end = 0;
kb_fifo_entries = 0;
for (i = 0; i < KB_FIFO_DEPTH; i++)
memset(kb_fifo[i], 0, KEYBOARD_COLS);
}
int keyboard_fifo_add(const uint8_t *buffp)
{
int ret = EC_SUCCESS;
if (kb_fifo_entries >= config.fifo_max_depth) {
CPRINTF("%s: FIFO depth %d reached\n", __func__,
config.fifo_max_depth);
ret = EC_ERROR_OVERFLOW;
goto kb_fifo_push_done;
}
memcpy(kb_fifo[kb_fifo_end], buffp, KEYBOARD_COLS);
kb_fifo_end = (kb_fifo_end + 1) % KB_FIFO_DEPTH;
atomic_add(&kb_fifo_entries, 1);
kb_fifo_push_done:
return ret;
}
/**
* Pop keyboard state from FIFO
*
* @return EC_SUCCESS if entry popped, EC_ERROR_UNKNOWN if FIFO is empty
*/
* Pop keyboard state from FIFO
*
* @return EC_SUCCESS if entry popped, EC_ERROR_UNKNOWN if FIFO is empty
*/
static int kb_fifo_remove(uint8_t *buffp)
{
if (!kb_fifo_entries) {
@@ -133,331 +96,68 @@ static void set_host_interrupt(int active)
gpio_set_level(GPIO_EC_INT, !active);
}
/**
* Check special runtime key combinations.
*
* @param state Keyboard state to use when checking keys.
* @return 1 if a special key was pressed, 0 if not
*/
static int check_runtime_keys(const uint8_t *state)
/*****************************************************************************/
/* Interface */
void keyboard_clear_buffer(void)
{
int num_press;
int c;
int i;
/* Count number of key pressed */
for (c = num_press = 0; c < KEYBOARD_COLS; c++) {
if (state[c])
++num_press;
}
CPRINTF("clearing keyboard fifo\n");
if (num_press != 3)
return 0;
if (state[KEYBOARD_COL_KEY_R] == KEYBOARD_MASK_KEY_R &&
state[KEYBOARD_COL_VOL_UP] == KEYBOARD_MASK_VOL_UP &&
(state[KEYBOARD_COL_RIGHT_ALT] == KEYBOARD_MASK_RIGHT_ALT ||
state[KEYBOARD_COL_LEFT_ALT] == KEYBOARD_MASK_LEFT_ALT)) {
keyboard_clear_state();
chipset_reset(0);
return 1;
}
return 0;
kb_fifo_start = 0;
kb_fifo_end = 0;
kb_fifo_entries = 0;
for (i = 0; i < KB_FIFO_DEPTH; i++)
memset(kb_fifo[i], 0, KEYBOARD_COLS);
}
/* Print the keyboard state. */
static void print_state(const uint8_t *state, const char *msg)
int keyboard_fifo_add(const uint8_t *buffp)
{
int c;
CPRINTF("[%T KB %s:", msg);
for (c = 0; c < KEYBOARD_COLS; c++) {
if (state[c])
CPRINTF(" %02x", state[c]);
else
CPUTS(" --");
}
CPUTS("]\n");
}
/**
* Read the raw keyboard matrix state.
*
* Used in pre-init, so must not make task-switching-dependent calls; udelay()
* is ok because it's a spin-loop.
*
* @param state Destination for new state (must be KEYBOARD_COLS long).
*
* @return 1 if at least one key is pressed, else zero.
*/
static int read_matrix(uint8_t *state)
{
int c;
uint8_t r;
int pressed = 0;
for (c = 0; c < KEYBOARD_COLS; c++) {
/* Assert output, then wait a bit for it to settle */
keyboard_raw_drive_column(c);
udelay(config.output_settle_us);
r = keyboard_raw_read_rows();
#ifdef CONFIG_KEYBOARD_TEST
/* Use simulated keyscan sequence instead if testing active */
r = keyscan_seq_get_scan(c, r);
#endif
#ifdef OR_WITH_CURRENT_STATE_FOR_TESTING
/* KLUDGE - or current state in, so we can make sure
* all the lines are hooked up */
r |= state[c];
#endif
state[c] = r;
pressed |= r;
}
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
return pressed ? 1 : 0;
}
/**
* Update keyboard state using low-level interface to read keyboard.
*
* @param state Keyboard state to update.
*
* @return 1 if any key is still pressed, 0 if no key is pressed.
*/
static int check_keys_changed(uint8_t *state)
{
int any_pressed = 0;
int c, i;
int any_change = 0;
uint8_t new_state[KEYBOARD_COLS];
uint32_t tnow = get_time().le.lo;
/* Save the current scan time */
if (++scan_time_index >= SCAN_TIME_COUNT)
scan_time_index = 0;
scan_time[scan_time_index] = tnow;
/* Read the raw key state */
any_pressed = read_matrix(new_state);
/* Check for changes between previous scan and this one */
for (c = 0; c < KEYBOARD_COLS; c++) {
int diff = new_state[c] ^ prev_state[c];
if (!diff)
continue;
for (i = 0; i < KEYBOARD_ROWS; i++) {
if (diff & (1 << i))
scan_edge_index[c][i] = scan_time_index;
}
debouncing[c] |= diff;
prev_state[c] = new_state[c];
}
/* Check for keys which are done debouncing */
for (c = 0; c < KEYBOARD_COLS; c++) {
int debc = debouncing[c];
if (!debc)
continue;
for (i = 0; i < KEYBOARD_ROWS; i++) {
int mask = 1 << i;
int new_mask = new_state[c] & mask;
/* Are we done debouncing this key? */
if (!(debc & mask))
continue; /* Not debouncing this key */
if (tnow - scan_time[scan_edge_index[c][i]] <
(new_mask ? config.debounce_down_us :
config.debounce_up_us))
continue; /* Not done debouncing */
debouncing[c] &= ~mask;
/* Did the key change from its previous state? */
if ((state[c] & mask) == new_mask)
continue; /* No */
state[c] ^= mask;
any_change = 1;
}
}
if (any_change) {
#ifdef CONFIG_KEYBOARD_SUPPRESS_NOISE
keyboard_suppress_noise();
#endif
print_state(state, "state");
#ifdef PRINT_SCAN_TIMES
/* Print delta times from now back to each previous scan */
for (i = 0; i < SCAN_TIME_COUNT; i++) {
int tnew = scan_time[
(SCAN_TIME_COUNT + scan_time_index - i) %
SCAN_TIME_COUNT];
CPRINTF(" %d", tnow - tnew);
}
CPRINTF("\n");
#endif
/* Swallow special keys */
if (check_runtime_keys(state))
return 0;
else if (keyboard_fifo_add(state) == EC_SUCCESS)
set_host_interrupt(1);
else
CPRINTF("dropped keystroke\n");
}
return any_pressed;
}
/*
* Check if the user has triggered a recovery reset
*
* Pressing Power + Refresh + ESC. triggers a recovery reset. Here we check
* for this.
*
* @param state Keyboard state to check
* @return 1 if there is a recovery reset, else 0
*/
static int check_recovery_key(const uint8_t *state)
{
int c;
/* check the recovery key only if we're booting due to a
* reset-pin-caused reset. */
if (!(system_get_reset_flags() & RESET_FLAG_RESET_PIN))
return 0;
/* cold boot : Power + Refresh were pressed,
* check if ESC is also pressed for recovery. */
if (!(state[KEYBOARD_COL_ESC] & KEYBOARD_MASK_ESC))
return 0;
/* Make sure only other allowed keys are pressed. This protects
* against accidentally triggering the special key when a cat sits on
* your keyboard. Currently, only the requested key and ESC are
* allowed. */
for (c = 0; c < KEYBOARD_COLS; c++) {
if (state[c] &&
(c != KEYBOARD_COL_ESC || state[c] != KEYBOARD_MASK_ESC) &&
(c != KEYBOARD_COL_REFRESH ||
state[c] != KEYBOARD_MASK_REFRESH))
return 0; /* Additional disallowed key pressed */
}
CPRINTF("Keyboard RECOVERY detected !\n");
host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY);
return 1;
}
void keyboard_scan_init(void)
{
keyboard_raw_init();
/* Tri-state (put into Hi-Z) the outputs */
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
/* Initialize raw state */
read_matrix(debounced_state);
memcpy(prev_state, debounced_state, sizeof(prev_state));
/* is recovery key pressed on cold startup ? */
check_recovery_key(debounced_state);
}
/* Scan the keyboard until all keys are released */
static void scan_keyboard(void)
{
timestamp_t poll_deadline, start;
int keys_changed = 1;
mutex_lock(&scanning_enabled);
keyboard_raw_drive_column(KEYBOARD_COLUMN_ALL);
keyboard_raw_enable_interrupt(1);
mutex_unlock(&scanning_enabled);
int ret = EC_SUCCESS;
/*
* if a key was pressed after the last polling,
* re-start immediatly polling instead of waiting
* for the next interrupt.
* If keyboard protocol is not enabled, don't save the state to the
* FIFO or trigger an interrupt.
*/
if (!keyboard_raw_read_rows()) {
#ifdef CONFIG_KEYBOARD_TEST
task_wait_event(keyscan_seq_next_event_delay());
#else
task_wait_event(-1);
#endif
if (!(config.flags & EC_MKBP_FLAGS_ENABLE))
return EC_SUCCESS;
if (kb_fifo_entries >= config.fifo_max_depth) {
CPRINTF("[%T KB FIFO depth %d reached]\n",
config.fifo_max_depth);
ret = EC_ERROR_OVERFLOW;
goto kb_fifo_push_done;
}
keyboard_raw_enable_interrupt(0);
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
mutex_lock(&fifo_mutex);
memcpy(kb_fifo[kb_fifo_end], buffp, KEYBOARD_COLS);
kb_fifo_end = (kb_fifo_end + 1) % KB_FIFO_DEPTH;
atomic_add(&kb_fifo_entries, 1);
mutex_unlock(&fifo_mutex);
/* Busy polling keyboard state. */
while (1) {
int wait_time;
kb_fifo_push_done:
if (!(config.flags & EC_MKBP_FLAGS_ENABLE))
break;
if (ret == EC_SUCCESS)
set_host_interrupt(1);
/* If we saw any keys pressed, reset deadline */
start = get_time();
if (keys_changed)
poll_deadline.val = start.val + config.poll_timeout_us;
else if (timestamp_expired(poll_deadline, &start))
break;
/* Scan immediately, with no delay */
mutex_lock(&scanning_enabled);
keys_changed = check_keys_changed(debounced_state);
mutex_unlock(&scanning_enabled);
/* Wait a bit before scanning again */
wait_time = config.scan_period_us -
(get_time().val - start.val);
if (wait_time < config.min_post_scan_delay_us)
wait_time = config.min_post_scan_delay_us;
task_wait_event(wait_time);
}
return ret;
}
void keyboard_scan_task(void)
void keyboard_send_battery_key(void)
{
print_state(debounced_state, "init state");
uint8_t state[KEYBOARD_COLS];
keyboard_raw_task_start();
/* Copy debounced state and add battery pseudo-key */
memcpy(state, keyboard_scan_get_state(), sizeof(state));
state[BATTERY_KEY_COL] ^= BATTERY_KEY_ROW_MASK;
while (1) {
if (config.flags & EC_MKBP_FLAGS_ENABLE) {
scan_keyboard();
} else {
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
task_wait_event(-1);
}
}
/* Add to FIFO */
keyboard_fifo_add(state);
}
int keyboard_has_char(void)
{
/* TODO: needs to be implemented */
return 0;
}
void keyboard_put_char(uint8_t chr, int send_irq)
{
/* TODO: needs to be implemented */
}
/*****************************************************************************/
/* Host commands */
static int keyboard_get_scan(struct host_cmd_handler_args *args)
{
@@ -489,91 +189,13 @@ DECLARE_HOST_COMMAND(EC_CMD_MKBP_INFO,
keyboard_get_info,
EC_VER_MASK(0));
void keyboard_scan_enable(int enable)
{
if (enable) {
mutex_unlock(&scanning_enabled);
task_wake(TASK_ID_KEYSCAN);
} else {
/*
* TODO: using a mutex to control scanning isn't very
* responsive. If we just started scanning the matrix, the
* mutex will already be locked, and we'll finish the entire
* matrix scan before we stop driving columns. We should
* instead do something like link, where disabling scanning
* immediately stops driving the columns.
*/
mutex_lock(&scanning_enabled);
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
}
}
/* Changes to col,row here need to also be reflected in kernel.
* drivers/input/mkbp.c ... see KEY_BATTERY.
*/
#define BATTERY_KEY_COL 0
#define BATTERY_KEY_ROW 7
#define BATTERY_KEY_ROW_MASK (1 << BATTERY_KEY_ROW)
void keyboard_send_battery_key(void)
{
mutex_lock(&scanning_enabled);
debounced_state[BATTERY_KEY_COL] ^= BATTERY_KEY_ROW_MASK;
if (keyboard_fifo_add(debounced_state) == EC_SUCCESS)
set_host_interrupt(1);
else
CPRINTF("dropped battery keystroke\n");
mutex_unlock(&scanning_enabled);
}
static int command_keyboard_press(int argc, char **argv)
{
int r, c, p;
char *e;
if (argc != 4)
return EC_ERROR_PARAM_COUNT;
c = strtoi(argv[1], &e, 0);
if (*e || c < 0 || c >= KEYBOARD_COLS)
return EC_ERROR_PARAM1;
r = strtoi(argv[2], &e, 0);
if (*e || r < 0 || r >= KEYBOARD_ROWS)
return EC_ERROR_PARAM2;
p = strtoi(argv[3], &e, 0);
if (*e || p < 0 || p > 1)
return EC_ERROR_PARAM3;
/*
* TODO(sjg@chromium.org): This ignores debouncing, so is a bit
* dodgy and might have strange side-effects on real key scans.
*/
if (p)
debounced_state[c] |= (1 << r);
else
debounced_state[c] &= ~(1 << r);
if (keyboard_fifo_add(debounced_state) == EC_SUCCESS)
set_host_interrupt(1);
else
ccprintf("dropped keystroke\n");
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(kbpress, command_keyboard_press,
"[col] [row] [0 | 1]",
"Simulate keypress",
NULL);
/**
* Copy keyscan configuration from one place to another according to flags
*
* This is like a structure copy, except that only selected fields are
* copied.
*
* TODO(sjg@chromium.org): Consider making this table drive as ectool.
* TODO(sjg@chromium.org): Consider making this table driven as ectool.
*
* @param src Source config
* @param dst Destination config
@@ -584,35 +206,43 @@ DECLARE_CONSOLE_COMMAND(kbpress, command_keyboard_press,
* over to dst->flags
*/
static void keyscan_copy_config(const struct ec_mkbp_config *src,
struct ec_mkbp_config *dst,
struct ec_mkbp_protocol_config *dst,
uint32_t valid_mask, uint8_t valid_flags)
{
struct keyboard_scan_config *ksc = keyboard_scan_get_config();
uint8_t new_flags;
if (valid_mask & EC_MKBP_VALID_SCAN_PERIOD)
dst->scan_period_us = src->scan_period_us;
ksc->scan_period_us = src->scan_period_us;
if (valid_mask & EC_MKBP_VALID_POLL_TIMEOUT)
dst->poll_timeout_us = src->poll_timeout_us;
ksc->poll_timeout_us = src->poll_timeout_us;
if (valid_mask & EC_MKBP_VALID_MIN_POST_SCAN_DELAY) {
/*
* Key scanning is high priority, so we should require at
* least 100us min delay here. Setting this to 0 will cause
* watchdog events. Use 200 to be safe.
*/
dst->min_post_scan_delay_us =
ksc->min_post_scan_delay_us =
MAX(src->min_post_scan_delay_us, 200);
}
if (valid_mask & EC_MKBP_VALID_OUTPUT_SETTLE)
dst->output_settle_us = src->output_settle_us;
ksc->output_settle_us = src->output_settle_us;
if (valid_mask & EC_MKBP_VALID_DEBOUNCE_DOWN)
dst->debounce_down_us = src->debounce_down_us;
ksc->debounce_down_us = src->debounce_down_us;
if (valid_mask & EC_MKBP_VALID_DEBOUNCE_UP)
dst->debounce_up_us = src->debounce_up_us;
ksc->debounce_up_us = src->debounce_up_us;
if (valid_mask & EC_MKBP_VALID_FIFO_MAX_DEPTH) {
/* Sanity check for fifo depth */
dst->fifo_max_depth = MIN(src->fifo_max_depth,
KB_FIFO_DEPTH);
}
new_flags = dst->flags & ~valid_flags;
new_flags |= src->flags & valid_flags;
dst->flags = new_flags;
@@ -636,21 +266,36 @@ static int host_command_mkbp_set_config(struct host_cmd_handler_args *args)
return EC_RES_SUCCESS;
}
static int host_command_mkbp_get_config(struct host_cmd_handler_args *args)
{
struct ec_response_mkbp_get_config *resp = args->response;
memcpy(&resp->config, &config, sizeof(config));
args->response_size = sizeof(*resp);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MKBP_SET_CONFIG,
host_command_mkbp_set_config,
EC_VER_MASK(0));
static int host_command_mkbp_get_config(struct host_cmd_handler_args *args)
{
const struct keyboard_scan_config *ksc = keyboard_scan_get_config();
struct ec_response_mkbp_get_config *resp = args->response;
struct ec_mkbp_config *dst = &resp->config;
memcpy(&resp->config, &config, sizeof(config));
/* Copy fields from mkbp protocol config to mkbp config */
dst->valid_mask = config.valid_mask;
dst->flags = config.flags;
dst->valid_flags = config.valid_flags;
dst->fifo_max_depth = config.fifo_max_depth;
/* Copy fields from keyscan config to mkbp config */
dst->output_settle_us = ksc->output_settle_us;
dst->debounce_down_us = ksc->debounce_down_us;
dst->debounce_up_us = ksc->debounce_up_us;
dst->scan_period_us = ksc->scan_period_us;
dst->min_post_scan_delay_us = ksc->min_post_scan_delay_us;
dst->poll_timeout_us = ksc->poll_timeout_us;
args->response_size = sizeof(*resp);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MKBP_GET_CONFIG,
host_command_mkbp_get_config,
EC_VER_MASK(0));

View File

@@ -23,42 +23,52 @@
#define CPUTS(outstr) cputs(CC_KEYSCAN, outstr)
#define CPRINTF(format, args...) cprintf(CC_KEYSCAN, format, ## args)
/* Time constants */
#define POLLING_MODE_TIMEOUT SECOND /* Max time to poll if no keys are down */
#define DEBOUNCE_UP_US (30 * MSEC) /* Debounce time for key-up */
#define DEBOUNCE_DOWN_US (6 * MSEC) /* Debounce time for key-down */
#define SCAN_LOOP_DELAY MSEC /* Delay in scan loop */
#define COLUMN_CHARGE_US 40 /* Column charge time in usec */
#define SCAN_TIME_COUNT 32 /* Number of last scan times to track */
static struct keyboard_scan_config config = {
#ifdef BOARD_link
.output_settle_us = 40,
.debounce_down_us = 6 * MSEC,
.debounce_up_us = 30 * MSEC,
.scan_period_us = 1500,
.min_post_scan_delay_us = 1000,
.poll_timeout_us = SECOND,
#else
.output_settle_us = 50,
.debounce_down_us = 9 * MSEC,
.debounce_up_us = 30 * MSEC,
.scan_period_us = 3 * MSEC,
.min_post_scan_delay_us = 1000,
.poll_timeout_us = 100 * MSEC,
#endif
.actual_key_mask = {
0x14, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xff,
0xa4, 0xff, 0xf6, 0x55, 0xfa, 0xc8 /* full set */
},
};
/* Boot key list. Must be in same order as enum boot_key. */
struct boot_key_entry {
uint8_t mask_index;
uint8_t mask_value;
};
const struct boot_key_entry boot_key_list[] = {
static const struct boot_key_entry boot_key_list[] = {
{0, 0x00}, /* (none) */
{KEYBOARD_COL_ESC, KEYBOARD_MASK_ESC}, /* Esc */
{KEYBOARD_COL_DOWN, KEYBOARD_MASK_DOWN}, /* Down-arrow */
};
static enum boot_key boot_key_value = BOOT_KEY_OTHER;
static uint8_t debounced_state[KEYBOARD_COLS]; /* Debounced key matrix */
static uint8_t prev_state[KEYBOARD_COLS]; /* Matrix from previous scan */
static uint8_t debouncing[KEYBOARD_COLS]; /* Mask of keys being debounced */
static uint8_t simulated_key[KEYBOARD_COLS]; /* Keys simulated-pressed */
static uint32_t scan_time[SCAN_TIME_COUNT]; /* Times of last scans */
static int scan_time_index; /* Current scan_time[] index */
/* Index into scan_time[] when each key started debouncing */
static uint8_t scan_edge_index[KEYBOARD_COLS][KEYBOARD_ROWS];
enum boot_key boot_key_value = BOOT_KEY_OTHER;
/* Mask with 1 bits only for keys that actually exist */
static const uint8_t actual_key_mask[KEYBOARD_COLS] = {
0x14, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xff,
0xa4, 0xff, 0xf6, 0x55, 0xfa, 0xc8 /* full set */
};
/*
* Print all keyboard scan state changes? Off by default because it generates
* a lot of debug output, which makes the saved EC console data less useful.
@@ -67,67 +77,16 @@ static int print_state_changes;
static int enable_scanning = 1; /* Must init to 1 for scanning at boot */
static void enable_interrupt(void)
{
CPRINTF("[%T KB wait]\n");
if (enable_scanning)
keyboard_raw_drive_column(KEYBOARD_COLUMN_ALL);
keyboard_raw_enable_interrupt(1);
}
static void enter_polling_mode(void)
{
CPRINTF("[%T KB poll]\n");
keyboard_raw_enable_interrupt(0);
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
}
static int is_scanning_enabled(void)
{
/* Scan only if enabled AND lid is open. */
return enable_scanning && switch_get_lid_open();
}
#ifdef BOARD_link
/* TODO: should apply to ARM too, but need standard lid API */
/* Scanning is never enabled when lid is closed */
if (!switch_get_lid_open())
return 0;
#endif
/**
* Read the raw keyboard matrix state.
*
* Used in pre-init, so must not make task-switching-dependent calls; udelay()
* is ok because it's a spin-loop.
*
* @param state Destination for new state (must be KEYBOARD_COLS long).
*
* @return 1 if at least one key is pressed, else zero.
*/
static int read_matrix(uint8_t *state)
{
int c;
uint8_t r;
int pressed = 0;
for (c = 0; c < KEYBOARD_COLS; c++) {
/* Stop if scanning becomes disabled */
if (!enable_scanning)
break;
/* Select column, then wait a bit for it to settle */
keyboard_raw_drive_column(c);
udelay(COLUMN_CHARGE_US);
/* Read the row state */
r = keyboard_raw_read_rows();
/* Mask off keys that don't exist so they never show
* as pressed */
r &= actual_key_mask[c];
state[c] = r;
pressed |= r;
}
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
return pressed ? 1 : 0;
return enable_scanning;
}
/**
@@ -150,12 +109,84 @@ static void print_state(const uint8_t *state, const char *msg)
CPUTS("]\n");
}
/**
* Simulate a keypress.
*
* @param row Row of key
* @param col Column of key
* @param pressed Non-zero if pressed, zero if released
*/
static void simulate_key(int row, int col, int pressed)
{
if ((simulated_key[col] & (1 << row)) == ((pressed ? 1 : 0) << row))
return; /* No change */
simulated_key[col] ^= (1 << row);
print_state(simulated_key, "simulated ");
/* Wake the task to handle changes in simulated keys */
task_wake(TASK_ID_KEYSCAN);
}
/**
* Read the raw keyboard matrix state.
*
* Used in pre-init, so must not make task-switching-dependent calls; udelay()
* is ok because it's a spin-loop.
*
* @param state Destination for new state (must be KEYBOARD_COLS long).
*
* @return 1 if at least one key is pressed, else zero.
*/
static int read_matrix(uint8_t *state)
{
int c;
uint8_t r;
int pressed = 0;
for (c = 0; c < KEYBOARD_COLS; c++) {
/*
* Stop if scanning becomes disabled. Check enable_cscanning
* instead of is_scanning_enabled() so that we can scan the
* matrix at boot time before the lid switch is readable.
*/
if (!enable_scanning)
break;
/* Select column, then wait a bit for it to settle */
keyboard_raw_drive_column(c);
udelay(config.output_settle_us);
/* Read the row state */
r = keyboard_raw_read_rows();
/* Mask off keys that don't exist on the actual keyboard */
r &= config.actual_key_mask[c];
/* Add in simulated keypresses */
r |= simulated_key[c];
#ifdef CONFIG_KEYBOARD_TEST
/* Use simulated keyscan sequence instead if testing active */
r = keyscan_seq_get_scan(c, r);
#endif
state[c] = r;
pressed |= r;
}
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
return pressed ? 1 : 0;
}
/**
* Check special runtime key combinations.
*
* @param state Keyboard state to use when checking keys.
*
* @return 1 if a special key was pressed, 0 if not
*/
static void check_runtime_keys(const uint8_t *state)
static int check_runtime_keys(const uint8_t *state)
{
int num_press = 0;
int c;
@@ -165,10 +196,11 @@ static void check_runtime_keys(const uint8_t *state)
* key NOT on the same col as alt or volume up )
*/
if (state[KEYBOARD_COL_VOL_UP] != KEYBOARD_MASK_VOL_UP)
return;
return 0;
if (state[KEYBOARD_COL_RIGHT_ALT] != KEYBOARD_MASK_RIGHT_ALT &&
state[KEYBOARD_COL_LEFT_ALT] != KEYBOARD_MASK_LEFT_ALT)
return;
return 0;
/*
* Count number of columns with keys pressed. We know two columns are
@@ -179,24 +211,34 @@ static void check_runtime_keys(const uint8_t *state)
if (state[c])
num_press++;
}
if (num_press != 3)
return;
return 0;
/* Check individual keys */
if (state[KEYBOARD_COL_KEY_R] == KEYBOARD_MASK_KEY_R) {
/* R = reboot */
CPRINTF("[%T KB warm reboot]\n");
keyboard_clear_buffer();
chipset_reset(0);
return 1;
} else if (state[KEYBOARD_COL_KEY_H] == KEYBOARD_MASK_KEY_H) {
/* H = hibernate */
CPRINTF("[%T KB hibernate]\n");
system_hibernate(0, 0);
return 1;
}
return 0;
}
/**
* Check for ghosting in the keyboard state.
*
* Assumes that the state has already been masked with the actual key mask, so
* that coords which don't correspond with actual keys don't trigger ghosting
* detection.
*
* @param state Keyboard state to check.
*
* @return 1 if ghosting detected, else 0.
@@ -262,6 +304,7 @@ static int check_keys_changed(uint8_t *state)
/* Check for changes between previous scan and this one */
for (c = 0; c < KEYBOARD_COLS; c++) {
int diff = new_state[c] ^ prev_state[c];
if (!diff)
continue;
@@ -277,6 +320,7 @@ static int check_keys_changed(uint8_t *state)
/* Check for keys which are done debouncing */
for (c = 0; c < KEYBOARD_COLS; c++) {
int debc = debouncing[c];
if (!debc)
continue;
@@ -288,7 +332,8 @@ static int check_keys_changed(uint8_t *state)
if (!(debc & mask))
continue; /* Not debouncing this key */
if (tnow - scan_time[scan_edge_index[c][i]] <
(new_mask ? DEBOUNCE_DOWN_US : DEBOUNCE_UP_US))
(new_mask ? config.debounce_down_us :
config.debounce_up_us))
continue; /* Not done debouncing */
debouncing[c] &= ~mask;
@@ -300,13 +345,21 @@ static int check_keys_changed(uint8_t *state)
state[c] ^= mask;
any_change = 1;
#ifdef CONFIG_KEYBOARD_PROTOCOL_8042
/* Inform keyboard module if scanning is enabled */
if (is_scanning_enabled())
keyboard_state_changed(i, c, new_mask ? 1 : 0);
#endif
}
}
if (any_change) {
#ifdef CONFIG_KEYBOARD_SUPPRESS_NOISE
/* Suppress keyboard noise */
keyboard_suppress_noise();
#endif
if (print_state_changes)
print_state(state, "state");
@@ -321,7 +374,13 @@ static int check_keys_changed(uint8_t *state)
CPRINTF("\n");
#endif
check_runtime_keys(state);
/* Swallow special keys */
if (check_runtime_keys(state))
return 0;
#ifdef CONFIG_KEYBOARD_PROTOCOL_MKBP
keyboard_fifo_add(state);
#endif
}
return any_pressed;
@@ -360,7 +419,7 @@ static int check_key(const uint8_t *state, int index, int mask)
* key combination is down or this isn't the right type of boot to look at
* boot keys.
*/
static enum boot_key keyboard_scan_check_boot_key(const uint8_t *state)
static enum boot_key check_boot_key(const uint8_t *state)
{
const struct boot_key_entry *k = boot_key_list;
int i;
@@ -389,11 +448,24 @@ static enum boot_key keyboard_scan_check_boot_key(const uint8_t *state)
return BOOT_KEY_OTHER;
}
/*****************************************************************************/
/* Interface */
struct keyboard_scan_config *keyboard_scan_get_config(void)
{
return &config;
}
enum boot_key keyboard_scan_get_boot_key(void)
{
return boot_key_value;
}
const uint8_t *keyboard_scan_get_state(void)
{
return debounced_state;
}
void keyboard_scan_init(void)
{
/* Configure GPIO */
@@ -407,7 +479,7 @@ void keyboard_scan_init(void)
memcpy(prev_state, debounced_state, sizeof(prev_state));
/* Check for keys held down at boot */
boot_key_value = keyboard_scan_check_boot_key(debounced_state);
boot_key_value = check_boot_key(debounced_state);
/* Trigger event if recovery key was pressed */
if (boot_key_value == BOOT_KEY_ESC)
@@ -416,7 +488,8 @@ void keyboard_scan_init(void)
void keyboard_scan_task(void)
{
int key_press_timer = 0;
timestamp_t poll_deadline, start;
int wait_time;
print_state(debounced_state, "init state");
@@ -424,7 +497,10 @@ void keyboard_scan_task(void)
while (1) {
/* Enable all outputs */
enable_interrupt();
CPRINTF("[%T KB wait]\n");
if (is_scanning_enabled())
keyboard_raw_drive_column(KEYBOARD_COLUMN_ALL);
keyboard_raw_enable_interrupt(1);
/* Wait for scanning enabled and key pressed. */
do {
@@ -438,21 +514,31 @@ void keyboard_scan_task(void)
task_wait_event(-1);
} while (!is_scanning_enabled());
enter_polling_mode();
/* Enter polling mode */
CPRINTF("[%T KB poll]\n");
keyboard_raw_enable_interrupt(0);
keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
/* Busy polling keyboard state. */
while (is_scanning_enabled()) {
start = get_time();
/* Check for keys down */
if (check_keys_changed(debounced_state)) {
key_press_timer = 0;
} else if (++key_press_timer >=
(POLLING_MODE_TIMEOUT / SCAN_LOOP_DELAY)) {
/* Stop polling */
key_press_timer = 0;
poll_deadline.val = start.val
+ config.poll_timeout_us;
} else if (timestamp_expired(poll_deadline, &start)) {
break;
}
/* Delay between scans */
usleep(SCAN_LOOP_DELAY);
wait_time = config.scan_period_us -
(get_time().val - start.val);
if (wait_time < config.min_post_scan_delay_us)
wait_time = config.min_post_scan_delay_us;
usleep(wait_time);
}
}
}
@@ -474,6 +560,28 @@ void keyboard_scan_enable(int enable)
}
}
/*****************************************************************************/
/* Host commands */
static int mkbp_command_simulate_key(struct host_cmd_handler_args *args)
{
const struct ec_params_mkbp_simulate_key *p = args->params;
/* Only available on unlocked systems */
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
if (p->col >= KEYBOARD_COLS || p->row >= KEYBOARD_ROWS)
return EC_RES_INVALID_PARAM;
simulate_key(p->row, p->col, p->pressed);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MKBP_SIMULATE_KEY,
mkbp_command_simulate_key,
EC_VER_MASK(0));
/*****************************************************************************/
/* Console commands */
@@ -500,3 +608,43 @@ DECLARE_CONSOLE_COMMAND(ksstate, command_ksstate,
"ksstate [on | off]",
"Show or toggle printing keyboard scan state",
NULL);
static int command_keyboard_press(int argc, char **argv)
{
if (argc == 1) {
int i, j;
ccputs("Simulated keys:\n");
for (i = 0; i < KEYBOARD_COLS; ++i) {
if (simulated_key[i] == 0)
continue;
for (j = 0; j < KEYBOARD_ROWS; ++j)
if (simulated_key[i] & (1 << j))
ccprintf("\t%d %d\n", i, j);
}
} else if (argc == 4) {
int r, c, p;
char *e;
c = strtoi(argv[1], &e, 0);
if (*e || c < 0 || c >= KEYBOARD_COLS)
return EC_ERROR_PARAM1;
r = strtoi(argv[2], &e, 0);
if (*e || r < 0 || r >= KEYBOARD_ROWS)
return EC_ERROR_PARAM2;
p = strtoi(argv[3], &e, 0);
if (*e || p < 0 || p > 1)
return EC_ERROR_PARAM3;
simulate_key(r, c, p);
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(kbpress, command_keyboard_press,
"[col] [row] [0 | 1]",
"Simulate keypress",
NULL);

View File

@@ -9,12 +9,40 @@
#define __CROS_EC_KEYBOARD_SCAN_H
#include "common.h"
#include "keyboard_config.h"
struct keyboard_scan_config {
/* Delay between setting up output and waiting for it to settle */
uint16_t output_settle_us;
/* Times for debouncing key-down and key-up */
uint16_t debounce_down_us;
uint16_t debounce_up_us;
/* Time between start of scans when in polling mode */
uint16_t scan_period_us;
/*
* Minimum time between end of one scan and start of the next one.
* This ensures keyboard scanning doesn't starve the rest of the system
* if the scan period is set too short, or if other higher-priority
* system activity is starving the keyboard scan task too.
*/
uint16_t min_post_scan_delay_us;
/* Revert to interrupt mode after no keyboard activity for this long */
uint32_t poll_timeout_us;
/* Mask with 1 bits only for keys that actually exist */
uint8_t actual_key_mask[KEYBOARD_COLS];
};
/**
* Initializes the module.
*/
void keyboard_scan_init(void);
/**
* Return a pointer to the keyboard scan config.
*/
struct keyboard_scan_config *keyboard_scan_get_config(void);
/* Key held down at keyboard-controlled reset boot time. */
enum boot_key {
BOOT_KEY_NONE, /* No keys other than keyboard-controlled reset keys */
@@ -30,6 +58,12 @@ enum boot_key {
*/
enum boot_key keyboard_scan_get_boot_key(void);
/**
* Return a pointer to the current debounced keyboard matrix state, which is
* KEYBOARD_COLS bytes long.
*/
const uint8_t *keyboard_scan_get_state(void);
/**
* Enables/disables keyboard matrix scan.
*/