diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/build.mk | 6 | ||||
-rw-r--r-- | common/console_output.c | 1 | ||||
-rw-r--r-- | common/gaia_power.c | 9 | ||||
-rw-r--r-- | common/i8042.c | 6 | ||||
-rw-r--r-- | common/keyboard_8042.c (renamed from common/keyboard.c) | 32 | ||||
-rw-r--r-- | common/keyboard_mkbp.c | 656 | ||||
-rw-r--r-- | common/keyboard_scan.c | 502 | ||||
-rw-r--r-- | common/main.c | 3 |
8 files changed, 1184 insertions, 31 deletions
diff --git a/common/build.mk b/common/build.mk index 99865dcfc4..1136015b7d 100644 --- a/common/build.mk +++ b/common/build.mk @@ -21,6 +21,10 @@ 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 @@ -30,7 +34,7 @@ common-$(CONFIG_SMART_BATTERY)+=smart_battery.o smart_battery_stub.o 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.o +common-$(CONFIG_TASK_I8042CMD)+=i8042.o keyboard_8042.o common-$(CONFIG_TASK_LIGHTBAR)+=lightbar.o common-$(CONFIG_TASK_THERMAL)+=thermal.o common-$(CONFIG_TASK_VBOOTHASH)+=sha256.o vboot_hash.o diff --git a/common/console_output.c b/common/console_output.c index 12e35f55a3..b7985883cc 100644 --- a/common/console_output.c +++ b/common/console_output.c @@ -26,7 +26,6 @@ static const char *channel_names[CC_CHANNEL_COUNT] = { "gpio", "hostcmd", "i2c", - "i8042", "keyboard", "keyscan", "lightbar", diff --git a/common/gaia_power.c b/common/gaia_power.c index 7f53dd800e..8252240aae 100644 --- a/common/gaia_power.c +++ b/common/gaia_power.c @@ -174,7 +174,7 @@ static int check_for_power_off_event(void) /* Dis/Enable keyboard scanning when the power button state changes */ if (!pressed || pressed != power_button_was_pressed) - keyboard_enable_scanning(!pressed); + keyboard_scan_enable(!pressed); now = get_time(); @@ -263,13 +263,6 @@ static int gaia_power_init(void) auto_power_on = 1; } - /* Auto power on if the recovery combination was pressed */ - if (keyboard_scan_recovery_pressed()) { - CPRINTF("[%T auto_power_on is set due to " - "keyboard_scan_recovery_pressed() ...]\n"); - auto_power_on = 1; - } - return EC_SUCCESS; } diff --git a/common/i8042.c b/common/i8042.c index 5c3b1bbf06..683bfbe1b1 100644 --- a/common/i8042.c +++ b/common/i8042.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -11,7 +11,7 @@ #include "config.h" #include "console.h" #include "i8042.h" -#include "keyboard.h" +#include "keyboard_protocol.h" #include "lpc.h" #include "queue.h" #include "task.h" @@ -19,7 +19,7 @@ #include "util.h" /* Console output macros */ -#define CPRINTF(format, args...) cprintf(CC_I8042, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_KEYBOARD, format, ## args) static int i8042_irq_enabled; diff --git a/common/keyboard.c b/common/keyboard_8042.c index feda6d884a..1a991d7075 100644 --- a/common/keyboard.c +++ b/common/keyboard_8042.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -12,8 +12,8 @@ #include "host_command.h" #include "i8042.h" #include "i8042_protocol.h" -#include "keyboard.h" #include "keyboard_config.h" +#include "keyboard_protocol.h" #include "lightbar.h" #include "lpc.h" #include "registers.h" @@ -54,10 +54,10 @@ enum scancode_set_list { /* * i8042 global settings. */ -static int keyboard_enabled = 0; /* default the keyboard is disabled. */ -static int keystroke_enabled; /* output keystrokes */ +static int keyboard_enabled; /* default the keyboard is disabled. */ +static int keystroke_enabled; /* output keystrokes */ static uint8_t resend_command[MAX_SCAN_CODE_LEN]; -static uint8_t resend_command_len = 0; +static uint8_t resend_command_len; static uint8_t controller_ram_address; static uint8_t controller_ram[0x20] = { /* the so called "command byte" */ @@ -65,7 +65,7 @@ static uint8_t controller_ram[0x20] = { /* 0x01 - 0x1f are controller RAM */ }; static uint8_t A20_status; -static int power_button_pressed = 0; +static int power_button_pressed; static void keyboard_special(uint16_t k); /* @@ -93,7 +93,7 @@ static uint8_t typematic_value_from_host = DEFAULT_TYPEMATIC_VALUE; static int refill_first_delay = DEFAULT_FIRST_DELAY; /* unit: ms */ static int refill_inter_delay = DEFAULT_INTER_DELAY; /* unit: ms */ static int typematic_delay; /* unit: us */ -static int typematic_len = 0; /* length of typematic_scan_code */ +static int typematic_len; /* length of typematic_scan_code */ static uint8_t typematic_scan_code[MAX_SCAN_CODE_LEN]; @@ -257,7 +257,7 @@ static void reset_rate_and_delay(void) } -void keyboard_clear_underlying_buffer(void) +void keyboard_clear_buffer(void) { i8042_flush_buffer(); } @@ -493,25 +493,25 @@ int handle_keyboard_data(uint8_t data, uint8_t *output) case I8042_CMD_ENABLE: output[out_len++] = I8042_RET_ACK; keystroke_enable(1); - keyboard_clear_underlying_buffer(); + keyboard_clear_buffer(); break; case I8042_CMD_RESET_DIS: output[out_len++] = I8042_RET_ACK; keystroke_enable(0); reset_rate_and_delay(); - keyboard_clear_underlying_buffer(); + keyboard_clear_buffer(); break; case I8042_CMD_RESET_DEF: output[out_len++] = I8042_RET_ACK; reset_rate_and_delay(); - keyboard_clear_underlying_buffer(); + keyboard_clear_buffer(); break; case I8042_CMD_RESET_BAT: reset_rate_and_delay(); - keyboard_clear_underlying_buffer(); + keyboard_clear_buffer(); output[out_len++] = I8042_RET_ACK; output[out_len++] = I8042_RET_BAT; output[out_len++] = I8042_RET_BAT; @@ -644,7 +644,7 @@ int handle_keyboard_command(uint8_t command, uint8_t *output) } else { CPRINTF("[%T KB unsupported cmd: 0x%02x]\n", command); reset_rate_and_delay(); - keyboard_clear_underlying_buffer(); + keyboard_clear_buffer(); output[out_len++] = I8042_RET_NAK; data_port_state = STATE_NORMAL; } @@ -658,7 +658,7 @@ int handle_keyboard_command(uint8_t command, uint8_t *output) /* U U D D L R L R b a */ static void keyboard_special(uint16_t k) { - static uint8_t s = 0; + static uint8_t s; static const uint16_t a[] = {0xe048, 0xe048, 0xe050, 0xe050, 0xe04b, 0xe04d, 0xe04b, 0xe04d, 0x0030, 0x001e}; #ifdef CONFIG_TASK_LIGHTBAR @@ -984,7 +984,7 @@ static void keyboard_preserve_state(void) state.ctlram = controller_ram[0]; system_add_jump_tag(KB_SYSJUMP_TAG, KB_HOOK_VERSION, - sizeof(state), &state); + sizeof(state), &state); } DECLARE_HOOK(HOOK_SYSJUMP, keyboard_preserve_state, HOOK_PRIO_DEFAULT); @@ -997,7 +997,7 @@ static void keyboard_restore_state(void) int version, size; prev = (const struct kb_state *)system_get_jump_tag(KB_SYSJUMP_TAG, - &version, &size); + &version, &size); if (prev && version == KB_HOOK_VERSION && size == sizeof(*prev)) { /* Coming back from a sysjump, so restore settings. */ scancode_set = prev->codeset; diff --git a/common/keyboard_mkbp.c b/common/keyboard_mkbp.c new file mode 100644 index 0000000000..9d2613b187 --- /dev/null +++ b/common/keyboard_mkbp.c @@ -0,0 +1,656 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * 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 + */ + +#include "atomic.h" +#include "chipset.h" +#include "console.h" +#include "gpio.h" +#include "host_command.h" +#include "keyboard_config.h" +#include "keyboard_protocol.h" +#include "keyboard_raw.h" +#include "keyboard_scan.h" +#include "keyboard_test.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#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 KB_FIFO_DEPTH 16 /* FIXME: this is pretty huge */ +static uint32_t kb_fifo_start; /* first 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]; + +/* + * Our configuration. The debounce parameters are not yet supported. + */ +static struct ec_mkbp_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 + */ +static int kb_fifo_remove(uint8_t *buffp) +{ + if (!kb_fifo_entries) { + /* no entry remaining in FIFO : return last known state */ + int last = (kb_fifo_start + KB_FIFO_DEPTH - 1) % KB_FIFO_DEPTH; + memcpy(buffp, kb_fifo[last], KEYBOARD_COLS); + + /* + * Bail out without changing any FIFO indices and let the + * caller know something strange happened. The buffer will + * will contain the last known state of the keyboard. + */ + return EC_ERROR_UNKNOWN; + } + memcpy(buffp, kb_fifo[kb_fifo_start], KEYBOARD_COLS); + + kb_fifo_start = (kb_fifo_start + 1) % KB_FIFO_DEPTH; + + atomic_sub(&kb_fifo_entries, 1); + + return EC_SUCCESS; +} + +/** + * Assert host keyboard interrupt line. + */ +static void set_host_interrupt(int active) +{ + /* interrupt host by using active low EC_INT signal */ + 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) +{ + int num_press; + int c; + + /* Count number of key pressed */ + for (c = num_press = 0; c < KEYBOARD_COLS; c++) { + if (state[c]) + ++num_press; + } + + 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; +} + +/* Print the keyboard state. */ +static void print_state(const uint8_t *state, const char *msg) +{ + 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); + + /* + * if a key was pressed after the last polling, + * re-start immediatly polling instead of waiting + * for the next interrupt. + */ + if (!keyboard_raw_read_rows()) { +#ifdef CONFIG_KEYBOARD_TEST + task_wait_event(keyscan_seq_next_event_delay()); +#else + task_wait_event(-1); +#endif + } + + keyboard_raw_enable_interrupt(0); + keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE); + + /* Busy polling keyboard state. */ + while (1) { + int wait_time; + + if (!(config.flags & EC_MKBP_FLAGS_ENABLE)) + break; + + /* 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); + } +} + +void keyboard_scan_task(void) +{ + print_state(debounced_state, "init state"); + + keyboard_raw_task_start(); + + while (1) { + if (config.flags & EC_MKBP_FLAGS_ENABLE) { + scan_keyboard(); + } else { + keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE); + task_wait_event(-1); + } + } +} + +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 */ +} + +static int keyboard_get_scan(struct host_cmd_handler_args *args) +{ + kb_fifo_remove(args->response); + if (!kb_fifo_entries) + set_host_interrupt(0); + + args->response_size = KEYBOARD_COLS; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_MKBP_STATE, + keyboard_get_scan, + EC_VER_MASK(0)); + +static int keyboard_get_info(struct host_cmd_handler_args *args) +{ + struct ec_response_mkbp_info *r = args->response; + + r->rows = KEYBOARD_ROWS; + r->cols = KEYBOARD_COLS; + r->switches = 0; + + args->response_size = sizeof(*r); + + return EC_RES_SUCCESS; +} +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. + * + * @param src Source config + * @param dst Destination config + * @param valid_mask Bits representing which fields to copy - each bit is + * from enum mkbp_config_valid + * @param valid_flags Bit mask controlling flags to copy. Any 1 bit means + * that the corresponding bit in src->flags is copied + * over to dst->flags + */ +static void keyscan_copy_config(const struct ec_mkbp_config *src, + struct ec_mkbp_config *dst, + uint32_t valid_mask, uint8_t valid_flags) +{ + uint8_t new_flags; + + if (valid_mask & EC_MKBP_VALID_SCAN_PERIOD) + dst->scan_period_us = src->scan_period_us; + if (valid_mask & EC_MKBP_VALID_POLL_TIMEOUT) + dst->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 = + MAX(src->min_post_scan_delay_us, 200); + } + if (valid_mask & EC_MKBP_VALID_OUTPUT_SETTLE) + dst->output_settle_us = src->output_settle_us; + if (valid_mask & EC_MKBP_VALID_DEBOUNCE_DOWN) + dst->debounce_down_us = src->debounce_down_us; + if (valid_mask & EC_MKBP_VALID_DEBOUNCE_UP) + dst->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; + + /* + * If we just enabled key scanning, kick the task so that it will + * fall out of the task_wait_event() in keyboard_scan_task(). + */ + if ((new_flags & EC_MKBP_FLAGS_ENABLE) && + !(dst->flags & EC_MKBP_FLAGS_ENABLE)) + task_wake(TASK_ID_KEYSCAN); +} + +static int host_command_mkbp_set_config(struct host_cmd_handler_args *args) +{ + const struct ec_params_mkbp_set_config *req = args->params; + + keyscan_copy_config(&req->config, &config, + config.valid_mask & req->config.valid_mask, + config.valid_flags & req->config.valid_flags); + + 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)); + +DECLARE_HOST_COMMAND(EC_CMD_MKBP_GET_CONFIG, + host_command_mkbp_get_config, + EC_VER_MASK(0)); diff --git a/common/keyboard_scan.c b/common/keyboard_scan.c new file mode 100644 index 0000000000..c01326d956 --- /dev/null +++ b/common/keyboard_scan.c @@ -0,0 +1,502 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * 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 */ + +#include "chipset.h" +#include "common.h" +#include "console.h" +#include "host_command.h" +#include "keyboard_config.h" +#include "keyboard_protocol.h" +#include "keyboard_raw.h" +#include "keyboard_scan.h" +#include "switch.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +/* Console output macros */ +#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 */ + +/* 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[] = { + {0, 0x00}, /* (none) */ + {KEYBOARD_COL_ESC, KEYBOARD_MASK_ESC}, /* Esc */ + {KEYBOARD_COL_DOWN, KEYBOARD_MASK_DOWN}, /* Down-arrow */ +}; + +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]; + +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. + */ +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(); +} + +/** + * 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; +} + +/** + * Print the keyboard state. + * + * @param state State array to print + * @param msg Description of state + */ +static void print_state(const uint8_t *state, const char *msg) +{ + 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"); +} + +/** + * Check special runtime key combinations. + * + * @param state Keyboard state to use when checking keys. + */ +static void check_runtime_keys(const uint8_t *state) +{ + int num_press = 0; + int c; + + /* + * All runtime key combos are (right or left ) alt + volume up + (some + * key NOT on the same col as alt or volume up ) + */ + if (state[KEYBOARD_COL_VOL_UP] != KEYBOARD_MASK_VOL_UP) + return; + if (state[KEYBOARD_COL_RIGHT_ALT] != KEYBOARD_MASK_RIGHT_ALT && + state[KEYBOARD_COL_LEFT_ALT] != KEYBOARD_MASK_LEFT_ALT) + return; + + /* + * Count number of columns with keys pressed. We know two columns are + * pressed for volume up and alt, so if only one more key is pressed + * there will be exactly 3 non-zero columns. + */ + for (c = 0; c < KEYBOARD_COLS; c++) { + if (state[c]) + num_press++; + } + if (num_press != 3) + return; + + /* Check individual keys */ + if (state[KEYBOARD_COL_KEY_R] == KEYBOARD_MASK_KEY_R) { + /* R = reboot */ + CPRINTF("[%T KB warm reboot]\n"); + chipset_reset(0); + } else if (state[KEYBOARD_COL_KEY_H] == KEYBOARD_MASK_KEY_H) { + /* H = hibernate */ + CPRINTF("[%T KB hibernate]\n"); + system_hibernate(0, 0); + } +} + +/** + * Check for ghosting in the keyboard state. + * + * @param state Keyboard state to check. + * + * @return 1 if ghosting detected, else 0. + */ +static int has_ghosting(const uint8_t *state) +{ + int c, c2; + + for (c = 0; c < KEYBOARD_COLS; c++) { + if (!state[c]) + continue; + + for (c2 = c + 1; c2 < KEYBOARD_COLS; c2++) { + /* + * A little bit of cleverness here. Ghosting happens + * if 2 columns share at least 2 keys. So we OR the + * columns together and then see if more than one bit + * is set. x&(x-1) is non-zero only if x has more than + * one bit set. + */ + uint8_t common = state[c] & state[c2]; + + if (common & (common - 1)) + return 1; + } + } + + return 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); + + /* Ignore if so many keys are pressed that we're ghosting */ + /* + * TODO: maybe in this case we should reset all the debounce times, + * because in the ghosting case we're not paying attention to any of + * the keys which aren't ghosting. + */ + if (has_ghosting(new_state)) + return any_pressed; + + /* 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 ? DEBOUNCE_DOWN_US : 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; + + /* Inform keyboard module if scanning is enabled */ + if (is_scanning_enabled()) + keyboard_state_changed(i, c, new_mask ? 1 : 0); + } + } + + if (any_change) { + if (print_state_changes) + 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 + + check_runtime_keys(state); + } + + return any_pressed; +} + +/* + * Return non-zero if the specified key is pressed, with at most the keys used + * for keyboard-controlled reset also pressed. + */ +static int check_key(const uint8_t *state, int index, int mask) +{ + uint8_t allowed_mask[KEYBOARD_COLS] = {0}; + int c; + + /* Check for the key */ + if (mask && !(state[index] & mask)) + return 0; + + /* Check for other allowed keys */ + allowed_mask[index] |= mask; + allowed_mask[KEYBOARD_COL_REFRESH] |= KEYBOARD_MASK_REFRESH; + + for (c = 0; c < KEYBOARD_COLS; c++) { + if (state[c] & ~allowed_mask[c]) + return 0; /* Disallowed key pressed */ + } + return 1; +} + +/** + * Check what boot key is down, if any. + * + * @param state Keyboard state at boot. + * + * @return the key which is down, or BOOT_KEY_OTHER if an unrecognized + * 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) +{ + const struct boot_key_entry *k = boot_key_list; + int i; + + /* + * If we jumped to this image, ignore boot keys. This prevents + * re-triggering events in RW firmware that were already processed by + * RO firmware. + */ + if (system_jumped_to_this_image()) + return BOOT_KEY_OTHER; + + /* If reset was not caused by reset pin, refresh must be held down */ + if (!(system_get_reset_flags() & RESET_FLAG_RESET_PIN) && + !(state[KEYBOARD_COL_REFRESH] & KEYBOARD_MASK_REFRESH)) + return BOOT_KEY_OTHER; + + /* Check what single key is down */ + for (i = 0; i < ARRAY_SIZE(boot_key_list); i++, k++) { + if (check_key(state, k->mask_index, k->mask_value)) { + CPRINTF("[%T KB boot key %d]\n", i); + return i; + } + } + + return BOOT_KEY_OTHER; +} + +enum boot_key keyboard_scan_get_boot_key(void) +{ + return boot_key_value; +} + +void keyboard_scan_init(void) +{ + /* Configure GPIO */ + keyboard_raw_init(); + + /* Tri-state the columns */ + keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE); + + /* Initialize raw state */ + read_matrix(debounced_state); + 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); + + /* Trigger event if recovery key was pressed */ + if (boot_key_value == BOOT_KEY_ESC) + host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY); +} + +void keyboard_scan_task(void) +{ + int key_press_timer = 0; + + print_state(debounced_state, "init state"); + + keyboard_raw_task_start(); + + while (1) { + /* Enable all outputs */ + enable_interrupt(); + + /* Wait for scanning enabled and key pressed. */ + do { + /* + * Don't wait if scanning is enabled and a key is + * already pressed. This prevents a race between the + * user pressing a key and enable_interrupt() + * starting to pay attention to edges. + */ + if (!keyboard_raw_read_rows() || !is_scanning_enabled()) + task_wait_event(-1); + } while (!is_scanning_enabled()); + + enter_polling_mode(); + /* Busy polling keyboard state. */ + while (is_scanning_enabled()) { + /* 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; + break; + } + + /* Delay between scans */ + usleep(SCAN_LOOP_DELAY); + } + } +} + +void keyboard_scan_enable(int enable) +{ + enable_scanning = enable; + + if (enable) { + /* + * A power button press had tri-stated all columns (see the + * 'else' statement below); we need a wake-up to unlock the + * task_wait_event() loop after enable_interrupt(). + */ + task_wake(TASK_ID_KEYSCAN); + } else { + keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE); + keyboard_clear_buffer(); + } +} + +/*****************************************************************************/ +/* Console commands */ + +static int command_ksstate(int argc, char **argv) +{ + if (argc > 1) { + if (!strcasecmp(argv[1], "on")) + print_state_changes = 1; + else if (!strcasecmp(argv[1], "off")) + print_state_changes = 0; + else + return EC_ERROR_PARAM1; + } else { + print_state(debounced_state, "debounced "); + print_state(prev_state, "prev "); + print_state(debouncing, "debouncing"); + } + + ccprintf("Keyboard scan state printing %s\n", + print_state_changes ? "on" : "off"); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(ksstate, command_ksstate, + "ksstate [on | off]", + "Show or toggle printing keyboard scan state", + NULL); diff --git a/common/main.c b/common/main.c index 0830cc6879..cb6b0b8bbb 100644 --- a/common/main.c +++ b/common/main.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -15,7 +15,6 @@ #include "gpio.h" #include "hooks.h" #include "jtag.h" -#include "keyboard.h" #include "keyboard_scan.h" #include "system.h" #include "task.h" |