summaryrefslogtreecommitdiff
path: root/common/keyboard_mkbp.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/keyboard_mkbp.c')
-rw-r--r--common/keyboard_mkbp.c656
1 files changed, 656 insertions, 0 deletions
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));