/* 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.
 */

/* Common functionality across all chipsets */

#include "battery.h"
#include "charge_state.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "display_7seg.h"
#include "espi.h"
#include "extpower.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "lpc.h"
#include "power.h"
#include "power/intel_x86.h"
#include "power/qcom.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)

/*
 * Default timeout in us; if we've been waiting this long for an input
 * transition, just jump to the next state.
 */
#define DEFAULT_TIMEOUT SECOND

/* Timeout for dropping back from S5 to G3 in seconds */
#ifdef CONFIG_CMD_S5_TIMEOUT
static int s5_inactivity_timeout = 10;
#else
static const int s5_inactivity_timeout = 10;
#endif

static const char * const state_names[] = {
	"G3",
	"S5",
	"S4",
	"S3",
	"S0",
#ifdef CONFIG_POWER_S0IX
	"S0ix",
#endif
	"G3->S5",
	"S5->S3",
	"S3->S0",
	"S0->S3",
	"S3->S5",
	"S5->G3",
	"S3->S4",
	"S4->S3",
	"S4->S5",
	"S5->S4",
#ifdef CONFIG_POWER_S0IX
	"S0ix->S0",
	"S0->S0ix",
#endif
};

static uint32_t in_signals;   /* Current input signal states (IN_PGOOD_*) */
static uint32_t in_want;      /* Input signal state we're waiting for */
static uint32_t in_debug;     /* Signal values which print debug output */

static enum power_state state = POWER_G3;  /* Current state */
static int want_g3_exit;      /* Should we exit the G3 state? */
static uint64_t last_shutdown_time; /* When did we enter G3? */

#ifdef CONFIG_HIBERNATE
/* Delay before hibernating, in seconds */
static uint32_t hibernate_delay = CONFIG_HIBERNATE_DELAY_SEC;
#endif

#ifdef CONFIG_POWER_SHUTDOWN_PAUSE_IN_S5
/* Pause in S5 on shutdown? */
static int pause_in_s5;
#endif

static bool want_reboot_ap_at_g3;/* Want to reboot AP from G3? */
/* Want to reboot AP from G3 with delay? */
static uint64_t reboot_ap_at_g3_delay;

static enum ec_status
host_command_reboot_ap_on_g3(struct host_cmd_handler_args *args)
{
	const struct ec_params_reboot_ap_on_g3_v1 *cmd = args->params;

	/* Store request for processing at g3 */
	want_reboot_ap_at_g3 = true;

	switch (args->version) {
	case 0:
		break;
	case 1:
		/* Store user specified delay to wait in G3 state */
		reboot_ap_at_g3_delay = cmd->reboot_ap_at_g3_delay;
		break;
	default:
		return EC_RES_INVALID_PARAM;
	}

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_REBOOT_AP_ON_G3,
		     host_command_reboot_ap_on_g3,
		     EC_VER_MASK(0) | EC_VER_MASK(1));

__overridable int power_signal_get_level(enum gpio_signal signal)
{
	if (IS_ENABLED(CONFIG_HOST_ESPI_VW_POWER_SIGNAL)) {
		/* Check signal is from GPIOs or VWs */
		if (espi_signal_is_vw(signal))
			return espi_vw_get_wire((enum espi_vw_signal)signal);
	}
	return gpio_get_level(signal);
}

int power_signal_disable_interrupt(enum gpio_signal signal)
{
	if (IS_ENABLED(CONFIG_HOST_ESPI_VW_POWER_SIGNAL)) {
		/* Check signal is from GPIOs or VWs */
		if (espi_signal_is_vw(signal))
			return espi_vw_disable_wire_int(
				(enum espi_vw_signal)signal);
	}
	return gpio_disable_interrupt(signal);
}

int power_signal_enable_interrupt(enum gpio_signal signal)
{
	if (IS_ENABLED(CONFIG_HOST_ESPI_VW_POWER_SIGNAL)) {
		/* Check signal is from GPIOs or VWs */
		if (espi_signal_is_vw(signal))
			return espi_vw_enable_wire_int(
				(enum espi_vw_signal)signal);
	}
	return gpio_enable_interrupt(signal);
}

int power_signal_is_asserted(const struct power_signal_info *s)
{
	return power_signal_get_level(s->gpio) ==
		!!(s->flags & POWER_SIGNAL_ACTIVE_STATE);
}

#ifdef CONFIG_BRINGUP
static const char *power_signal_get_name(enum gpio_signal signal)
{
	if (IS_ENABLED(CONFIG_HOST_INTERFACE_ESPI)) {
		/* Check signal is from GPIOs or VWs */
		if (espi_signal_is_vw(signal))
			return espi_vw_get_wire_name(
				(enum espi_vw_signal)signal);
	}
	return gpio_get_name(signal);
}
#endif

/**
 * Update input signals mask
 */
static void power_update_signals(void)
{
	uint32_t inew = 0;
	const struct power_signal_info *s = power_signal_list;
	int i;

	for (i = 0; i < POWER_SIGNAL_COUNT; i++, s++) {
		if (power_signal_is_asserted(s))
			inew |= 1 << i;
	}

	if ((in_signals & in_debug) != (inew & in_debug))
		CPRINTS("power in 0x%04x", inew);

	in_signals = inew;
}

uint32_t power_get_signals(void)
{
	return in_signals;
}

int power_has_signals(uint32_t want)
{
	if ((in_signals & want) == want)
		return 1;

	CPRINTS("power lost input; wanted 0x%04x, got 0x%04x",
		want, in_signals & want);

	return 0;
}

int power_wait_signals(uint32_t want)
{
	int ret = power_wait_signals_timeout(want, DEFAULT_TIMEOUT);

	if (ret == EC_ERROR_TIMEOUT)
		CPRINTS("power timeout on input; wanted 0x%04x, got 0x%04x",
			want, in_signals & want);
	return ret;
}

int power_wait_signals_timeout(uint32_t want, int timeout)
{
	return power_wait_mask_signals_timeout(want, want, timeout);
}

int power_wait_mask_signals_timeout(uint32_t want, uint32_t mask, int timeout)
{
	in_want = want;
	if (!mask)
		return EC_SUCCESS;

	while ((in_signals & mask) != in_want) {
		if (task_wait_event(timeout) == TASK_EVENT_TIMER) {
			power_update_signals();
			return EC_ERROR_TIMEOUT;
		}
		/*
		 * TODO(crosbug.com/p/23772): should really shrink the
		 * remaining timeout if we woke up but didn't have all the
		 * signals we wanted.  Also need to handle aborts if we're no
		 * longer in the same state we were when we started waiting.
		 */
	}
	return EC_SUCCESS;
}

void power_set_state(enum power_state new_state)
{
	/* Record the time we go into G3 */
	if (new_state == POWER_G3)
		last_shutdown_time = get_time().val;

	/* Print out the RTC value to help correlate EC and kernel logs. */
	print_system_rtc(CC_CHIPSET);

	state = new_state;

	/*
	 * Reset want_g3_exit flag here to prevent the situation that if the
	 * error handler in POWER_S5S4 decides to force shutdown the system and
	 * the flag is set, the system will go to G3 and then immediately exit
	 * G3 again.
	 */
	if ((state == POWER_S5S4) || (state == POWER_S5S3))
		want_g3_exit = 0;
}

enum power_state power_get_state(void)
{
	return state;
}

#ifdef CONFIG_HOSTCMD_X86

/* If host doesn't program s0ix lazy wake mask, use default s0ix mask */
#define DEFAULT_WAKE_MASK_S0IX  (EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_OPEN) | \
				EC_HOST_EVENT_MASK(EC_HOST_EVENT_MODE_CHANGE))

 /*
  * Set the wake mask according to the current power state:
  * 1. On transition to S0, wake mask is reset.
  * 2. In non-S0 states, active mask set by host gets a higher preference.
  * 3. If host has not set any active mask, then check if a lazy mask exists
  *    for the current power state.
  * 4. If state is S0ix and no lazy or active wake mask is set, then use default
  *    S0ix mask to be compatible with older BIOS versions.
  */

void power_update_wake_mask(void)
{
	host_event_t wake_mask;
	enum power_state state;

	state = power_get_state();

	if (state == POWER_S0)
		wake_mask = 0;
	else if (lpc_is_active_wm_set_by_host())
		return;
	else if (get_lazy_wake_mask(state, &wake_mask))
		return;
#ifdef CONFIG_POWER_S0IX
	if ((state == POWER_S0ix) && (wake_mask == 0))
		wake_mask = DEFAULT_WAKE_MASK_S0IX;
#endif

	lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, wake_mask);
}
 /*
  * Set wake mask after power state has stabilized, 5ms after power state
  * change. The reason for making this a deferred call is to avoid race
  * conditions occurring from S0ix periodic wakes on the SoC.
  */

static void power_update_wake_mask_deferred(void);
DECLARE_DEFERRED(power_update_wake_mask_deferred);

static void power_update_wake_mask_deferred(void)
{
	hook_call_deferred(&power_update_wake_mask_deferred_data, -1);
	power_update_wake_mask();
}

static void power_set_active_wake_mask(void)
{
	/*
	 * Allow state machine to stabilize and update wake mask after 5msec. It
	 * was observed that on platforms where host wakes up periodically from
	 * S0ix for hardware book-keeping activities, there is a small window
	 * where host is not really up and running software, but still SLP_S0#
	 * is de-asserted and hence setting wake mask right away can cause user
	 * wake events to be missed.
	 *
	 * Time for deferred callback was chosen to be 5msec based on the fact
	 * that it takes ~2msec for the periodic wake cycle to complete on the
	 * host for KBL.
	 */
	hook_call_deferred(&power_update_wake_mask_deferred_data,
			   5 * MSEC);
}

#else
static void power_set_active_wake_mask(void) { }
#endif

#ifdef CONFIG_HIBERNATE
#ifdef CONFIG_BATTERY
/*
 * Smart discharge system
 *
 * EC controls how the system discharges differently depending on the remaining
 * capacity and the expected hours to zero.
 *
 * 0          X1                X2                                   full
 * |----------|-------------------|------------------------------------|
 *    cutoff        stay-up                       safe
 *
 * EC cuts off the battery at X1 mAh and hibernates the system at X2 mAh. X1 and
 * X2 are derived from the cutoff and hibernation discharge rate, respectively.
 *
 * TODO: Learn discharge rates dynamically.
 *
 * TODO: Save sdzone in non-volatile memory and restore it when waking up from
 * cutoff or hibernation.
 */
static struct smart_discharge_zone sdzone;

static enum ec_status hc_smart_discharge(struct host_cmd_handler_args *args)
{
	static uint16_t hours_to_zero;
	static struct discharge_rate drate;
	const struct ec_params_smart_discharge *p = args->params;
	struct ec_response_smart_discharge *r = args->response;

	if (p->flags & EC_SMART_DISCHARGE_FLAGS_SET) {
		int cap;

		if (battery_full_charge_capacity(&cap))
			return EC_RES_UNAVAILABLE;

		if (p->drate.hibern < p->drate.cutoff)
			/* Hibernation discharge rate should be always higher */
			return EC_RES_INVALID_PARAM;
		else if (p->drate.cutoff > 0 && p->drate.hibern > 0)
			drate = p->drate;
		else if (p->drate.cutoff == 0 && p->drate.hibern == 0)
			;  /* no-op. use the current drate. */
		else
			return EC_RES_INVALID_PARAM;

		/* Commit */
		hours_to_zero = p->hours_to_zero;
		sdzone.stayup = MIN(hours_to_zero * drate.hibern / 1000, cap);
		sdzone.cutoff = MIN(hours_to_zero * drate.cutoff / 1000,
				    sdzone.stayup);
	}

	/* Return the effective values. */
	r->hours_to_zero = hours_to_zero;
	r->dzone = sdzone;
	r->drate = drate;
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_SMART_DISCHARGE,
		     hc_smart_discharge,
		     EC_VER_MASK(0));

__overridable enum critical_shutdown board_system_is_idle(
		uint64_t last_shutdown_time, uint64_t *target, uint64_t now)
{
	int remain;

	if (now < *target)
		return CRITICAL_SHUTDOWN_IGNORE;

	if (battery_remaining_capacity(&remain)) {
		CPRINTS("SDC Failed to get remaining capacity");
		return CRITICAL_SHUTDOWN_HIBERNATE;
	}

	if (remain < sdzone.cutoff) {
		CPRINTS("SDC Cutoff");
		return CRITICAL_SHUTDOWN_CUTOFF;
	} else if (remain < sdzone.stayup) {
		CPRINTS("SDC Stay-up");
		return CRITICAL_SHUTDOWN_IGNORE;
	}

	CPRINTS("SDC Safe");
	return CRITICAL_SHUTDOWN_HIBERNATE;
}
#else
/* Default implementation for battery-less systems */
__overridable enum critical_shutdown board_system_is_idle(
		uint64_t last_shutdown_time, uint64_t *target, uint64_t now)
{
	return now > *target ?
			CRITICAL_SHUTDOWN_HIBERNATE : CRITICAL_SHUTDOWN_IGNORE;
}
#endif	/* CONFIG_BATTERY */
#endif	/* CONFIG_HIBERNATE */

/**
 * Common handler for steady states
 *
 * @param state		Current power state
 * @return Updated power state
 */
static enum power_state power_common_state(enum power_state state)
{
	switch (state) {
	case POWER_G3:
		if (want_g3_exit || want_reboot_ap_at_g3) {
			uint64_t i;

			want_g3_exit = 0;
			want_reboot_ap_at_g3 = false;
			reboot_ap_at_g3_delay = reboot_ap_at_g3_delay * MSEC;
			/*
			 * G3->S0 transition should happen only after the
			 * user specified delay. Hence, wait until the
			 * user specified delay times out.
			 */
			for (i = 0; i < reboot_ap_at_g3_delay; i += 100)
				msleep(100);
			reboot_ap_at_g3_delay = 0;

			return POWER_G3S5;
		}

		in_want = 0;
#ifdef CONFIG_HIBERNATE
		{
			uint64_t target, now, wait;
			if (extpower_is_present()) {
				task_wait_event(-1);
				break;
			}

			now = get_time().val;
			target = last_shutdown_time +
					(uint64_t)hibernate_delay * SECOND;
			switch (board_system_is_idle(last_shutdown_time,
						     &target, now)) {
			case CRITICAL_SHUTDOWN_HIBERNATE:
				CPRINTS("Hibernate due to G3 idle");
				system_hibernate(0, 0);
				break;
#ifdef CONFIG_BATTERY_CUT_OFF
			case CRITICAL_SHUTDOWN_CUTOFF:
				CPRINTS("Cutoff due to G3 idle");
				/* Ensure logs are flushed. */
				cflush();
				board_cut_off_battery();
				break;
#endif
			case CRITICAL_SHUTDOWN_IGNORE:
			default:
				break;
			}

			wait = MIN(target - now, TASK_MAX_WAIT_US);
			task_wait_event(wait);
		}
#else /* !CONFIG_HIBERNATE */
		task_wait_event(-1);
#endif
		break;

	case POWER_S5:
		/*
		 * If the power button is pressed before S5 inactivity timer
		 * expires, the timer will be cancelled and the task of the
		 * power state machine will be back here again. Since we are
		 * here, which means the system has been waiting for CPU
		 * starting up, we don't need want_g3_exit flag to be set
		 * anymore. Therefore, we can reset the flag here to prevent
		 * the situation that the flag is still set after S5 inactivity
		 * timer expires, which can cause the system to exit G3 again.
		 */
		want_g3_exit = 0;

		power_wait_signals(0);

		/* Wait for inactivity timeout, if desired */
		if (s5_inactivity_timeout == 0) {
			return POWER_S5G3;
		} else if (s5_inactivity_timeout < 0) {
			task_wait_event(-1);
		} else if (task_wait_event(s5_inactivity_timeout * SECOND) ==
			   TASK_EVENT_TIMER) {
			/* Prepare to drop to G3; wake not requested yet */
			return POWER_S5G3;
		}
		break;

	case POWER_S4:
		/* fallthrough */
	case POWER_S3:
		/* fallthrough */
	case POWER_S0:
#ifdef CONFIG_POWER_S0IX
		/* fallthrough */
	case POWER_S0ix:
#endif
		/* Wait for a message */
		power_wait_signals(0);
		task_wait_event(-1);
		break;

	default:
		/* No common functionality for transition states */
		break;
	}

	return state;
}

/*****************************************************************************/
/* Chipset interface */

int chipset_in_state(int state_mask)
{
	int need_mask = 0;

	/*
	 * TODO(crosbug.com/p/23773): what to do about state transitions?  If
	 * the caller wants HARD_OFF|SOFT_OFF and we're in G3S5, we could still
	 * return non-zero.
	 */
	switch (state) {
	case POWER_G3:
		need_mask = CHIPSET_STATE_HARD_OFF;
		break;
	case POWER_G3S5:
	case POWER_S5G3:
		/*
		 * In between hard and soft off states.  Match only if caller
		 * will accept both.
		 */
		need_mask = CHIPSET_STATE_HARD_OFF | CHIPSET_STATE_SOFT_OFF;
		break;
	case POWER_S5:
	case POWER_S5S4:
	case POWER_S4S5:
	case POWER_S4:
		need_mask = CHIPSET_STATE_SOFT_OFF;
		break;
	case POWER_S5S3:
	case POWER_S3S5:
	case POWER_S4S3:
	case POWER_S3S4:
		need_mask = CHIPSET_STATE_SOFT_OFF | CHIPSET_STATE_SUSPEND;
		break;
	case POWER_S3:
		need_mask = CHIPSET_STATE_SUSPEND;
		break;
	case POWER_S3S0:
	case POWER_S0S3:
		need_mask = CHIPSET_STATE_SUSPEND | CHIPSET_STATE_ON;
		break;
	case POWER_S0:
		need_mask = CHIPSET_STATE_ON;
		break;
#ifdef CONFIG_POWER_S0IX
	case POWER_S0ixS0:
	case POWER_S0S0ix:
		need_mask = CHIPSET_STATE_ON | CHIPSET_STATE_STANDBY;
		break;
	case POWER_S0ix:
		need_mask = CHIPSET_STATE_STANDBY;
		break;
#endif
	}

	/* Return non-zero if all needed bits are present */
	return (state_mask & need_mask) == need_mask;
}

int chipset_in_or_transitioning_to_state(int state_mask)
{
	switch (state) {
	case POWER_G3:
	case POWER_S5G3:
		return state_mask & CHIPSET_STATE_HARD_OFF;
	case POWER_S5:
	case POWER_S4:
	case POWER_S3S5:
	case POWER_G3S5:
	case POWER_S4S5:
	case POWER_S5S4:
	case POWER_S3S4:
		return state_mask & CHIPSET_STATE_SOFT_OFF;
	case POWER_S5S3:
	case POWER_S3:
	case POWER_S4S3:
	case POWER_S0S3:
		return state_mask & CHIPSET_STATE_SUSPEND;
#ifdef CONFIG_POWER_S0IX
	case POWER_S0ix:
	case POWER_S0S0ix:
		return state_mask & CHIPSET_STATE_STANDBY;
#endif
	case POWER_S0:
	case POWER_S3S0:
#ifdef CONFIG_POWER_S0IX
	case POWER_S0ixS0:
#endif
		return state_mask & CHIPSET_STATE_ON;
	}

	/* Unknown power state; return false. */
	return 0;
}

void chipset_exit_hard_off(void)
{
	/*
	 * If not in the soft-off state, hard-off state, or headed there,
	 * nothing to do.
	 */
	if (state != POWER_G3 && state != POWER_S5G3 && state != POWER_S5)
		return;

	/*
	 * Set a flag to leave G3, then wake the task. If the power state is
	 * POWER_S5G3, or is POWER_S5 but the S5 inactivity timer has
	 * expired, set this flag can let system go to G3 and then exit G3
	 * immediately for powering on.
	 */
	want_g3_exit = 1;

	/*
	 * If the power state is in POWER_S5 and S5 inactivity timer is
	 * running, to wake the chipset task can cancel S5 inactivity timer and
	 * then restart the timer. This will give cpu a chance to start up if
	 * S5 inactivity timer is about to expire while power button is
	 * pressed. For other states here, to wake the chipset task to trigger
	 * the event for leaving G3 is necessary.
	 */
	task_wake(TASK_ID_CHIPSET);
}

/*****************************************************************************/
/* Task function */

void chipset_task(void *u)
{
	enum power_state new_state;
	static enum power_state last_state;
	uint32_t this_in_signals;
	static uint32_t last_in_signals;

	while (1) {
		/*
		 * In order to prevent repeated console spam, only print the
		 * current power state if something has actually changed.  It's
		 * possible that one of the power signals goes away briefly and
		 * comes back by the time we update our in_signals.
		 */
		this_in_signals = in_signals;
		if (this_in_signals != last_in_signals || state != last_state) {
			CPRINTS("power state %d = %s, in 0x%04x",
				state, state_names[state], this_in_signals);
			if (IS_ENABLED(CONFIG_SEVEN_SEG_DISPLAY))
				display_7seg_write(SEVEN_SEG_EC_DISPLAY, state);
			last_in_signals = this_in_signals;
			last_state = state;
		}

		/* Always let the specific chipset handle the state first */
		new_state = power_handle_state(state);

		/*
		 * If the state hasn't changed, run common steady-state
		 * handler.
		 */
		if (new_state == state)
			new_state = power_common_state(state);

		/* Handle state changes */
		if (new_state != state) {
			power_set_state(new_state);
			power_set_active_wake_mask();

			/* Call hooks before we enter G3 */
			if (new_state == POWER_G3)
				hook_notify(HOOK_CHIPSET_HARD_OFF);
		}
	}
}

/*****************************************************************************/
/* Hooks */

static void power_common_init(void)
{
	const struct power_signal_info *s = power_signal_list;
	int i;

	/* Update input state */
	power_update_signals();

	/* Enable interrupts for input signals */
	for (i = 0; i < POWER_SIGNAL_COUNT; i++, s++)
		if (s->flags & POWER_SIGNAL_DISABLE_AT_BOOT)
			power_signal_disable_interrupt(s->gpio);
		else
			power_signal_enable_interrupt(s->gpio);

	/* Call chipset-specific init to set initial state */
	power_set_state(power_chipset_init());

	/*
	 * Update input state again since there is a small window
	 * before GPIO is enabled.
	 */
	power_update_signals();
}
DECLARE_HOOK(HOOK_INIT, power_common_init, HOOK_PRIO_INIT_CHIPSET);

static void power_lid_change(void)
{
	/* Wake up the task to update power state */
	task_wake(TASK_ID_CHIPSET);
}
DECLARE_HOOK(HOOK_LID_CHANGE, power_lid_change, HOOK_PRIO_DEFAULT);

#ifdef CONFIG_EXTPOWER
static void power_ac_change(void)
{
	if (extpower_is_present()) {
		CPRINTS("AC on");
	} else {
		CPRINTS("AC off");

		if (state == POWER_G3) {
			last_shutdown_time = get_time().val;
			task_wake(TASK_ID_CHIPSET);
		}
	}
}
DECLARE_HOOK(HOOK_AC_CHANGE, power_ac_change, HOOK_PRIO_DEFAULT);
#endif

/*****************************************************************************/
/* Interrupts */

#ifdef CONFIG_BRINGUP
#define MAX_SIGLOG_ENTRIES 24

static unsigned int siglog_entries;
static unsigned int siglog_truncated;

static struct {
	timestamp_t time;
	enum gpio_signal signal;
	int level;
} siglog[MAX_SIGLOG_ENTRIES];

static void siglog_deferred(void)
{
	unsigned int i;
	timestamp_t tdiff = {.val = 0};

	/* Disable interrupts for input signals while we print stuff.*/
	for (i = 0; i < POWER_SIGNAL_COUNT; i++)
		power_signal_disable_interrupt(power_signal_list[i].gpio);

	CPRINTF("%d signal changes:\n", siglog_entries);
	for (i = 0; i < siglog_entries; i++) {
		if (i)
			tdiff.val = siglog[i].time.val - siglog[i-1].time.val;
		CPRINTF("  %.6lld  +%.6lld  %s => %d\n",
			siglog[i].time.val, tdiff.val,
			power_signal_get_name(siglog[i].signal),
			siglog[i].level);
	}
	if (siglog_truncated)
		CPRINTF("  SIGNAL LOG TRUNCATED...\n");
	siglog_entries = siglog_truncated = 0;

	/* Okay, turn 'em on again. */
	for (i = 0; i < POWER_SIGNAL_COUNT; i++)
		power_signal_enable_interrupt(power_signal_list[i].gpio);
}
DECLARE_DEFERRED(siglog_deferred);

static void siglog_add(enum gpio_signal signal)
{
	if (siglog_entries >= MAX_SIGLOG_ENTRIES) {
		siglog_truncated = 1;
		return;
	}

	siglog[siglog_entries].time = get_time();
	siglog[siglog_entries].signal = signal;
	siglog[siglog_entries].level = power_signal_get_level(signal);
	siglog_entries++;

	hook_call_deferred(&siglog_deferred_data, SECOND);
}

#define SIGLOG(S) siglog_add(S)

#else
#define SIGLOG(S)
#endif	/* CONFIG_BRINGUP */

#ifdef CONFIG_POWER_SIGNAL_INTERRUPT_STORM_DETECT_THRESHOLD
/*
 * Print an interrupt storm warning when we receive more than
 * CONFIG_POWER_SIGNAL_INTERRUPT_STORM_DETECT_THRESHOLD interrupts of a
 * single source within 1 second.
 */
static int power_signal_interrupt_count[POWER_SIGNAL_COUNT];

static void reset_power_signal_interrupt_count(void)
{
	int i;

	for (i = 0; i < POWER_SIGNAL_COUNT; ++i)
		power_signal_interrupt_count[i] = 0;
}
DECLARE_HOOK(HOOK_SECOND,
	     reset_power_signal_interrupt_count,
	     HOOK_PRIO_DEFAULT);
#endif

void power_signal_interrupt(enum gpio_signal signal)
{
#ifdef CONFIG_POWER_SIGNAL_INTERRUPT_STORM_DETECT_THRESHOLD
	int i;

	/* Tally our interrupts and print a warning if necessary. */
	for (i = 0; i < POWER_SIGNAL_COUNT; ++i) {
		if (power_signal_list[i].gpio == signal) {
			if (power_signal_interrupt_count[i]++ ==
			   CONFIG_POWER_SIGNAL_INTERRUPT_STORM_DETECT_THRESHOLD)
				CPRINTS("Interrupt storm! Signal %d", i);
			break;
		}
	}
#endif

	SIGLOG(signal);

	/* Shadow signals and compare with our desired signal state. */
	power_update_signals();

	/* Wake up the task */
	task_wake(TASK_ID_CHIPSET);
}

#ifdef CONFIG_POWER_SHUTDOWN_PAUSE_IN_S5
inline int power_get_pause_in_s5(void)
{
	return pause_in_s5;
}

inline void power_set_pause_in_s5(int pause)
{
	pause_in_s5 = pause;
}
#endif

/*****************************************************************************/
/* Console commands */

static int command_powerinfo(int argc, char **argv)
{
	/*
	 * Print power state in same format as state machine.  This is
	 * used by FAFT tests, so must match exactly.
	 */
	ccprintf("power state %d = %s, in 0x%04x\n",
		 state, state_names[state], in_signals);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(powerinfo, command_powerinfo,
			NULL,
			"Show current power state");

#ifdef CONFIG_CMD_POWERINDEBUG
static int command_powerindebug(int argc, char **argv)
{
	const struct power_signal_info *s = power_signal_list;
	int i;
	char *e;

	/* If one arg, set the mask */
	if (argc == 2) {
		int m = strtoi(argv[1], &e, 0);
		if (*e)
			return EC_ERROR_PARAM1;

		in_debug = m;
	}

	/* Print the mask */
	ccprintf("power in:   0x%04x\n", in_signals);
	ccprintf("debug mask: 0x%04x\n", in_debug);

	/* Print the decode */

	ccprintf("bit meanings:\n");
	for (i = 0; i < POWER_SIGNAL_COUNT; i++, s++) {
		int mask = 1 << i;
		ccprintf("  0x%04x %d %s\n",
			 mask, in_signals & mask ? 1 : 0, s->name);
	}

	return EC_SUCCESS;
};
DECLARE_CONSOLE_COMMAND(powerindebug, command_powerindebug,
			"[mask]",
			"Get/set power input debug mask");
#endif

#ifdef CONFIG_CMD_S5_TIMEOUT
/* Allow command-line access to configure our S5 delay for power testing */
static int command_s5_timeout(int argc, char **argv)
{
	char *e;

	if (argc >= 2) {
		uint32_t s = strtoi(argv[1], &e, 0);

		if (*e)
			return EC_ERROR_PARAM1;

		s5_inactivity_timeout = s;
	}

	/* Print the current setting */
	ccprintf("S5 inactivity timeout: %d s\n", s5_inactivity_timeout);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(s5_timeout, command_s5_timeout,
			"[sec]",
			"Set the timeout from S5 to G3 transition, "
			"-1 to indicate no transition");
#endif

#ifdef CONFIG_HIBERNATE
static int command_hibernation_delay(int argc, char **argv)
{
	char *e;
	uint32_t time_g3 = ((uint32_t)(get_time().val - last_shutdown_time))
				/ SECOND;

	if (argc >= 2) {
		uint32_t s = strtoi(argv[1], &e, 0);
		if (*e)
			return EC_ERROR_PARAM1;

		hibernate_delay = s;
	}

	/* Print the current setting */
	ccprintf("Hibernation delay: %d s\n", hibernate_delay);
	if (state == POWER_G3 && !extpower_is_present()) {
		ccprintf("Time G3: %d s\n", time_g3);
		ccprintf("Time left: %d s\n", hibernate_delay - time_g3);
	}
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(hibdelay, command_hibernation_delay,
			"[sec]",
			"Set the delay before going into hibernation");

static enum ec_status
host_command_hibernation_delay(struct host_cmd_handler_args *args)
{
	const struct ec_params_hibernation_delay *p = args->params;
	struct ec_response_hibernation_delay *r = args->response;

	uint32_t time_g3;
	uint64_t t = get_time().val - last_shutdown_time;

	uint64divmod(&t, SECOND);
	time_g3 = (uint32_t)t;

	/* Only change the hibernation delay if seconds is non-zero. */
	if (p->seconds)
		hibernate_delay = p->seconds;

	if (state == POWER_G3 && !extpower_is_present())
		r->time_g3 = time_g3;
	else
		r->time_g3 = 0;

	if ((time_g3 != 0) && (time_g3 > hibernate_delay))
		r->time_remaining = 0;
	else
		r->time_remaining = hibernate_delay - time_g3;
	r->hibernate_delay = hibernate_delay;

	args->response_size = sizeof(struct ec_response_hibernation_delay);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_HIBERNATION_DELAY,
		     host_command_hibernation_delay,
		     EC_VER_MASK(0));
#endif /* CONFIG_HIBERNATE */

#ifdef CONFIG_POWER_SHUTDOWN_PAUSE_IN_S5
static enum ec_status
host_command_pause_in_s5(struct host_cmd_handler_args *args)
{
	const struct ec_params_get_set_value *p = args->params;
	struct ec_response_get_set_value *r = args->response;

	if (p->flags & EC_GSV_SET)
		pause_in_s5 = p->value;

	r->value = pause_in_s5;

	args->response_size = sizeof(*r);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_GSV_PAUSE_IN_S5,
		     host_command_pause_in_s5,
		     EC_VER_MASK(0));

static int command_pause_in_s5(int argc, char **argv)
{
	if (argc > 1 && !parse_bool(argv[1], &pause_in_s5))
		return EC_ERROR_INVAL;

	ccprintf("pause_in_s5 = %s\n", pause_in_s5 ? "on" : "off");

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pause_in_s5, command_pause_in_s5,
			"[on|off]",
			"Should the AP pause in S5 during shutdown?");
#endif /* CONFIG_POWER_SHUTDOWN_PAUSE_IN_S5 */

#ifdef CONFIG_POWER_PP5000_CONTROL
__overridable void board_power_5v_enable(int enable)
{
	if (enable)
		gpio_set_level(GPIO_EN_PP5000, 1);
	else
		gpio_set_level(GPIO_EN_PP5000, 0);
}

/* 5V enable request bitmask from various tasks. */
static uint32_t pwr_5v_en_req;
K_MUTEX_DEFINE(pwr_5v_ctl_mtx);

void power_5v_enable(task_id_t tid, int enable)
{
	mutex_lock(&pwr_5v_ctl_mtx);

	if (enable) /* Set the bit indicating the request. */
		pwr_5v_en_req |= 1 << tid;
	else /* Clear the task's request bit. */
		pwr_5v_en_req &= ~(1 << tid);

	/*
	 * If there are any outstanding requests for the rail to be enabled,
	 * turn on the rail.  Otherwise, turn it off.
	 */
	board_power_5v_enable(pwr_5v_en_req);
	mutex_unlock(&pwr_5v_ctl_mtx);
}

#define P5_SYSJUMP_TAG 0x5005  /* "P5" */
static void restore_enable_5v_state(void)
{
	const uint32_t *state;
	int size;

	state = (const uint32_t *) system_get_jump_tag(P5_SYSJUMP_TAG, 0,
						       &size);
	if (state && size == sizeof(pwr_5v_en_req)) {
		mutex_lock(&pwr_5v_ctl_mtx);
		pwr_5v_en_req |= *state;
		mutex_unlock(&pwr_5v_ctl_mtx);
	}
}
DECLARE_HOOK(HOOK_INIT, restore_enable_5v_state, HOOK_PRIO_FIRST);

static void preserve_enable_5v_state(void)
{
	mutex_lock(&pwr_5v_ctl_mtx);
	system_add_jump_tag(P5_SYSJUMP_TAG, 0, sizeof(pwr_5v_en_req),
	    &pwr_5v_en_req);
	mutex_unlock(&pwr_5v_ctl_mtx);
}
DECLARE_HOOK(HOOK_SYSJUMP, preserve_enable_5v_state, HOOK_PRIO_DEFAULT);
#endif /* defined(CONFIG_POWER_PP5000_CONTROL) */
