721 lines
19 KiB
C
721 lines
19 KiB
C
|
/* Copyright 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.
|
||
|
*
|
||
|
* MKBP keyboard protocol
|
||
|
*/
|
||
|
|
||
|
#include "atomic.h"
|
||
|
#include "base_state.h"
|
||
|
#include "button.h"
|
||
|
#include "chipset.h"
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "ec_commands.h"
|
||
|
#include "gpio.h"
|
||
|
#include "hooks.h"
|
||
|
#include "host_command.h"
|
||
|
#include "keyboard_config.h"
|
||
|
#include "keyboard_mkbp.h"
|
||
|
#include "keyboard_protocol.h"
|
||
|
#include "keyboard_raw.h"
|
||
|
#include "keyboard_scan.h"
|
||
|
#include "keyboard_test.h"
|
||
|
#include "lid_switch.h"
|
||
|
#include "mkbp_event.h"
|
||
|
#include "power_button.h"
|
||
|
#include "system.h"
|
||
|
#include "tablet_mode.h"
|
||
|
#include "task.h"
|
||
|
#include "timer.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
/* Console output macros */
|
||
|
#define CPUTS(outstr) cputs(CC_KEYBOARD, outstr)
|
||
|
#define CPRINTS(format, args...) cprints(CC_KEYBOARD, format, ## args)
|
||
|
|
||
|
/*
|
||
|
* Common FIFO depth. This needs to be big enough not to overflow if a
|
||
|
* series of keys is pressed in rapid succession and the kernel is too busy
|
||
|
* to read them out right away.
|
||
|
*
|
||
|
* RAM usage is (depth * #cols); A 16-entry FIFO will consume 16x13=208 bytes,
|
||
|
* which is non-trivial but not horrible.
|
||
|
*/
|
||
|
|
||
|
#define FIFO_DEPTH 16
|
||
|
|
||
|
/* 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 BIT(BATTERY_KEY_ROW)
|
||
|
|
||
|
static uint32_t fifo_start; /* first entry */
|
||
|
static uint32_t fifo_end; /* last entry */
|
||
|
static uint32_t fifo_entries; /* number of existing entries */
|
||
|
static struct ec_response_get_next_event fifo[FIFO_DEPTH];
|
||
|
/*
|
||
|
* Mutex for critical sections of mkbp_fifo_add(), which is called
|
||
|
* from various tasks.
|
||
|
*/
|
||
|
static struct mutex fifo_add_mutex;
|
||
|
/*
|
||
|
* Mutex for critical sections of fifo_remove(), which is called from the
|
||
|
* hostcmd task and from keyboard_clear_buffer().
|
||
|
*/
|
||
|
static struct mutex fifo_remove_mutex;
|
||
|
|
||
|
/* Button and switch state. */
|
||
|
static uint32_t mkbp_button_state;
|
||
|
static uint32_t mkbp_switch_state;
|
||
|
#ifndef HAS_TASK_KEYSCAN
|
||
|
/* Keys simulated-pressed */
|
||
|
static uint8_t __bss_slow simulated_key[KEYBOARD_COLS_MAX];
|
||
|
uint8_t keyboard_cols = KEYBOARD_COLS_MAX;
|
||
|
#endif /* !defined(HAS_TASK_KEYSCAN) */
|
||
|
|
||
|
/* 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,
|
||
|
.fifo_max_depth = FIFO_DEPTH,
|
||
|
};
|
||
|
|
||
|
static int get_data_size(enum ec_mkbp_event e)
|
||
|
{
|
||
|
switch (e) {
|
||
|
case EC_MKBP_EVENT_KEY_MATRIX:
|
||
|
return keyboard_cols;
|
||
|
|
||
|
#ifdef CONFIG_HOST_EVENT64
|
||
|
case EC_MKBP_EVENT_HOST_EVENT64:
|
||
|
return sizeof(uint64_t);
|
||
|
#endif
|
||
|
|
||
|
case EC_MKBP_EVENT_HOST_EVENT:
|
||
|
case EC_MKBP_EVENT_BUTTON:
|
||
|
case EC_MKBP_EVENT_SWITCH:
|
||
|
case EC_MKBP_EVENT_SYSRQ:
|
||
|
return sizeof(uint32_t);
|
||
|
default:
|
||
|
/* For unknown types, say it's 0. */
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pop MKBP event data from FIFO
|
||
|
*
|
||
|
* @return EC_SUCCESS if entry popped, EC_ERROR_UNKNOWN if FIFO is empty
|
||
|
*/
|
||
|
static int fifo_remove(uint8_t *buffp)
|
||
|
{
|
||
|
int size;
|
||
|
|
||
|
mutex_lock(&fifo_remove_mutex);
|
||
|
if (!fifo_entries) {
|
||
|
/* no entry remaining in FIFO : return last known state */
|
||
|
int last = (fifo_start + FIFO_DEPTH - 1) % FIFO_DEPTH;
|
||
|
|
||
|
size = get_data_size(fifo[last].event_type);
|
||
|
|
||
|
memcpy(buffp, &fifo[last].data, size);
|
||
|
mutex_unlock(&fifo_remove_mutex);
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/* Return just the event data. */
|
||
|
if (buffp) {
|
||
|
size = get_data_size(fifo[fifo_start].event_type);
|
||
|
/* skip over event_type. */
|
||
|
memcpy(buffp, &fifo[fifo_start].data, size);
|
||
|
}
|
||
|
|
||
|
fifo_start = (fifo_start + 1) % FIFO_DEPTH;
|
||
|
atomic_sub(&fifo_entries, 1);
|
||
|
mutex_unlock(&fifo_remove_mutex);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Interface */
|
||
|
|
||
|
void keyboard_clear_buffer(void)
|
||
|
{
|
||
|
mkbp_clear_fifo();
|
||
|
}
|
||
|
|
||
|
void mkbp_clear_fifo(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
CPRINTS("clear MKBP fifo");
|
||
|
|
||
|
/*
|
||
|
* Order of these locks is important to prevent deadlock since
|
||
|
* mkbp_fifo_add() may call fifo_remove().
|
||
|
*/
|
||
|
mutex_lock(&fifo_add_mutex);
|
||
|
mutex_lock(&fifo_remove_mutex);
|
||
|
|
||
|
fifo_start = 0;
|
||
|
fifo_end = 0;
|
||
|
/* This assignment is safe since both mutexes are held. */
|
||
|
fifo_entries = 0;
|
||
|
for (i = 0; i < FIFO_DEPTH; i++)
|
||
|
memset(&fifo[i], 0, sizeof(struct ec_response_get_next_event));
|
||
|
|
||
|
mutex_unlock(&fifo_remove_mutex);
|
||
|
mutex_unlock(&fifo_add_mutex);
|
||
|
}
|
||
|
|
||
|
test_mockable int keyboard_fifo_add(const uint8_t *buffp)
|
||
|
{
|
||
|
return mkbp_fifo_add((uint8_t)EC_MKBP_EVENT_KEY_MATRIX, buffp);
|
||
|
}
|
||
|
|
||
|
test_mockable int mkbp_fifo_add(uint8_t event_type, const uint8_t *buffp)
|
||
|
{
|
||
|
uint8_t size;
|
||
|
|
||
|
/*
|
||
|
* If the data is a keyboard matrix and the keyboard protocol is not
|
||
|
* enabled, don't save the state to the FIFO or trigger an interrupt.
|
||
|
*/
|
||
|
if (!(config.flags & EC_MKBP_FLAGS_ENABLE) &&
|
||
|
(event_type == EC_MKBP_EVENT_KEY_MATRIX))
|
||
|
return EC_SUCCESS;
|
||
|
|
||
|
mutex_lock(&fifo_add_mutex);
|
||
|
if (fifo_entries >= config.fifo_max_depth) {
|
||
|
mutex_unlock(&fifo_add_mutex);
|
||
|
CPRINTS("MKBP common FIFO depth %d reached",
|
||
|
config.fifo_max_depth);
|
||
|
|
||
|
return EC_ERROR_OVERFLOW;
|
||
|
}
|
||
|
|
||
|
size = get_data_size(event_type);
|
||
|
fifo[fifo_end].event_type = event_type;
|
||
|
memcpy(&fifo[fifo_end].data, buffp, size);
|
||
|
fifo_end = (fifo_end + 1) % FIFO_DEPTH;
|
||
|
atomic_add(&fifo_entries, 1);
|
||
|
|
||
|
/*
|
||
|
* If our event didn't generate an interrupt then the host is still
|
||
|
* asleep. In this case, we don't want to queue our event, except if
|
||
|
* another event just woke the host (and wake is already in progress).
|
||
|
*/
|
||
|
if (!mkbp_send_event(event_type) && fifo_entries == 1)
|
||
|
fifo_remove(NULL);
|
||
|
|
||
|
mutex_unlock(&fifo_add_mutex);
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
void mkbp_update_switches(uint32_t sw, int state)
|
||
|
{
|
||
|
|
||
|
mkbp_switch_state &= ~BIT(sw);
|
||
|
mkbp_switch_state |= (!!state << sw);
|
||
|
|
||
|
mkbp_fifo_add(EC_MKBP_EVENT_SWITCH,
|
||
|
(const uint8_t *)&mkbp_switch_state);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_LID_SWITCH
|
||
|
/**
|
||
|
* Handle lid changing state.
|
||
|
*/
|
||
|
static void mkbp_lid_change(void)
|
||
|
{
|
||
|
mkbp_update_switches(EC_MKBP_LID_OPEN, lid_is_open());
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_LID_CHANGE, mkbp_lid_change, HOOK_PRIO_LAST);
|
||
|
DECLARE_HOOK(HOOK_INIT, mkbp_lid_change, HOOK_PRIO_INIT_LID+1);
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_TABLET_MODE_SWITCH
|
||
|
static void mkbp_tablet_mode_change(void)
|
||
|
{
|
||
|
mkbp_update_switches(EC_MKBP_TABLET_MODE, tablet_get_mode());
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_TABLET_MODE_CHANGE, mkbp_tablet_mode_change, HOOK_PRIO_LAST);
|
||
|
DECLARE_HOOK(HOOK_INIT, mkbp_tablet_mode_change, HOOK_PRIO_INIT_LID+1);
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_BASE_ATTACHED_SWITCH
|
||
|
static void mkbp_base_attached_change(void)
|
||
|
{
|
||
|
mkbp_update_switches(EC_MKBP_BASE_ATTACHED, base_get_state());
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_BASE_ATTACHED_CHANGE, mkbp_base_attached_change,
|
||
|
HOOK_PRIO_LAST);
|
||
|
DECLARE_HOOK(HOOK_INIT, mkbp_base_attached_change, HOOK_PRIO_INIT_LID+1);
|
||
|
#endif
|
||
|
|
||
|
void keyboard_update_button(enum keyboard_button_type button, int is_pressed)
|
||
|
{
|
||
|
switch (button) {
|
||
|
case KEYBOARD_BUTTON_POWER:
|
||
|
mkbp_button_state &= ~BIT(EC_MKBP_POWER_BUTTON);
|
||
|
mkbp_button_state |= (is_pressed << EC_MKBP_POWER_BUTTON);
|
||
|
break;
|
||
|
|
||
|
case KEYBOARD_BUTTON_VOLUME_UP:
|
||
|
mkbp_button_state &= ~BIT(EC_MKBP_VOL_UP);
|
||
|
mkbp_button_state |= (is_pressed << EC_MKBP_VOL_UP);
|
||
|
break;
|
||
|
|
||
|
case KEYBOARD_BUTTON_VOLUME_DOWN:
|
||
|
mkbp_button_state &= ~BIT(EC_MKBP_VOL_DOWN);
|
||
|
mkbp_button_state |= (is_pressed << EC_MKBP_VOL_DOWN);
|
||
|
break;
|
||
|
|
||
|
case KEYBOARD_BUTTON_RECOVERY:
|
||
|
mkbp_button_state &= ~BIT(EC_MKBP_RECOVERY);
|
||
|
mkbp_button_state |= (is_pressed << EC_MKBP_RECOVERY);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* ignored. */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CPRINTS("buttons: %x", mkbp_button_state);
|
||
|
|
||
|
/* Add the new state to the FIFO. */
|
||
|
mkbp_fifo_add(EC_MKBP_EVENT_BUTTON,
|
||
|
(const uint8_t *)&mkbp_button_state);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_EMULATED_SYSRQ
|
||
|
void host_send_sysrq(uint8_t key)
|
||
|
{
|
||
|
uint32_t value = key;
|
||
|
|
||
|
mkbp_fifo_add(EC_MKBP_EVENT_SYSRQ, (const uint8_t *)&value);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_POWER_BUTTON
|
||
|
/**
|
||
|
* Handle power button changing state.
|
||
|
*/
|
||
|
static void keyboard_power_button(void)
|
||
|
{
|
||
|
keyboard_update_button(KEYBOARD_BUTTON_POWER,
|
||
|
power_button_is_pressed());
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, keyboard_power_button,
|
||
|
HOOK_PRIO_DEFAULT);
|
||
|
#endif /* defined(CONFIG_POWER_BUTTON) */
|
||
|
|
||
|
static int get_next_event(uint8_t *out, enum ec_mkbp_event evt)
|
||
|
{
|
||
|
uint8_t t = fifo[fifo_start].event_type;
|
||
|
uint8_t size;
|
||
|
|
||
|
if (!fifo_entries)
|
||
|
return -1;
|
||
|
|
||
|
/*
|
||
|
* We need to peek at the next event to check that we were called with
|
||
|
* the correct event.
|
||
|
*/
|
||
|
if (t != (uint8_t)evt) {
|
||
|
/*
|
||
|
* We were called with the wrong event. The next element in the
|
||
|
* FIFO's event type doesn't match with what we were called
|
||
|
* with. Return an error that we're busy. The caller will need
|
||
|
* to call us with the correct event first.
|
||
|
*/
|
||
|
return -EC_ERROR_BUSY;
|
||
|
}
|
||
|
|
||
|
fifo_remove(out);
|
||
|
|
||
|
/* Keep sending events if FIFO is not empty */
|
||
|
if (fifo_entries)
|
||
|
mkbp_send_event(fifo[fifo_start].event_type);
|
||
|
|
||
|
/* Return the correct size of the data. */
|
||
|
size = get_data_size(t);
|
||
|
if (size)
|
||
|
return size;
|
||
|
else
|
||
|
return -EC_ERROR_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
static int keyboard_get_next_event(uint8_t *out)
|
||
|
{
|
||
|
return get_next_event(out, EC_MKBP_EVENT_KEY_MATRIX);
|
||
|
}
|
||
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_KEY_MATRIX, keyboard_get_next_event);
|
||
|
|
||
|
static int button_get_next_event(uint8_t *out)
|
||
|
{
|
||
|
return get_next_event(out, EC_MKBP_EVENT_BUTTON);
|
||
|
}
|
||
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_BUTTON, button_get_next_event);
|
||
|
|
||
|
static int switch_get_next_event(uint8_t *out)
|
||
|
{
|
||
|
return get_next_event(out, EC_MKBP_EVENT_SWITCH);
|
||
|
}
|
||
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_SWITCH, switch_get_next_event);
|
||
|
|
||
|
#ifdef CONFIG_EMULATED_SYSRQ
|
||
|
static int sysrq_get_next_event(uint8_t *out)
|
||
|
{
|
||
|
return get_next_event(out, EC_MKBP_EVENT_SYSRQ);
|
||
|
}
|
||
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_SYSRQ, sysrq_get_next_event);
|
||
|
#endif
|
||
|
|
||
|
void keyboard_send_battery_key(void)
|
||
|
{
|
||
|
uint8_t state[KEYBOARD_COLS_MAX];
|
||
|
|
||
|
/* Copy debounced state and add battery pseudo-key */
|
||
|
memcpy(state, keyboard_scan_get_state(), sizeof(state));
|
||
|
state[BATTERY_KEY_COL] ^= BATTERY_KEY_ROW_MASK;
|
||
|
|
||
|
/* Add to FIFO only if AP is on or else it will wake from suspend */
|
||
|
if (chipset_in_state(CHIPSET_STATE_ON))
|
||
|
keyboard_fifo_add(state);
|
||
|
}
|
||
|
|
||
|
void clear_typematic_key(void)
|
||
|
{ }
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Host commands */
|
||
|
static uint32_t get_supported_buttons(void)
|
||
|
{
|
||
|
uint32_t val = 0;
|
||
|
|
||
|
#ifdef CONFIG_VOLUME_BUTTONS
|
||
|
val |= BIT(EC_MKBP_VOL_UP) | BIT(EC_MKBP_VOL_DOWN);
|
||
|
#endif /* defined(CONFIG_VOLUME_BUTTONS) */
|
||
|
|
||
|
#ifdef CONFIG_DEDICATED_RECOVERY_BUTTON
|
||
|
val |= BIT(EC_MKBP_RECOVERY);
|
||
|
#endif /* defined(CONFIG_DEDICATED_RECOVERY_BUTTON) */
|
||
|
|
||
|
#ifdef CONFIG_POWER_BUTTON
|
||
|
val |= BIT(EC_MKBP_POWER_BUTTON);
|
||
|
#endif /* defined(CONFIG_POWER_BUTTON) */
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static uint32_t get_supported_switches(void)
|
||
|
{
|
||
|
uint32_t val = 0;
|
||
|
|
||
|
#ifdef CONFIG_LID_SWITCH
|
||
|
val |= BIT(EC_MKBP_LID_OPEN);
|
||
|
#endif
|
||
|
#ifdef CONFIG_TABLET_MODE_SWITCH
|
||
|
val |= BIT(EC_MKBP_TABLET_MODE);
|
||
|
#endif
|
||
|
#ifdef CONFIG_BASE_ATTACHED_SWITCH
|
||
|
val |= BIT(EC_MKBP_BASE_ATTACHED);
|
||
|
#endif
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static enum ec_status mkbp_get_info(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
const struct ec_params_mkbp_info *p = args->params;
|
||
|
|
||
|
if (args->params_size == 0 || p->info_type == EC_MKBP_INFO_KBD) {
|
||
|
struct ec_response_mkbp_info *r = args->response;
|
||
|
|
||
|
/* Version 0 just returns info about the keyboard. */
|
||
|
r->rows = KEYBOARD_ROWS;
|
||
|
r->cols = keyboard_cols;
|
||
|
/* This used to be "switches" which was previously 0. */
|
||
|
r->reserved = 0;
|
||
|
|
||
|
args->response_size = sizeof(struct ec_response_mkbp_info);
|
||
|
} else {
|
||
|
union ec_response_get_next_data *r = args->response;
|
||
|
|
||
|
/* Version 1 (other than EC_MKBP_INFO_KBD) */
|
||
|
switch (p->info_type) {
|
||
|
case EC_MKBP_INFO_SUPPORTED:
|
||
|
switch (p->event_type) {
|
||
|
case EC_MKBP_EVENT_BUTTON:
|
||
|
r->buttons = get_supported_buttons();
|
||
|
args->response_size = sizeof(r->buttons);
|
||
|
break;
|
||
|
|
||
|
case EC_MKBP_EVENT_SWITCH:
|
||
|
r->switches = get_supported_switches();
|
||
|
args->response_size = sizeof(r->switches);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* Don't care for now for other types. */
|
||
|
return EC_RES_INVALID_PARAM;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case EC_MKBP_INFO_CURRENT:
|
||
|
switch (p->event_type) {
|
||
|
#ifdef HAS_TASK_KEYSCAN
|
||
|
case EC_MKBP_EVENT_KEY_MATRIX:
|
||
|
memcpy(r->key_matrix, keyboard_scan_get_state(),
|
||
|
sizeof(r->key_matrix));
|
||
|
args->response_size = sizeof(r->key_matrix);
|
||
|
break;
|
||
|
#endif
|
||
|
case EC_MKBP_EVENT_HOST_EVENT:
|
||
|
r->host_event = (uint32_t)host_get_events();
|
||
|
args->response_size = sizeof(r->host_event);
|
||
|
break;
|
||
|
|
||
|
#ifdef CONFIG_HOST_EVENT64
|
||
|
case EC_MKBP_EVENT_HOST_EVENT64:
|
||
|
r->host_event64 = host_get_events();
|
||
|
args->response_size = sizeof(r->host_event64);
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case EC_MKBP_EVENT_BUTTON:
|
||
|
r->buttons = mkbp_button_state;
|
||
|
args->response_size = sizeof(r->buttons);
|
||
|
break;
|
||
|
|
||
|
case EC_MKBP_EVENT_SWITCH:
|
||
|
r->switches = mkbp_switch_state;
|
||
|
args->response_size = sizeof(r->switches);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* Doesn't make sense for other event types. */
|
||
|
return EC_RES_INVALID_PARAM;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* Unsupported query. */
|
||
|
return EC_RES_ERROR;
|
||
|
}
|
||
|
}
|
||
|
return EC_RES_SUCCESS;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_MKBP_INFO, mkbp_get_info,
|
||
|
EC_VER_MASK(0) | EC_VER_MASK(1));
|
||
|
|
||
|
#ifndef HAS_TASK_KEYSCAN
|
||
|
/* For boards without a keyscan task, try and simulate keyboard presses. */
|
||
|
static void simulate_key(int row, int col, int pressed)
|
||
|
{
|
||
|
if ((simulated_key[col] & BIT(row)) == ((pressed ? 1 : 0) << row))
|
||
|
return; /* No change */
|
||
|
|
||
|
simulated_key[col] &= ~BIT(row);
|
||
|
if (pressed)
|
||
|
simulated_key[col] |= BIT(row);
|
||
|
|
||
|
keyboard_fifo_add(simulated_key);
|
||
|
}
|
||
|
|
||
|
static int command_mkbp_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] & BIT(j))
|
||
|
ccprintf("\t%d %d\n", i, j);
|
||
|
}
|
||
|
|
||
|
} else if (argc == 3 || 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;
|
||
|
|
||
|
if (argc == 3) {
|
||
|
/* Simulate a press and release */
|
||
|
simulate_key(r, c, 1);
|
||
|
simulate_key(r, c, 0);
|
||
|
} else {
|
||
|
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_mkbp_keyboard_press,
|
||
|
"[col row [0 | 1]]",
|
||
|
"Simulate keypress");
|
||
|
#endif /* !defined(HAS_TASK_KEYSCAN) */
|
||
|
|
||
|
#ifdef HAS_TASK_KEYSCAN
|
||
|
static void set_keyscan_config(const struct ec_mkbp_config *src,
|
||
|
struct ec_mkbp_protocol_config *dst,
|
||
|
uint32_t valid_mask, uint8_t new_flags)
|
||
|
{
|
||
|
struct keyboard_scan_config *ksc = keyboard_scan_get_config();
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_SCAN_PERIOD)
|
||
|
ksc->scan_period_us = src->scan_period_us;
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_POLL_TIMEOUT)
|
||
|
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.
|
||
|
*/
|
||
|
ksc->min_post_scan_delay_us =
|
||
|
MAX(src->min_post_scan_delay_us, 200);
|
||
|
}
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_OUTPUT_SETTLE)
|
||
|
ksc->output_settle_us = src->output_settle_us;
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_DEBOUNCE_DOWN)
|
||
|
ksc->debounce_down_us = src->debounce_down_us;
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_DEBOUNCE_UP)
|
||
|
ksc->debounce_up_us = src->debounce_up_us;
|
||
|
|
||
|
/*
|
||
|
* 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 void get_keyscan_config(struct ec_mkbp_config *dst)
|
||
|
{
|
||
|
const struct keyboard_scan_config *ksc = keyboard_scan_get_config();
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copy keyscan configuration from one place to another according to flags
|
||
|
*
|
||
|
* This is like a structure copy, except that only selected fields are
|
||
|
* copied.
|
||
|
*
|
||
|
* @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_protocol_config *dst,
|
||
|
uint32_t valid_mask, uint8_t valid_flags)
|
||
|
{
|
||
|
uint8_t new_flags;
|
||
|
|
||
|
if (valid_mask & EC_MKBP_VALID_FIFO_MAX_DEPTH) {
|
||
|
/* Sanity check for fifo depth */
|
||
|
dst->fifo_max_depth = MIN(src->fifo_max_depth,
|
||
|
FIFO_DEPTH);
|
||
|
}
|
||
|
|
||
|
new_flags = dst->flags & ~valid_flags;
|
||
|
new_flags |= src->flags & valid_flags;
|
||
|
|
||
|
set_keyscan_config(src, dst, valid_mask, new_flags);
|
||
|
dst->flags = new_flags;
|
||
|
}
|
||
|
|
||
|
static enum ec_status
|
||
|
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;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_MKBP_SET_CONFIG,
|
||
|
host_command_mkbp_set_config,
|
||
|
EC_VER_MASK(0));
|
||
|
|
||
|
static enum ec_status
|
||
|
host_command_mkbp_get_config(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
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;
|
||
|
|
||
|
get_keyscan_config(dst);
|
||
|
|
||
|
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));
|
||
|
#endif /* HAS_TASK_KEYSCAN */
|