/* 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.
	 * Use == instead of & here since we don't want to apply the host event
	 * skipping logic if we are adding a host event and something else.
	 */
	if (events_to_add == BIT(EC_MKBP_EVENT_HOST_EVENT) ||
	    events_to_add == BIT(EC_MKBP_EVENT_HOST_EVENT64))
		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 */
