/* Copyright (c) 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(void) { CPRINTS("%s()", __func__); /* * 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(int cold_reset) { CPRINTS("%s(%d)", __func__, cold_reset); if (cold_reset) { /* * Drop and restore PWROK. This causes the PCH to reboot, * regardless of its after-G3 setting. This type of reboot * causes the PCH to assert PLTRST#, SLP_S3#, and SLP_S5#, so * we actually drop power to the rest of the system (hence, a * "cold" reboot). */ /* Ignore if PWROK is already low */ if (gpio_get_level(GPIO_PCH_SYS_PWROK) == 0) return; /* PWROK must deassert for at least 3 RTC clocks = 91 us */ gpio_set_level(GPIO_PCH_SYS_PWROK, 0); udelay(100); gpio_set_level(GPIO_PCH_SYS_PWROK, 1); } else { /* * 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) { 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(); 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(); 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(); /*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). */ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu); /* 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(); 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; }