coreboot-libre-fam15h-rdimm/3rdparty/chromeec/common/mkbp_event.c

507 lines
15 KiB
C

/* Copyright 2014 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.
*
* Event handling in MKBP keyboard protocol
*/
#include "atomic.h"
#include "chipset.h"
#include "gpio.h"
#include "host_command.h"
#include "host_command_heci.h"
#include "hwtimer.h"
#include "timer.h"
#include "link_defs.h"
#include "mkbp_event.h"
#include "power.h"
#include "util.h"
#define CPUTS(outstr) cputs(CC_COMMAND, outstr)
#define CPRINTS(format, args...) cprints(CC_COMMAND, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_COMMAND, format, ## args)
/*
* Tracks the current state of the MKBP interrupt send from the EC to the AP.
*
* The inactive state is only valid when there are no events to set to the AP.
* If the AP is asleep, then some events are not worth waking the AP up, so the
* interrupt could remain in an inactive in that case.
*
* The transition state (INTERRUPT_INACTIVE_TO_ACTIVE) is used to track the
* sometimes lock transition for a "rising edge" for platforms that send the
* rising edge interrupt through a host communication layer
*
* The active state represents that a rising edge interrupt has already been
* sent to the AP, and the EC is waiting for the AP to call get next event
* host command to consume all of the events (at which point the state will
* move to inactive).
*
* The transition from ACTIVE -> INACTIVE is considerer to be simple meaning
* the operation can be performed within a blocking mutex (e.g. no-op or setting
* a gpio).
*/
enum interrupt_state {
INTERRUPT_INACTIVE,
INTERRUPT_INACTIVE_TO_ACTIVE, /* Transitioning */
INTERRUPT_ACTIVE,
};
struct mkbp_state {
struct mutex lock;
uint32_t events;
enum interrupt_state interrupt;
/*
* Tracks unique transitions to INTERRUPT_INACTIVE_TO_ACTIVE allowing
* only the most recent transition to finish the transition to a final
* state -- either active or inactive depending on the result of the
* operation.
*/
uint8_t interrupt_id;
/*
* Tracks the number of consecutive failed attempts for the AP to poll
* get_next_events in order to limit the retry logic.
*/
uint8_t failed_attempts;
};
static struct mkbp_state state;
uint32_t mkbp_last_event_time;
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
static uint32_t mkbp_event_wake_mask = CONFIG_MKBP_EVENT_WAKEUP_MASK;
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
static uint32_t mkbp_host_event_wake_mask = CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK;
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#if defined(CONFIG_MKBP_USE_GPIO) || \
defined(CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT)
static int mkbp_set_host_active_via_gpio(int active, uint32_t *timestamp)
{
/*
* If we want to take a timestamp, then disable interrupts temporarily
* to ensure that the timestamp is as close as possible to the setting
* of the GPIO pin in hardware (i.e. we aren't interrupted between
* taking the timestamp and setting the gpio)
*/
if (timestamp) {
interrupt_disable();
*timestamp = __hw_clock_source_read();
}
gpio_set_level(GPIO_EC_INT_L, !active);
if (timestamp)
interrupt_enable();
#ifdef CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT
/*
* In case EC_INT_L is not a wake pin, make sure that we also attempt to
* wake the AP via a host event. Only use this second notification
* interface in suspend since MKBP events are a part of the
* HOST_EVENT_ALWAYS_REPORT_DEFAULT_MASK. This can cause an MKBP host
* event to be set in S0, but not triggering an SCI since the event is
* not in the SCI mask. This would also cause the board to prematurely
* wake up when suspending due to the lingering event.
*/
if (active && chipset_in_state(CHIPSET_STATE_ANY_SUSPEND))
host_set_single_event(EC_HOST_EVENT_MKBP);
#endif /* CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT */
return EC_SUCCESS;
}
#endif /* CONFIG_MKBP_USE_GPIO(_AND_HOST_EVENT)? */
#ifdef CONFIG_MKBP_USE_HOST_EVENT
static int mkbp_set_host_active_via_event(int active, uint32_t *timestamp)
{
/* This should be moved into host_set_single_event for more accuracy */
if (timestamp)
*timestamp = __hw_clock_source_read();
if (active)
host_set_single_event(EC_HOST_EVENT_MKBP);
return EC_SUCCESS;
}
#endif
#ifdef CONFIG_MKBP_USE_HECI
static int mkbp_set_host_active_via_heci(int active, uint32_t *timestamp)
{
if (active)
return heci_send_mkbp_event(timestamp);
return EC_SUCCESS;
}
#endif
/*
* This communicates to the AP whether an MKBP event is currently available
* for processing.
*
* NOTE: When active is 0 this function CANNOT de-schedule. It must be very
* simple like toggling a GPIO or no-op
*
* @param active 1 if there is an event, 0 otherwise
* @param timestamp, if non-null this variable will be written as close to the
* hardware interrupt from EC->AP as possible.
*/
static int mkbp_set_host_active(int active, uint32_t *timestamp)
{
#if defined(CONFIG_MKBP_USE_CUSTOM)
return mkbp_set_host_active_via_custom(active, timestamp);
#elif defined(CONFIG_MKBP_USE_HOST_EVENT)
return mkbp_set_host_active_via_event(active, timestamp);
#elif defined(CONFIG_MKBP_USE_GPIO) ||\
defined(CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT)
return mkbp_set_host_active_via_gpio(active, timestamp);
#elif defined(CONFIG_MKBP_USE_HECI)
return mkbp_set_host_active_via_heci(active, timestamp);
#endif
}
#if defined(CONFIG_MKBP_EVENT_WAKEUP_MASK) || \
defined(CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK)
/**
* Check if the host is sleeping. Check our power state in addition to the
* self-reported sleep state of host (CONFIG_POWER_TRACK_HOST_SLEEP_STATE).
*/
static inline int host_is_sleeping(void)
{
int is_sleeping = !chipset_in_state(CHIPSET_STATE_ON);
#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE
enum host_sleep_event sleep_state = power_get_host_sleep_state();
is_sleeping |=
(sleep_state == HOST_SLEEP_EVENT_S0IX_SUSPEND ||
sleep_state == HOST_SLEEP_EVENT_S3_SUSPEND ||
sleep_state == HOST_SLEEP_EVENT_S3_WAKEABLE_SUSPEND);
#endif
return is_sleeping;
}
#endif /* CONFIG_MKBP_(HOST_EVENT_)?WAKEUP_MASK */
/*
* This is the deferred function that ensures that we attempt to set the MKBP
* interrupt again if there was a failure in the system (EC or AP) and the AP
* never called get_next_event.
*/
static void force_mkbp_if_events(void);
DECLARE_DEFERRED(force_mkbp_if_events);
static void activate_mkbp_with_events(uint32_t events_to_add)
{
int interrupt_id = -1;
int skip_interrupt = 0;
int rv, schedule_deferred = 0;
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
/* Check to see if this host event should wake the system. */
skip_interrupt = host_is_sleeping() &&
!(host_get_events() &
mkbp_host_event_wake_mask);
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
/* Check to see if this MKBP event should wake the system. */
if (!skip_interrupt)
skip_interrupt = host_is_sleeping() &&
!(events_to_add & mkbp_event_wake_mask);
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
mutex_lock(&state.lock);
state.events |= events_to_add;
/* To skip the interrupt, we cannot have the EC_MKBP_EVENT_KEY_MATRIX */
skip_interrupt = skip_interrupt &&
!(state.events & BIT(EC_MKBP_EVENT_KEY_MATRIX));
if (state.events && state.interrupt == INTERRUPT_INACTIVE &&
!skip_interrupt) {
state.interrupt = INTERRUPT_INACTIVE_TO_ACTIVE;
interrupt_id = ++state.interrupt_id;
}
mutex_unlock(&state.lock);
/* If we don't need to send an interrupt we are done */
if (interrupt_id < 0)
return;
/* Send a rising edge MKBP interrupt */
rv = mkbp_set_host_active(1, &mkbp_last_event_time);
/*
* If this was the last interrupt to the AP, update state;
* otherwise the latest interrupt should update state.
*/
mutex_lock(&state.lock);
if (state.interrupt == INTERRUPT_INACTIVE_TO_ACTIVE &&
interrupt_id == state.interrupt_id) {
schedule_deferred = 1;
state.interrupt = rv == EC_SUCCESS ? INTERRUPT_ACTIVE
: INTERRUPT_INACTIVE;
}
mutex_unlock(&state.lock);
if (schedule_deferred) {
hook_call_deferred(&force_mkbp_if_events_data, SECOND);
if (rv != EC_SUCCESS)
CPRINTS("Could not activate MKBP (%d). Deferring", rv);
}
}
/*
* This is the deferred function that ensures that we attempt to set the MKBP
* interrupt again if there was a failure in the system (EC or AP) and the AP
* never called get_next_event.
*/
static void force_mkbp_if_events(void)
{
int toggled = 0;
mutex_lock(&state.lock);
if (state.interrupt == INTERRUPT_ACTIVE) {
if (++state.failed_attempts < 3) {
state.interrupt = INTERRUPT_INACTIVE;
toggled = 1;
}
}
mutex_unlock(&state.lock);
if (toggled)
CPRINTS("MKBP not cleared within threshold, toggling.");
activate_mkbp_with_events(0);
}
int mkbp_send_event(uint8_t event_type)
{
activate_mkbp_with_events(BIT(event_type));
return 1;
}
static int set_inactive_if_no_events(void)
{
int interrupt_cleared;
mutex_lock(&state.lock);
interrupt_cleared = !state.events;
if (interrupt_cleared) {
state.interrupt = INTERRUPT_INACTIVE;
state.failed_attempts = 0;
/* Only simple tasks (i.e. gpio set or no-op) allowed here */
mkbp_set_host_active(0, NULL);
}
mutex_unlock(&state.lock);
/* Cancel our safety net since the events were cleared. */
if (interrupt_cleared)
hook_call_deferred(&force_mkbp_if_events_data, -1);
return interrupt_cleared;
}
/* This can only be called when the state.lock mutex is held */
static int take_event_if_set(uint8_t event_type)
{
int taken;
taken = state.events & BIT(event_type);
state.events &= ~BIT(event_type);
return taken;
}
static enum ec_status mkbp_get_next_event(struct host_cmd_handler_args *args)
{
static int last;
int i, evt;
uint8_t *resp = args->response;
const struct mkbp_event_source *src;
int data_size = -EC_ERROR_BUSY;
do {
/*
* Find the next event to service. We do this in a round-robin
* way to make sure no event gets starved.
*/
mutex_lock(&state.lock);
for (i = 0; i < EC_MKBP_EVENT_COUNT; ++i)
if (take_event_if_set((last + i) % EC_MKBP_EVENT_COUNT))
break;
mutex_unlock(&state.lock);
if (i == EC_MKBP_EVENT_COUNT) {
if (set_inactive_if_no_events())
return EC_RES_UNAVAILABLE;
/* An event was set just now, restart loop. */
continue;
}
evt = (i + last) % EC_MKBP_EVENT_COUNT;
last = evt + 1;
for (src = __mkbp_evt_srcs; src < __mkbp_evt_srcs_end; ++src)
if (src->event_type == evt)
break;
if (src == __mkbp_evt_srcs_end)
return EC_RES_ERROR;
resp[0] = evt; /* Event type */
/*
* get_data() can return -EC_ERROR_BUSY which indicates that the
* next element in the keyboard FIFO does not match what we were
* called with. For example, get_data is expecting a keyboard
* matrix, however the next element in the FIFO is a button
* event instead. Therefore, we have to service that button
* event first.
*/
data_size = src->get_data(resp + 1);
if (data_size == -EC_ERROR_BUSY) {
mutex_lock(&state.lock);
state.events |= BIT(evt);
mutex_unlock(&state.lock);
}
} while (data_size == -EC_ERROR_BUSY);
/* If there are no more events and we support the "more" flag, set it */
if (!set_inactive_if_no_events() && args->version >= 2)
resp[0] |= EC_MKBP_HAS_MORE_EVENTS;
if (data_size < 0)
return EC_RES_ERROR;
args->response_size = 1 + data_size;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_GET_NEXT_EVENT,
mkbp_get_next_event,
EC_VER_MASK(0) | EC_VER_MASK(1) | EC_VER_MASK(2));
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
#ifdef CONFIG_MKBP_USE_HOST_EVENT
static enum ec_status
mkbp_get_host_event_wake_mask(struct host_cmd_handler_args *args)
{
struct ec_response_host_event_mask *r = args->response;
r->mask = CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_HOST_EVENT_GET_WAKE_MASK,
mkbp_get_host_event_wake_mask,
EC_VER_MASK(0));
#endif /* CONFIG_MKBP_USE_HOST_EVENT */
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#if defined(CONFIG_MKBP_EVENT_WAKEUP_MASK) || \
defined(CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK)
static enum ec_status hc_mkbp_wake_mask(struct host_cmd_handler_args *args)
{
struct ec_response_mkbp_event_wake_mask *r = args->response;
const struct ec_params_mkbp_event_wake_mask *p = args->params;
enum ec_mkbp_event_mask_action action = p->action;
switch (action) {
case GET_WAKE_MASK:
switch (p->mask_type) {
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
case EC_MKBP_HOST_EVENT_WAKE_MASK:
r->wake_mask = mkbp_host_event_wake_mask;
break;
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
case EC_MKBP_EVENT_WAKE_MASK:
r->wake_mask = mkbp_event_wake_mask;
break;
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
default:
/* Unknown mask, or mask is not in use. */
return EC_RES_INVALID_PARAM;
}
args->response_size = sizeof(*r);
break;
case SET_WAKE_MASK:
args->response_size = 0;
switch (p->mask_type) {
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
case EC_MKBP_HOST_EVENT_WAKE_MASK:
CPRINTF("MKBP hostevent mask updated to: 0x%08x "
"(was 0x%08x)\n",
p->new_wake_mask,
mkbp_host_event_wake_mask);
mkbp_host_event_wake_mask = p->new_wake_mask;
break;
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
case EC_MKBP_EVENT_WAKE_MASK:
mkbp_event_wake_mask = p->new_wake_mask;
CPRINTF("MKBP event mask updated to: 0x%08x\n",
mkbp_event_wake_mask);
break;
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
default:
/* Unknown mask, or mask is not in use. */
return EC_RES_INVALID_PARAM;
}
break;
default:
return EC_RES_INVALID_PARAM;
}
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MKBP_WAKE_MASK,
hc_mkbp_wake_mask,
EC_VER_MASK(0));
static int command_mkbp_wake_mask(int argc, char **argv)
{
if (argc == 3) {
char *e;
uint32_t new_mask = strtoul(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
if (strncmp(argv[1], "event", 5) == 0)
mkbp_event_wake_mask = new_mask;
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
if (strncmp(argv[1], "hostevent", 9) == 0)
mkbp_host_event_wake_mask = new_mask;
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
} else if (argc != 1) {
return EC_ERROR_PARAM_COUNT;
}
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
ccprintf("MKBP host event wake mask: 0x%08x\n",
mkbp_host_event_wake_mask);
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
ccprintf("MKBP event wake mask: 0x%08x\n", mkbp_event_wake_mask);
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(mkbpwakemask, command_mkbp_wake_mask,
"[event | hostevent] [new_mask]",
"Show or set MKBP event/hostevent wake mask");
#endif /* CONFIG_MKBP_(HOST)?EVENT_WAKEUP_MASK */