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

/* FalconLite chipset power control module for Chrome EC */

#include "charge_state.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "ec_commands.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 CPUTS(outstr) cputs(CC_CHIPSET, outstr)
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)

/* 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. */
#define POWERBTN_BOOT_DELAY (10 * MSEC)

#define SYS_RST_PULSE_LENGTH (30 * MSEC)

/* Masks for power signals */
#define IN_PG_S5		POWER_SIGNAL_MASK(FCL_PG_S5)
#define IN_PGOOD		(POWER_SIGNAL_MASK(FCL_PG_VDD1_VDD2) | \
				 POWER_SIGNAL_MASK(FCL_PG_VDD_MEDIA_ML) | \
				 POWER_SIGNAL_MASK(FCL_PG_VDD_SOC) | \
				 POWER_SIGNAL_MASK(FCL_PG_VDD_DDR_OD) | \
				 POWER_SIGNAL_MASK(FCL_PG_S5))

#define IN_ALL_S0		IN_PGOOD
#define IN_ALL_S3		IN_PGOOD

/* Power signal list. Must match order of enum power_signal. */
const struct power_signal_info power_signal_list[] = {
	[FCL_AP_WARM_RST_REQ] = {
		GPIO_AP_EC_WARM_RST_REQ,
		POWER_SIGNAL_ACTIVE_HIGH,
		"AP_WARM_RST_REQ"
	},
	[FCL_AP_SHUTDOWN_REQ] = {
		GPIO_AP_EC_SHUTDOWN_REQ_L,
		POWER_SIGNAL_ACTIVE_LOW,
		"AP_SHUTDOWN_REQ"
	},
	[FCL_AP_WATCHDOG] = {
		GPIO_AP_EC_WATCHDOG_L,
		POWER_SIGNAL_ACTIVE_LOW,
		"AP_WDT"
	},
	[FCL_PG_S5] = {
		GPIO_PG_S5_PWR_OD,
		POWER_SIGNAL_ACTIVE_HIGH,
		"PG_S5"
	},
	[FCL_PG_VDD1_VDD2] = {
		GPIO_PG_VDD1_VDD2_OD,
		POWER_SIGNAL_ACTIVE_HIGH,
		"PG_VDD1_VDD2"
	},
	[FCL_PG_VDD_MEDIA_ML] = {
		GPIO_PG_VDD_MEDIA_ML_OD,
		POWER_SIGNAL_ACTIVE_HIGH,
		"PG_VDD_MEDIA_ML"
	},
	[FCL_PG_VDD_SOC] = {
		GPIO_PG_VDD_SOC_OD,
		POWER_SIGNAL_ACTIVE_HIGH,
		"PG_VDD_SOC"
	},
	[FCL_PG_VDD_DDR_OD] = {
		GPIO_PG_VDD_DDR_OD,
		POWER_SIGNAL_ACTIVE_HIGH,
		"PG_VDD_DDR"
	},
};


/* Data structure for a GPIO operation for power sequencing */
struct power_seq_op {
	enum gpio_signal signal;
	uint8_t level;
	/* Number of milliseconds to delay after setting signal to level */
	uint32_t delay;
};

/*
 * The entries in the table are handled sequentially from the top
 * to the bottom.
 */

/* The power sequence for POWER_S3S5 */
static const struct power_seq_op s3s5_power_seq[] = {
	{GPIO_EN_VDD_CPU, 0, 0},
	{GPIO_EN_VDD_GPU, 0, 0},
	{GPIO_EN_VDD_MEDIA_ML, 0, 4},

	{GPIO_EN_VDDQ_VR_D, 0, 4},    /* LPDDR */

	{GPIO_EN_VDD1_VDD2_VR, 0, 4}, /* LPDDR */

	{GPIO_EN_VDD_DDR, 0, 4},

	{GPIO_EN_PP3300A_IO_X, 0, 0},
	{GPIO_EN_PP3300_S3, 0, 4},

	{GPIO_EN_PP1820A_IO_X, 0, 0},
	{GPIO_EN_PP1800_S3, 0, 0},
};

/* The power sequence for POWER_G3S5 */
static const struct power_seq_op g3s5_power_seq[] = {
	/* delay 10ms as PP1800_S5 uses PP1800_S5 as alaternative supply */
	{GPIO_EN_PP5000_S5, 1, 10},

	{GPIO_EN_PP1800_S5, 1, 0},

	{GPIO_EN_PP1800_VDDIO_PMC_X, 1, 4},

	{GPIO_EN_PP0800_VDD_PMC_X, 1, 0},
	{GPIO_EN_VDD_SOC, 1, 4},

	{GPIO_EN_PP1800_VDD33_PMC_X, 1, 0},
};

/* This is the power sequence for POWER_S5S3. */
static const struct power_seq_op s5s3_power_seq[] = {
	{GPIO_EN_PP1800_S3, 1, 0},
	{GPIO_EN_PP1820A_IO_X, 1, 4},

	{GPIO_EN_PP3300_S3, 1, 0},
	{GPIO_EN_PP3300A_IO_X, 1, 4},

	{GPIO_EN_VDD_DDR, 1, 4},

	{GPIO_EN_VDD1_VDD2_VR, 1, 4}, /* LPDDR */

	{GPIO_EN_VDDQ_VR_D, 1, 4},    /* LPDDR */

	{GPIO_EN_VDD_MEDIA_ML, 1, 0},
	{GPIO_EN_VDD_GPU, 1, 0},
	{GPIO_EN_VDD_CPU, 1, 0},
};

/* The power sequence for POWER_S5G3 */
static const struct power_seq_op s5g3_power_seq[] = {
	{GPIO_EN_PP1800_VDD33_PMC_X, 0, 4},

	{GPIO_EN_VDD_SOC, 0, 0},

	{GPIO_EN_PP0800_VDD_PMC_X, 0, 4},

	{GPIO_EN_PP1800_VDDIO_PMC_X, 0, 4},

	{GPIO_EN_PP1800_S5, 0, 4},

	{GPIO_EN_PP5000_S5, 0, 4},
};

/* most recently received sleep event */
static enum host_sleep_event ap_sleep_event;
/* indicator for shutdown AP */
static char ap_shutdown;
/* indicator for boot AP from off state */
static char boot_from_off;

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

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.
	 */
	ap_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 */
	ap_shutdown = 0;
	boot_from_off = 1;
	CPRINTS("PWRON:BTN");
	chipset_exit_hard_off();
}
DECLARE_DEFERRED(chipset_exit_hard_off_button);

void chipset_reset_request_interrupt(enum gpio_signal signal)
{
	/*
	 * indicator for the following reset is a reboot or a AP requested
	 * shutdown.
	 */
	static char want_reboot;

	if (signal == GPIO_AP_EC_WARM_RST_REQ) {
		CPRINTS("AP wants reboot");
		hook_call_deferred(&reset_request_interrupt_deferred_data, 0);
		want_reboot = 1;
	} else if (signal == GPIO_AP_EC_SHUTDOWN_REQ_L) {
		/*
		 * When AP_SHUTDOWN_REQ_L is asserted, we have to check if
		 * there is a AP_EC_WARM_RST_REQ interrupt prior to this one,
		 * and that would be a reboot request, rather than a
		 * shutdown. In the meantime, the WDT should not be asserted,
		 * or this is a WDT reset, which will be handled by AP.
		 */
		if (gpio_get_level(GPIO_AP_EC_WATCHDOG_L) &&
		    !gpio_get_level(signal) && !want_reboot) {
			CPRINTS("AP wants shutdown");
			ap_shutdown = 1;
		}
		want_reboot = 0;
	}
	power_signal_interrupt(signal);
}

enum power_state power_chipset_init(void)
{
	uint32_t reset_flags = system_get_reset_flags();
	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_EC_SHUTDOWN_REQ_L);

	if (reset_flags & EC_RESET_FLAG_SYSJUMP) {
		if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
			disable_sleep(SLEEP_MASK_AP_RUN);
			CPRINTS("already in S0");
			return POWER_S0;
		}
	} else if (reset_flags & EC_RESET_FLAG_AP_OFF) {
		exit_hard_off = 0;
	} else if ((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) {
		CPRINTS("PWRON:0x%x", reset_flags);
		ap_shutdown = 0;
		boot_from_off = 1;
		/* Auto-power on */
		chipset_exit_hard_off();
	}

	/* Start from S5 if the rail is already up. */
	if (power_get_signals() & IN_PG_S5) {
		/* Force shutdown from S5 if the rails is already up. */
		if (!exit_hard_off)
			ap_shutdown = 1;
		return POWER_S5;
	}

	return POWER_G3;
}

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);
}
/**
 * Step through the power sequence table and do corresponding GPIO operations.
 *
 * @param	power_seq_ops	The pointer to the power sequence table.
 * @param	op_count	The number of entries of power_seq_ops.
 */
static void power_seq_run(const struct power_seq_op *power_seq_ops,
			  int op_count)
{
	int i;

	for (i = 0; i < op_count; i++) {
		GPIO_SET_LEVEL(power_seq_ops[i].signal,
			       power_seq_ops[i].level);
		if (!power_seq_ops[i].delay)
			continue;
		msleep(power_seq_ops[i].delay);
	}
}

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

	switch (state) {
	case POWER_G3:
		break;

	case POWER_S5:
		if (boot_from_off) {
			s5s3_retry = 1;
			return POWER_S5S3;
		}

		/*
		 * 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) || ap_shutdown)
			return POWER_S3S5;
		else if (ap_sleep_event == HOST_SLEEP_EVENT_S3_RESUME ||
			 boot_from_off)
			return POWER_S3S0;
		break;

	case POWER_S0:
		if (ap_sleep_event == HOST_SLEEP_EVENT_S3_SUSPEND ||
		    !power_has_signals(IN_ALL_S0) || ap_shutdown)
			return POWER_S0S3;
		break;

	case POWER_G3S5:
		ap_shutdown = 0;
		power_seq_run(g3s5_power_seq, ARRAY_SIZE(g3s5_power_seq));

		/* Power up to next state, or go back */
		if (power_get_signals() & IN_PG_S5)
			return POWER_S5;
		else
			return POWER_G3;
		break;

	case POWER_S5S3:
		hook_notify(HOOK_CHIPSET_PRE_INIT);

		power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq));

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

		GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1);

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

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

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

		GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0);
		power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq));

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

		/* Start shutting down */
		return POWER_S5;

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

		/*
		 * 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()) {
			ap_shutdown = 1;
			hook_call_deferred(&chipset_force_shutdown_button_data,
					   -1);
		}

		return POWER_S3;

	case POWER_S3S0:
		if (power_wait_signals(IN_ALL_S0)) {
			chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
			return POWER_S0S3;
		}
		boot_from_off = 0;

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

		/*
		 * 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_S5G3:
		power_seq_run(s5g3_power_seq, ARRAY_SIZE(s5g3_power_seq));
		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
__override void power_chipset_handle_host_sleep_event(
		enum host_sleep_event state,
		struct host_sleep_event_context *ctx)
{
	CPRINTS("Handle sleep: %d", state);

	ap_sleep_event = state;

	if (state == HOST_SLEEP_EVENT_S3_RESUME ||
	    state == HOST_SLEEP_EVENT_S3_SUSPEND)
		task_wake(TASK_ID_CHIPSET);
}
#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)) {
		CPRINTS("PWRON:LIDOPEN");
		ap_shutdown = 0;
		boot_from_off = 1;
		chipset_exit_hard_off();
	}
}
DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT);
#endif
