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

/* X86 braswell chipset power control module for Chrome EC */

#include "chipset.h"
#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
#include "lid_switch.h"
#include "lpc.h"
#include "power.h"
#include "power_button.h"
#include "system.h"
#include "timer.h"
#include "usb_charge.h"
#include "util.h"
#include "wireless.h"
#include "registers.h"

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

/* Input state flags */
#define IN_RSMRST_L_PWRGD          POWER_SIGNAL_MASK(X86_RSMRST_L_PWRGD)
#define IN_ALL_SYS_PWRGD           POWER_SIGNAL_MASK(X86_ALL_SYS_PWRGD)
#define IN_SLP_S3_DEASSERTED       POWER_SIGNAL_MASK(X86_SLP_S3_DEASSERTED)
#define IN_SLP_S4_DEASSERTED       POWER_SIGNAL_MASK(X86_SLP_S4_DEASSERTED)

/* All always-on supplies */
#define IN_PGOOD_ALWAYS_ON   (IN_RSMRST_L_PWRGD)
/* All non-core power rails */
#define IN_PGOOD_ALL_NONCORE (IN_ALL_SYS_PWRGD)
/* All core power rails */
#define IN_PGOOD_ALL_CORE    (IN_ALL_SYS_PWRGD)
/* Rails required for S5 */
#define IN_PGOOD_S5          (IN_PGOOD_ALWAYS_ON)
/* Rails required for S3 */
#define IN_PGOOD_S3          (IN_PGOOD_ALWAYS_ON)
/* Rails required for S0 */
#define IN_PGOOD_S0          (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE)

/* All PM_SLP signals from PCH deasserted */
#define IN_ALL_PM_SLP_DEASSERTED (IN_SLP_S3_DEASSERTED | IN_SLP_S4_DEASSERTED)
/* All inputs in the right state for S0 */
#define IN_ALL_S0 (IN_PGOOD_S0 | IN_ALL_PM_SLP_DEASSERTED)

static int throttle_cpu;      /* Throttle CPU? */
static int forcing_shutdown;  /* Forced shutdown in progress? */

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.
	 */
#ifndef CONFIG_PMIC
	gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
#endif
	gpio_set_level(GPIO_PCH_RSMRST_L, 0);
	forcing_shutdown = 1;
}

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

	/*
	 * Send a reset pulse to the PCH.  This just causes it to
	 * assert INIT# to the CPU without dropping power or asserting
	 * PLTRST# to reset the rest of the system.  The PCH uses a 16
	 * ms debounce time, so assert the signal for twice that.
	 */
	gpio_set_level(GPIO_PCH_RCIN_L, 0);
	usleep(32 * MSEC);
	gpio_set_level(GPIO_PCH_RCIN_L, 1);
}

void chipset_throttle_cpu(int throttle)
{
#ifdef CONFIG_CPU_PROCHOT_ACTIVE_LOW
	throttle = !throttle;
#endif /* CONFIG_CPU_PROCHOT_ACTIVE_LOW */
	if (chipset_in_state(CHIPSET_STATE_ON))
		gpio_set_level(GPIO_CPU_PROCHOT, throttle);
}

enum power_state power_chipset_init(void)
{
	/* Pause in S5 when shutting down. */
	power_set_pause_in_s5(1);

	/*
	 * If we're switching between images without rebooting, see if the x86
	 * is already powered on; if so, leave it there instead of cycling
	 * through G3.
	 */
	if (system_jumped_to_this_image()) {
		if ((power_get_signals() & IN_PGOOD_S0) == IN_PGOOD_S0) {
			/* Disable idle task deep sleep when in S0. */
			disable_sleep(SLEEP_MASK_AP_RUN);

			CPRINTS("already in S0");
			return POWER_S0;
		} else {
			/* Force all signals to their G3 states */
			CPRINTS("forcing G3");
			gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
			gpio_set_level(GPIO_PCH_RSMRST_L, 0);

			/*wireless_set_state(WIRELESS_OFF);*/
		}
	}

	return POWER_G3;
}

enum power_state power_handle_state(enum power_state state)
{
	switch (state) {
	case POWER_G3:
		break;

	case POWER_G3S5:
		/* Exit SOC G3 */
#ifdef CONFIG_PMIC
		gpio_set_level(GPIO_PCH_SYS_PWROK, 1);
#else
		gpio_set_level(GPIO_SUSPWRDNACK_SOC_EC, 0);
#endif
		CPRINTS("Exit SOC G3");

		if (power_wait_signals(IN_PGOOD_S5)) {
			chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
			return POWER_G3;
		}

		/* Deassert RSMRST# */
		gpio_set_level(GPIO_PCH_RSMRST_L, 1);
		return POWER_S5;

	case POWER_S5:
		/* Check for SLP S4 */
		if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 1)
			return POWER_S5S3; /* Power up to next state */
		break;

	case POWER_S5S3:

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

		return POWER_S3;


	case POWER_S3:

		/* Check for state transitions */
		if (!power_has_signals(IN_PGOOD_S3)) {
			/* Required rail went away */
			chipset_force_shutdown(CHIPSET_SHUTDOWN_POWERFAIL);
			return POWER_S3S5;
		} else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) {
			/* Power up to next state */
			return POWER_S3S0;
		} else if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 0) {
			/* Power down to next state */
			return POWER_S3S5;
		}
		break;

	case POWER_S3S0:
		/* Enable wireless */

		/*wireless_set_state(WIRELESS_ON);*/

		if (!power_has_signals(IN_PGOOD_S3)) {
			chipset_force_shutdown(CHIPSET_SHUTDOWN_POWERFAIL);

		/*wireless_set_state(WIRELESS_OFF);*/
			return POWER_S3S5;
		}

		/* 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);

		/*
		 * Wait 15 ms after all voltages good.  100 ms is only needed
		 * for PCIe devices; mini-PCIe devices should need only 10 ms.
		 */
		msleep(15);

		/*
		 * Throttle CPU if necessary.  This should only be asserted
		 * when +VCCP is powered (it is by now).
		 */
#ifdef CONFIG_CPU_PROCHOT_ACTIVE_LOW
		gpio_set_level(GPIO_CPU_PROCHOT, !throttle_cpu);
#else
		gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu);
#endif /* CONFIG_CPU_PROCHOT_ACTIVE_LOW */

		/* Set SYS and CORE PWROK */
		gpio_set_level(GPIO_PCH_SYS_PWROK, 1);

		return POWER_S0;


	case POWER_S0:

		if (!power_has_signals(IN_PGOOD_ALWAYS_ON)) {
			chipset_force_shutdown(CHIPSET_SHUTDOWN_POWERFAIL);
			return POWER_S0S3;
		}

		if (!power_has_signals(IN_ALL_S0)) {
			return POWER_S0S3;
		}

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

#ifndef CONFIG_PMIC
		/* Clear SYS and CORE PWROK */
		gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
#endif
		/* Wait 40ns */
		udelay(1);

		/* Suspend wireless */

		/*wireless_set_state(WIRELESS_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);

		/*
		 * Deassert prochot since CPU is off and we're about to drop
		 * +VCCP.
		 */
		gpio_set_level(GPIO_CPU_PROCHOT, 0);

		return POWER_S3;

	case POWER_S3S5:

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

		/*wireless_set_state(WIRELESS_OFF);*/

		/* Start shutting down */
		return power_get_pause_in_s5() ? POWER_S5 : POWER_S5G3;

	case POWER_S5G3:
		/*
		 * in case shutdown is already done by apshutdown
		 * (or chipset_force_shutdown()), SOC already lost
		 * power and can't assert PMC_SUSPWRDNACK any more.
		 */
		if (forcing_shutdown) {
			/* Config pins for SOC G3 */
			gpio_config_module(MODULE_GPIO, 1);
#ifndef CONFIG_PMIC
			gpio_set_level(GPIO_SUSPWRDNACK_SOC_EC, 1);
#else
			gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
#endif

			forcing_shutdown = 0;

			CPRINTS("Enter SOC G3");

			return POWER_G3;
		}

		if (gpio_get_level(GPIO_PCH_SUSPWRDNACK) == 1) {
			/* Assert RSMRST# */
			gpio_set_level(GPIO_PCH_RSMRST_L, 0);

			/* Config pins for SOC G3 */
			gpio_config_module(MODULE_GPIO, 1);

			/* Enter SOC G3 */
#ifdef CONFIG_PMIC
			gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
			udelay(1);
			gpio_set_level(GPIO_PCH_RSMRST_L, 0);
#else
			gpio_set_level(GPIO_SUSPWRDNACK_SOC_EC, 1);
#endif
			CPRINTS("Enter SOC G3");

			return POWER_G3;
		} else {
			CPRINTS("waiting for PMC_SUSPWRDNACK to assert!");
			return POWER_S5;
		}
	}
	return state;
}
