| /* Copyright 2015 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. |
| */ |
| |
| /* Skylake IMVP8 / ROP PMIC chipset power control module for Chrome EC */ |
| |
| #include "chipset.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "intel_x86.h" |
| #include "lpc.h" |
| #include "power_button.h" |
| #include "skylake.h" |
| #include "system.h" |
| #include "timer.h" |
| |
| /* Console output macros */ |
| #define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args) |
| |
| static int forcing_shutdown; /* Forced shutdown in progress? */ |
| |
| |
| void chipset_force_shutdown(void) |
| { |
| CPRINTS("%s()", __func__); |
| |
| /* |
| * Force off. Sending a reset command to the PMIC will power off |
| * the EC, so simulate a long power button press instead. This |
| * condition will reset once the state machine transitions to G3. |
| * Consider reducing the latency here by changing the power off |
| * hold time on the PMIC. |
| */ |
| if (!chipset_in_state(CHIPSET_STATE_HARD_OFF)) { |
| forcing_shutdown = 1; |
| power_button_pch_press(); |
| } |
| } |
| |
| __attribute__((weak)) void chipset_set_pmic_slp_sus_l(int level) |
| { |
| gpio_set_level(GPIO_PMIC_SLP_SUS_L, level); |
| } |
| |
| enum power_state chipset_force_g3(void) |
| { |
| CPRINTS("Forcing fake G3."); |
| |
| chipset_set_pmic_slp_sus_l(0); |
| |
| return POWER_G3; |
| } |
| |
| void chipset_reset(int cold_reset) |
| { |
| CPRINTS("%s(%d)", __func__, cold_reset); |
| |
| if (cold_reset) { |
| if (gpio_get_level(GPIO_SYS_RESET_L) == 0) |
| return; |
| gpio_set_level(GPIO_SYS_RESET_L, 0); |
| /* Debounce time for SYS_RESET_L is 16 ms */ |
| udelay(20 * MSEC); |
| gpio_set_level(GPIO_SYS_RESET_L, 1); |
| } else { |
| /* |
| * Send a RCIN_PCH_RCIN_L |
| * assert INIT# to the CPU without dropping power or asserting |
| * PLTRST# to reset the rest of the system. |
| */ |
| |
| /* Pulse must be at least 16 PCI clocks long = 500 ns */ |
| #ifdef CONFIG_ESPI_VW_SIGNALS |
| lpc_host_reset(); |
| #else |
| gpio_set_level(GPIO_PCH_RCIN_L, 0); |
| udelay(10); |
| gpio_set_level(GPIO_PCH_RCIN_L, 1); |
| #endif |
| } |
| } |
| |
| static void handle_slp_sus(enum power_state state) |
| { |
| /* If we're down or going down don't do anythin with SLP_SUS_L. */ |
| if (state == POWER_G3 || state == POWER_S5G3) |
| return; |
| |
| /* Always mimic PCH SLP_SUS request for all other states. */ |
| chipset_set_pmic_slp_sus_l(gpio_get_level(GPIO_PCH_SLP_SUS_L)); |
| } |
| |
| void chipset_handle_espi_reset_assert(void) |
| { |
| /* |
| * If eSPI_Reset# pin is asserted without SLP_SUS# being asserted, then |
| * it means that there is an unexpected power loss (global reset |
| * event). In this case, check if shutdown was being forced by pressing |
| * power button. If yes, release power button. |
| */ |
| if ((power_get_signals() & IN_PCH_SLP_SUS_DEASSERTED) && |
| forcing_shutdown) { |
| power_button_pch_release(); |
| forcing_shutdown = 0; |
| } |
| } |
| |
| enum power_state power_handle_state(enum power_state state) |
| { |
| enum power_state new_state; |
| |
| /* Process RSMRST_L state changes. */ |
| common_intel_x86_handle_rsmrst(state); |
| |
| if (state == POWER_S5 && forcing_shutdown) { |
| power_button_pch_release(); |
| forcing_shutdown = 0; |
| } |
| |
| new_state = common_intel_x86_power_handle_state(state); |
| |
| /* Process SLP_SUS_L state changes after a new state is decided. */ |
| handle_slp_sus(new_state); |
| |
| return new_state; |
| } |
| |
| /* Workaround for flags getting lost with power cycle */ |
| __attribute__((weak)) int board_has_working_reset_flags(void) |
| { |
| return 1; |
| } |
| |
| #ifdef CONFIG_CHIPSET_HAS_PLATFORM_PMIC_RESET |
| static void chipset_handle_reboot(void) |
| { |
| int flags; |
| |
| if (system_jumped_to_this_image()) |
| return; |
| |
| /* Interrogate current reset flags from previous reboot. */ |
| flags = system_get_reset_flags(); |
| |
| /* |
| * Do not make PMIC re-sequence the power rails if the following reset |
| * conditions are not met. |
| */ |
| if (!(flags & |
| (RESET_FLAG_WATCHDOG | RESET_FLAG_SOFT | RESET_FLAG_HARD))) |
| return; |
| |
| /* Preserve AP off request. */ |
| if (flags & RESET_FLAG_AP_OFF) { |
| /* Do not issue PMIC reset if board cannot save reset flags */ |
| if (!board_has_working_reset_flags()) { |
| ccprintf("Skip PMIC reset due to board issue.\n"); |
| cflush(); |
| return; |
| } |
| chip_save_reset_flags(RESET_FLAG_AP_OFF); |
| } |
| |
| ccprintf("Restarting system with PMIC.\n"); |
| /* Flush console */ |
| cflush(); |
| |
| /* Bring down all rails but RTC rail (including EC power). */ |
| gpio_set_level(GPIO_EC_PLATFORM_RST, 1); |
| while (1) |
| ; /* wait here */ |
| } |
| DECLARE_HOOK(HOOK_INIT, chipset_handle_reboot, HOOK_PRIO_FIRST); |
| #endif |