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

/*
 * MT8192 SoC power sequencing module for Chrome EC
 *
 * This implements the following features:
 *
 * - Cold reset powers on the AP
 *
 * When powered off:
 *  - Press power button turns on the AP
 *  - Hold power button turns on the AP, and then 8s later turns it off and
 *    leaves it off until pwron is released and press again.
 *  - Lid open turns on the AP
 *
 *  When powered on:
 *  - Holding power button for 8s powers off the AP
 *  - Pressing and releaseing pwron within that 8s is ignored
 */

#include "battery.h"
#include "chipset.h"
#include "common.h"
#include "gpio.h"
#include "hooks.h"
#include "lid_switch.h"
#include "power.h"
#include "power_button.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

#ifdef CONFIG_BRINGUP
#define GPIO_SET_LEVEL(signal, value) \
	gpio_set_level_verbose(CC_CHIPSET, signal, value)
#else
#define GPIO_SET_LEVEL(signal, value) gpio_set_level(signal, value)
#endif

/* Console output macros */
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ##args)

/* Input state flags */
#define IN_SUSPEND_ASSERTED POWER_SIGNAL_MASK(AP_IN_S3_L)
#define IN_PGOOD_PMIC POWER_SIGNAL_MASK(PMIC_PWR_GOOD)
#define IN_AP_WDT_ASSERTED POWER_SIGNAL_MASK(AP_WDT_ASSERTED)

/* Rails required for S3 and S0 */
#define IN_PGOOD_S0 (IN_PGOOD_PMIC)
#define IN_PGOOD_S3 (IN_PGOOD_PMIC)

/* All inputs in the right state for S0 */
#define IN_ALL_S0 (IN_PGOOD_S0 & ~IN_SUSPEND_ASSERTED)

/* Long power key press to force shutdown in S0. go/crosdebug */
#define FORCED_SHUTDOWN_DELAY (8 * SECOND)

/* Long power key press to boot from S5/G3 state. */
#ifndef POWERBTN_BOOT_DELAY
#define POWERBTN_BOOT_DELAY (10 * MSEC)
#endif
#define PMIC_EN_PULSE_MS 50

/* Maximum time it should for PMIC to turn on after toggling PMIC_EN_ODL. */
#define PMIC_EN_TIMEOUT (300 * MSEC)

/*
 * Time delay for AP on/off the AP_EC_WDT when received SYS_RST_ODL.
 * Generally it can be done within 3 ms.
 */
#define AP_EC_WDT_TIMEOUT (100 * MSEC)

/* 30 ms for hard reset, we hold it longer to prevent TPM false alarm. */
#define SYS_RST_PULSE_LENGTH (50 * MSEC)

#ifndef CONFIG_ZEPHYR

/* power signal list.  Must match order of enum power_signal. */
const struct power_signal_info power_signal_list[] = {
	{GPIO_PMIC_EC_PWRGD, POWER_SIGNAL_ACTIVE_HIGH, "PMIC_PWR_GOOD"},
	{GPIO_AP_IN_SLEEP_L, POWER_SIGNAL_ACTIVE_LOW, "AP_IN_S3_L"},
	{GPIO_AP_EC_WATCHDOG_L, POWER_SIGNAL_ACTIVE_LOW, "AP_WDT_ASSERTED"},
};
BUILD_ASSERT(ARRAY_SIZE(power_signal_list) == POWER_SIGNAL_COUNT);

#endif /* !CONFIG_ZEPHYR */

static int forcing_shutdown;

static void watchdog_interrupt_deferred(void)
{
	chipset_reset(CHIPSET_RESET_AP_WATCHDOG);
}
DECLARE_DEFERRED(watchdog_interrupt_deferred);

static void reset_request_interrupt_deferred(void)
{
	chipset_reset(CHIPSET_RESET_AP_REQ);
}
DECLARE_DEFERRED(reset_request_interrupt_deferred);

void chipset_reset_request_interrupt(enum gpio_signal signal)
{
	hook_call_deferred(&reset_request_interrupt_deferred_data, 0);
}

/*
 * Triggers on falling edge of AP watchdog line only. The falling edge can
 * happen in these 3 cases:
 *  - AP asserts watchdog while the AP is on: this is a real AP-initiated reset.
 *  - EC asserted GPIO_SYS_RST_ODL, so the AP is in reset and AP watchdog falls
 *    as well. This is _not_ a watchdog reset. We mask these cases by disabling
 *    the interrupt just before shutting down the AP, and re-enabling it just
 *    after starting the AP.
 *  - PMIC has shut down (e.g. the AP powered off by itself), this is not a
 *    watchdog reset either. This should be covered by the case above if the
 *    EC reacts quickly enough, but we mask those cases as well by testing if
 *    the PMIC is still on when the watchdog line falls.
 */
void chipset_watchdog_interrupt(enum gpio_signal signal)
{
	/* Pass AP_EC_WATCHDOG_L signal to PMIC */
	GPIO_SET_LEVEL(GPIO_EC_PMIC_WATCHDOG_L, gpio_get_level(signal));

	/* Update power signals */
	power_signal_interrupt(signal);

	/*
	 * case 1: PMIC is good, WDT asserts, and EC is not asserting
	 * SYS_RST_ODL. This is AP initiated real WDT.
	 */
	if (gpio_get_level(GPIO_SYS_RST_ODL) &&
	    power_get_signals() & IN_PGOOD_PMIC &&
	    power_get_signals() & IN_AP_WDT_ASSERTED)
		hook_call_deferred(&watchdog_interrupt_deferred_data, 0);

	/*
	 * case 2&3: Fall through. The chipset_reset should have been
	 * invoked.
	 */
}

void chipset_force_shutdown(enum chipset_shutdown_reason reason)
{
	CPRINTS("%s(%d)", __func__, reason);
	report_ap_reset(reason);

	/*
	 * Force power off. This condition will reset once the state machine
	 * transitions to G3.
	 */
	forcing_shutdown = 1;
	task_wake(TASK_ID_CHIPSET);
}

void chipset_force_shutdown_button(void)
{
	chipset_force_shutdown(CHIPSET_SHUTDOWN_BUTTON);
}
DECLARE_DEFERRED(chipset_force_shutdown_button);

void chipset_exit_hard_off_button(void)
{
	/* Power up from off */
	forcing_shutdown = 0;
	chipset_exit_hard_off();
}
DECLARE_DEFERRED(chipset_exit_hard_off_button);

void chipset_reset(enum chipset_shutdown_reason reason)
{
	CPRINTS("%s: %d", __func__, reason);
	report_ap_reset(reason);

	GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0);
	usleep(SYS_RST_PULSE_LENGTH);
	GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1);
}

#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE
static void power_reset_host_sleep_state(void)
{
	power_set_host_sleep_state(HOST_SLEEP_EVENT_DEFAULT_RESET);
	sleep_reset_tracking();
	power_chipset_handle_host_sleep_event(HOST_SLEEP_EVENT_DEFAULT_RESET,
					      NULL);
}

static void handle_chipset_reset(void)
{
	if (chipset_in_state(CHIPSET_STATE_SUSPEND)) {
		CPRINTS("Chipset reset: exit s3");
		power_reset_host_sleep_state();
		task_wake(TASK_ID_CHIPSET);
	}
}
DECLARE_HOOK(HOOK_CHIPSET_RESET, handle_chipset_reset, HOOK_PRIO_FIRST);
#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */

enum power_state power_chipset_init(void)
{
	int exit_hard_off = 1;

	/* Enable reboot / sleep control inputs from AP */
	gpio_enable_interrupt(GPIO_AP_EC_WARM_RST_REQ);
	gpio_enable_interrupt(GPIO_AP_IN_SLEEP_L);

	if (system_jumped_late()) {
		if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
			disable_sleep(SLEEP_MASK_AP_RUN);
			power_signal_enable_interrupt(GPIO_AP_EC_WATCHDOG_L);
			CPRINTS("already in S0");
			return POWER_S0;
		}
	} else if (system_get_reset_flags() & EC_RESET_FLAG_AP_OFF) {
		exit_hard_off = 0;
	} else if ((system_get_reset_flags() & EC_RESET_FLAG_HIBERNATE) &&
			gpio_get_level(GPIO_AC_PRESENT)) {
		/*
		 * If AC present, assume this is a wake-up by AC insert.
		 * Boot EC only.
		 *
		 * Note that extpower module is not initialized at this point,
		 * the only way is to ask GPIO_AC_PRESENT directly.
		 */
		exit_hard_off = 0;
	}

	if (battery_is_present() == BP_YES)
		/*
		 * (crosbug.com/p/28289): Wait battery stable.
		 * Some batteries use clock stretching feature, which requires
		 * more time to be stable.
		 */
		battery_wait_for_stable();

	if (exit_hard_off)
		/* Auto-power on */
		chipset_exit_hard_off();

	/* Start from S5 if the PMIC is already up. */
	if (power_get_signals() & IN_PGOOD_PMIC) {
		/* Force shutdown from S5 if the PMIC is already up. */
		if (!exit_hard_off)
			forcing_shutdown = 1;
		return POWER_S5;
	}

	return POWER_G3;
}

enum power_state power_handle_state(enum power_state state)
{
	/* Retry S5->S3 transition, if not zero. */
	static int s5s3_retry;

	/*
	 * PMIC power went away (AP most likely decided to shut down):
	 * transition to S5, G3.
	 */
	static int ap_shutdown;

	switch (state) {
	case POWER_G3:

		/* Go back to S5->G3 if the PMIC unexpectedly starts again. */
		if (power_get_signals() & IN_PGOOD_PMIC)
			return POWER_S5G3;
		break;

	case POWER_S5:
		/*
		 * If AP initiated shutdown, PMIC is off, and we can transition
		 * to G3 immediately.
		 */
		if (ap_shutdown) {
			ap_shutdown = 0;
			return POWER_S5G3;
		} else if (!forcing_shutdown) {
			/* Powering up. */
			s5s3_retry = 1;
			return POWER_S5S3;
		}

		/* Forcing shutdown */

		/* Long press has worked, transition to G3. */
		if (!(power_get_signals() & IN_PGOOD_PMIC))
			return POWER_S5G3;

		/*
		 * Try to force PMIC shutdown with a long press. This takes 8s,
		 * shorter than the common code S5->G3 timeout (10s).
		 *
		 * Note: We might run twice at this line because we
		 * deasserts SYS_RST_ODL in S5->S3 and then WDT interrupt
		 * handler sets the wake event for chipset_task. This should be
		 * no harm, but to prevent misunderstanding in the console, we
		 * check EC_PMIC_EN_ODL before set.
		 */
		if (gpio_get_level(GPIO_EC_PMIC_EN_ODL)) {
			CPRINTS("Forcing shutdown with long press.");
			GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 0);
		}

		/*
		 * Stay in S5, common code will drop to G3 after timeout
		 * if the long press does not work.
		 */
		return POWER_S5;

	case POWER_S3:
		if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown)
			return POWER_S3S5;
		else if (!(power_get_signals() & IN_SUSPEND_ASSERTED))
			return POWER_S3S0;
		break;

	case POWER_S0:
		if (!power_has_signals(IN_PGOOD_S0) || forcing_shutdown ||
		    power_get_signals() & IN_SUSPEND_ASSERTED)
			return POWER_S0S3;

		break;

	case POWER_G3S5:
		forcing_shutdown = 0;

		/* Power up to next state */
		return POWER_S5;

	case POWER_S5S3:
		hook_notify(HOOK_CHIPSET_PRE_INIT);

		/*
		 * Release power button in case it was pressed by force shutdown
		 * sequence.
		 */
		GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1);

		/* If PMIC is off, switch it on by pulsing PMIC enable. */
		if (!(power_get_signals() & IN_PGOOD_PMIC)) {
			msleep(PMIC_EN_PULSE_MS);
			GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 0);
			msleep(PMIC_EN_PULSE_MS);
			GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1);
		}

		/*
		 * Wait for PMIC to bring up rails. Retry if it fails
		 * (it may take 2 attempts on restart after we use
		 * force reset).
		 */
		if (power_wait_signals_timeout(IN_PGOOD_PMIC,
					       PMIC_EN_TIMEOUT)) {
			if (s5s3_retry) {
				s5s3_retry = 0;
				return POWER_S5S3;
			}
			/* Give up, go back to G3. */
			return POWER_S5G3;
		}

		/* Release AP reset and waits for AP pulling WDT up. */
		power_signal_enable_interrupt(GPIO_AP_EC_WATCHDOG_L);
		GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1);
		if (power_wait_mask_signals_timeout(0, IN_AP_WDT_ASSERTED,
						    AP_EC_WDT_TIMEOUT)) {
			if (s5s3_retry) {
				s5s3_retry = 0;
				return POWER_S5S3;
			}
			/* Give up, go back to G3. */
			return POWER_S5G3;
		}

		/* Call hooks now that rails are up */
		hook_notify(HOOK_CHIPSET_STARTUP);

		/*
		 * Clearing the sleep failure detection tracking on the path
		 * to S0 to handle any reset conditions.
		 */
#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION
		power_reset_host_sleep_state();
#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */
		/* Power up to next state */
		return POWER_S3;

	case POWER_S3S0:
		if (power_wait_signals(IN_PGOOD_S0)) {
			chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
			return POWER_S0S3;
		}

		/* Call hooks now that rails are up */
		hook_notify(HOOK_CHIPSET_RESUME);

#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION
		sleep_resume_transition();
#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */

		/*
		 * Disable idle task deep sleep. This means that the low
		 * power idle task will not go into deep sleep while in S0.
		 */
		disable_sleep(SLEEP_MASK_AP_RUN);

		/* Power up to next state */
		return POWER_S0;

	case POWER_S0S3:
		/* Call hooks before we remove power rails */
		hook_notify(HOOK_CHIPSET_SUSPEND);

#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION
		sleep_suspend_transition();
#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */

		/*
		 * Enable idle task deep sleep. Allow the low power idle task
		 * to go into deep sleep in S3 or lower.
		 */
		enable_sleep(SLEEP_MASK_AP_RUN);

		/*
		 * In case the power button is held awaiting power-off timeout,
		 * power off immediately now that we're entering S3.
		 */
		if (power_button_is_pressed()) {
			forcing_shutdown = 1;
			hook_call_deferred(&chipset_force_shutdown_button_data,
					   -1);
		}

		return POWER_S3;

	case POWER_S3S5:
		/* PMIC has shutdown, transition to G3. */
		if (!(power_get_signals() & IN_PGOOD_PMIC))
			ap_shutdown = 1;

		/* Call hooks before we remove power rails */
		hook_notify(HOOK_CHIPSET_SHUTDOWN);

		/*
		 * Assert SYS_RST_ODL, and waits for AP finishing epilogue and
		 * asserting WDT.
		 */
		GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0);
		if (EC_ERROR_TIMEOUT ==
		    power_wait_signals_timeout(IN_AP_WDT_ASSERTED,
					       AP_EC_WDT_TIMEOUT)) {
			CPRINTS("Timeout waitting AP watchdog, force if off");
			GPIO_SET_LEVEL(GPIO_EC_PMIC_WATCHDOG_L, 0);
		}
		power_signal_disable_interrupt(GPIO_AP_EC_WATCHDOG_L);

		/* Call hooks after we remove power rails */
		hook_notify(HOOK_CHIPSET_SHUTDOWN_COMPLETE);

		/* Start shutting down */
		return POWER_S5;

	case POWER_S5G3:
		/* Release the power button, in case it was long pressed. */
		if (forcing_shutdown)
			GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1);

		/* If PMIC is not off, go back to S5 and try again. */
		if (power_get_signals() & IN_PGOOD_PMIC)
			return POWER_S5;

		return POWER_G3;

	default:
		CPRINTS("Unexpected power state %d", state);
		ASSERT(0);
		break;
	}

	return state;
}

static void power_button_changed(void)
{
	if (power_button_is_pressed()) {
		if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
			hook_call_deferred(&chipset_exit_hard_off_button_data,
					   POWERBTN_BOOT_DELAY);

		/* Delayed power down from S0/S3, cancel on PB release */
		hook_call_deferred(&chipset_force_shutdown_button_data,
				   FORCED_SHUTDOWN_DELAY);
	} else {
		/* Power button released, cancel deferred shutdown/boot */
		hook_call_deferred(&chipset_exit_hard_off_button_data, -1);
		hook_call_deferred(&chipset_force_shutdown_button_data, -1);
	}
}
DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT);

#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE
static void suspend_hang_detected(void)
{
	CPRINTS("Warning: Detected sleep hang! Waking host up!");
	host_set_single_event(EC_HOST_EVENT_HANG_DETECT);
}

__override void power_chipset_handle_host_sleep_event(
		enum host_sleep_event state,
		struct host_sleep_event_context *ctx)
{
	CPRINTS("Handle sleep: %d", state);

	if (state == HOST_SLEEP_EVENT_S3_SUSPEND) {
		/*
		 * Indicate to power state machine that a new host event for
		 * S3 suspend has been received and so chipset suspend
		 * notification needs to be sent to listeners.
		 */
		sleep_set_notify(SLEEP_NOTIFY_SUSPEND);
		sleep_start_suspend(ctx, suspend_hang_detected);

	} else if (state == HOST_SLEEP_EVENT_S3_RESUME) {
		/*
		 * Wake up chipset task and indicate to power state machine that
		 * listeners need to be notified of chipset resume.
		 */
		sleep_set_notify(SLEEP_NOTIFY_RESUME);
		task_wake(TASK_ID_CHIPSET);
		sleep_complete_resume(ctx);

	}
}
#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */

#ifdef CONFIG_LID_SWITCH
static void lid_changed(void)
{
	/* Power-up from off on lid open */
	if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF))
		chipset_exit_hard_off();
}
DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT);
#endif
