| /* Copyright 2017 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. |
| */ |
| |
| /* Stoney power sequencing 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 "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) |
| |
| #define IN_S5_PGOOD POWER_SIGNAL_MASK(X86_S5_PGOOD) |
| |
| static int forcing_shutdown; /* Forced shutdown in progress? */ |
| |
| void chipset_force_shutdown(enum chipset_shutdown_reason reason) |
| { |
| CPRINTS("%s()", __func__); |
| |
| if (!chipset_in_state(CHIPSET_STATE_ANY_OFF)) { |
| forcing_shutdown = 1; |
| power_button_pch_press(); |
| report_ap_reset(reason); |
| } |
| } |
| |
| static void chipset_force_g3(void) |
| { |
| /* Disable system power ("*_A" rails) in G3. */ |
| gpio_set_level(GPIO_EN_PWR_A, 0); |
| } |
| |
| void chipset_reset(enum chipset_reset_reason reason) |
| { |
| CPRINTS("%s: %d", __func__, reason); |
| |
| if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { |
| CPRINTS("Can't reset: SOC is off"); |
| return; |
| } |
| |
| report_ap_reset(reason); |
| /* |
| * Send a pulse to SYS_RST to trigger a warm reset. |
| */ |
| gpio_set_level(GPIO_SYS_RESET_L, 0); |
| usleep(32 * MSEC); |
| gpio_set_level(GPIO_SYS_RESET_L, 1); |
| } |
| |
| void chipset_throttle_cpu(int throttle) |
| { |
| CPRINTS("%s(%d)", __func__, throttle); |
| if (IS_ENABLED(CONFIG_CPU_PROCHOT_ACTIVE_LOW)) |
| throttle = !throttle; |
| |
| if (chipset_in_state(CHIPSET_STATE_ON)) |
| gpio_set_level(GPIO_CPU_PROCHOT, throttle); |
| } |
| |
| void chipset_handle_espi_reset_assert(void) |
| { |
| /* |
| * eSPI_Reset# pin being asserted without RSMRST# being asserted |
| * means there is an unexpected power loss (global reset event). |
| * In this case, check if the shutdown is forced by the EC (due |
| * to battery, thermal, or console command). The forced shutdown |
| * initiates a power button press that we need to release. |
| * |
| * NOTE: S5_PGOOD input is passed through to the RSMRST# output to |
| * the AP. |
| */ |
| if ((power_get_signals() & IN_S5_PGOOD) && forcing_shutdown) { |
| power_button_pch_release(); |
| forcing_shutdown = 0; |
| } |
| } |
| |
| 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 (gpio_get_level(GPIO_S0_PGOOD)) { |
| /* Disable idle task deep sleep when in S0. */ |
| disable_sleep(SLEEP_MASK_AP_RUN); |
| |
| CPRINTS("already in S0"); |
| return POWER_S0; |
| } |
| |
| CPRINTS("forcing G3"); |
| chipset_force_g3(); |
| } |
| return POWER_G3; |
| } |
| |
| static void handle_pass_through(enum gpio_signal pin_in, |
| enum gpio_signal pin_out) |
| { |
| /* |
| * Pass through asynchronously, as SOC may not react |
| * immediately to power changes. |
| */ |
| int in_level = gpio_get_level(pin_in); |
| int out_level = gpio_get_level(pin_out); |
| |
| /* |
| * Only pass through high S0_PGOOD (S0 power) when S5_PGOOD (S5 power) |
| * is also high (S0_PGOOD is pulled high in G3 when S5_PGOOD is low). |
| */ |
| if ((pin_in == GPIO_S0_PGOOD) && !gpio_get_level(GPIO_S5_PGOOD)) |
| in_level = 0; |
| |
| /* Nothing to do. */ |
| if (in_level == out_level) |
| return; |
| |
| /* |
| * SOC requires a delay of 1ms with stable power before |
| * asserting PWR_GOOD. |
| */ |
| if ((pin_in == GPIO_S0_PGOOD) && in_level) |
| msleep(1); |
| |
| gpio_set_level(pin_out, in_level); |
| |
| CPRINTS("Pass through %s: %d", gpio_get_name(pin_in), in_level); |
| } |
| |
| enum power_state power_handle_state(enum power_state state) |
| { |
| handle_pass_through(GPIO_S5_PGOOD, GPIO_PCH_RSMRST_L); |
| |
| handle_pass_through(GPIO_S0_PGOOD, GPIO_PCH_SYS_PWROK); |
| |
| if (state == POWER_S5 && forcing_shutdown) { |
| power_button_pch_release(); |
| forcing_shutdown = 0; |
| } |
| |
| switch (state) { |
| case POWER_G3: |
| break; |
| |
| case POWER_G3S5: |
| /* Exit SOC G3 */ |
| /* Enable system power ("*_A" rails) in S5. */ |
| gpio_set_level(GPIO_EN_PWR_A, 1); |
| |
| /* |
| * Callback to do pre-initialization within the context of |
| * chipset task. |
| */ |
| if (IS_ENABLED(CONFIG_CHIPSET_HAS_PRE_INIT_CALLBACK)) |
| chipset_pre_init_callback(); |
| |
| if (power_wait_signals(IN_S5_PGOOD)) { |
| chipset_force_g3(); |
| return POWER_G3; |
| } |
| |
| CPRINTS("Exit SOC G3"); |
| |
| return POWER_S5; |
| |
| case POWER_S5: |
| if (!power_has_signals(IN_S5_PGOOD)) { |
| /* Required rail went away */ |
| return POWER_S5G3; |
| } else if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 1) { |
| /* Power up to next state */ |
| return POWER_S5S3; |
| } |
| break; |
| |
| case POWER_S5S3: |
| if (!power_has_signals(IN_S5_PGOOD)) { |
| /* Required rail went away */ |
| return POWER_S5G3; |
| } |
| |
| /* Call hooks now that rails are up */ |
| hook_notify(HOOK_CHIPSET_STARTUP); |
| |
| return POWER_S3; |
| |
| case POWER_S3: |
| if (!power_has_signals(IN_S5_PGOOD)) { |
| /* Required rail went away */ |
| return POWER_S5G3; |
| } 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_S5_L) == 0) { |
| /* Power down to next state */ |
| return POWER_S3S5; |
| } |
| break; |
| |
| case POWER_S3S0: |
| if (!power_has_signals(IN_S5_PGOOD)) { |
| /* Required rail went away */ |
| return POWER_S5G3; |
| } |
| |
| /* Enable wireless */ |
| wireless_set_state(WIRELESS_ON); |
| |
| /* 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); |
| |
| return POWER_S0; |
| |
| case POWER_S0: |
| if (!power_has_signals(IN_S5_PGOOD)) { |
| /* Required rail went away */ |
| return POWER_S5G3; |
| } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) { |
| /* Power down to next state */ |
| return POWER_S0S3; |
| } |
| break; |
| |
| case POWER_S0S3: |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SUSPEND); |
| |
| /* 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); |
| |
| return POWER_S3; |
| |
| case POWER_S3S5: |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SHUTDOWN); |
| |
| /* Disable wireless */ |
| wireless_set_state(WIRELESS_OFF); |
| |
| return POWER_S5; |
| |
| case POWER_S5G3: |
| chipset_force_g3(); |
| |
| return POWER_G3; |
| |
| default: |
| break; |
| } |
| return state; |
| } |