| /* Copyright 2016 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. |
| */ |
| |
| /* Apollolake chipset power control module for Chrome EC */ |
| |
| #include "charge_state.h" |
| #include "chipset.h" |
| #include "common.h" |
| #include "console.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "power.h" |
| #include "power_button.h" |
| #include "system.h" |
| #include "task.h" |
| #include "util.h" |
| #include "wireless.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_N POWER_SIGNAL_MASK(X86_RSMRST_N) |
| #define IN_ALL_SYS_PG POWER_SIGNAL_MASK(X86_ALL_SYS_PG) |
| #define IN_SLP_S0_N POWER_SIGNAL_MASK(X86_SLP_S0_N) |
| #define IN_SLP_S3_N POWER_SIGNAL_MASK(X86_SLP_S3_N) |
| #define IN_SLP_S4_N POWER_SIGNAL_MASK(X86_SLP_S4_N) |
| #define IN_SUSPWRDNACK POWER_SIGNAL_MASK(X86_SUSPWRDNACK) |
| #define IN_SUS_STAT_N POWER_SIGNAL_MASK(X86_SUS_STAT_N) |
| |
| #ifdef CONFIG_POWER_S0IX |
| #define IN_ALL_PM_SLP_DEASSERTED (IN_SLP_S0_N | \ |
| IN_SLP_S3_N | \ |
| IN_SLP_S4_N) |
| #else |
| #define IN_ALL_PM_SLP_DEASSERTED (IN_SLP_S3_N | \ |
| IN_SLP_S4_N) |
| #endif |
| |
| #define IN_PGOOD_ALL_CORE (IN_RSMRST_N) |
| |
| #define IN_ALL_S0 (IN_PGOOD_ALL_CORE | IN_ALL_PM_SLP_DEASSERTED) |
| |
| #define CHARGER_INITIALIZED_DELAY_MS 100 |
| #define CHARGER_INITIALIZED_TRIES 40 |
| |
| static int throttle_cpu; /* Throttle CPU? */ |
| static int forcing_coldreset; /* Forced coldreset in progress? */ |
| static int power_s5_up; /* Chipset is sequencing up or down */ |
| |
| void chipset_force_shutdown(void) |
| { |
| if (!forcing_coldreset) |
| CPRINTS("%s()", __func__); |
| |
| /* |
| * Disable V5A which de-assert PMIC_EN and causes PMIC to shutdown. |
| */ |
| gpio_set_level(GPIO_V5A_EN, 0); |
| } |
| |
| void chipset_reset(int cold_reset) |
| { |
| CPRINTS("%s(%d)", __func__, cold_reset); |
| if (cold_reset) { |
| /* |
| * Perform chipset_force_shutdown and mark forcing_coldreset. |
| * Once in S5G3 state, check forcing_coldreset to power up. |
| */ |
| forcing_coldreset = 1; |
| |
| chipset_force_shutdown(); |
| } else { |
| /* |
| * Send a pulse to SOC PMU_RSTBTN_N to trigger a warm reset. |
| */ |
| 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) |
| { |
| /* |
| * 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_ALL_S0) == IN_ALL_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 */ |
| chipset_force_shutdown(); |
| } |
| } |
| |
| return POWER_G3; |
| } |
| |
| static void handle_pass_through(enum power_state state, |
| 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); |
| |
| /* Nothing to do. */ |
| if (in_level == out_level) |
| return; |
| |
| gpio_set_level(pin_out, in_level); |
| |
| CPRINTS("Pass through %s: %d", gpio_get_name(pin_in), in_level); |
| } |
| |
| #ifdef CONFIG_BOARD_HAS_RTC_RESET |
| static enum power_state power_wait_s5_rtc_reset(void) |
| { |
| static int s5_exit_tries; |
| |
| /* Wait for S5 exit and then attempt RTC reset */ |
| while ((power_get_signals() & IN_PCH_SLP_S4_DEASSERTED) == 0) { |
| /* Handle RSMRST passthru event while waiting */ |
| handle_rsmrst(POWER_S5); |
| if (task_wait_event(SECOND*4) == TASK_EVENT_TIMER) { |
| CPRINTS("timeout waiting for S5 exit"); |
| chipset_force_g3(); |
| |
| /* Assert RTCRST# and retry 5 times */ |
| board_rtc_reset(); |
| |
| if (++s5_exit_tries > 4) { |
| s5_exit_tries = 0; |
| return POWER_G3; /* Stay off */ |
| } |
| |
| udelay(10 * MSEC); |
| return POWER_G3S5; /* Power up again */ |
| } |
| } |
| |
| s5_exit_tries = 0; |
| return POWER_S5S3; /* Power up to next state */ |
| } |
| #endif |
| |
| static enum power_state _power_handle_state(enum power_state state) |
| { |
| int tries = 0; |
| |
| switch (state) { |
| case POWER_G3: |
| break; |
| |
| case POWER_S5: |
| #ifdef CONFIG_BOARD_HAS_RTC_RESET |
| /* Wait for S5 exit and attempt RTC reset it supported */ |
| if (power_s5_up) |
| return power_wait_s5_rtc_reset(); |
| #endif |
| |
| if (!power_has_signals(IN_PGOOD_ALL_CORE)) { |
| /* Required rail went away */ |
| chipset_force_shutdown(); |
| return POWER_S5G3; |
| } else if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 1) { |
| /* Power up to next state */ |
| return POWER_S5S3; |
| } |
| break; |
| |
| case POWER_S3: |
| if (!power_has_signals(IN_PGOOD_ALL_CORE)) { |
| /* 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_S0: |
| if (!power_has_signals(IN_PGOOD_ALL_CORE)) { |
| chipset_force_shutdown(); |
| return POWER_S0S3; |
| #ifdef CONFIG_POWER_S0IX |
| } else if ((gpio_get_level(GPIO_PCH_SLP_S0_L) == 0) && |
| (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1)) { |
| return POWER_S0S0ix; |
| #endif |
| } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) { |
| /* Power down to next state */ |
| return POWER_S0S3; |
| } |
| |
| break; |
| |
| #ifdef CONFIG_POWER_S0IX |
| case POWER_S0ix: |
| /* |
| * TODO: add code for unexpected power loss |
| */ |
| if ((gpio_get_level(GPIO_PCH_SLP_S0_L) == 1) && |
| (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1)) { |
| return POWER_S0ixS0; |
| } |
| |
| break; |
| #endif |
| |
| case POWER_G3S5: |
| /* Platform is powering up, clear forcing_coldreset */ |
| forcing_coldreset = 0; |
| |
| /* Call hooks to initialize PMIC */ |
| hook_notify(HOOK_CHIPSET_PRE_INIT); |
| |
| /* |
| * Allow up to 1s for charger to be initialized, in case |
| * we're trying to boot the AP with no battery. |
| */ |
| while (charge_prevent_power_on(0) && |
| tries++ < CHARGER_INITIALIZED_TRIES) { |
| msleep(CHARGER_INITIALIZED_DELAY_MS); |
| } |
| |
| /* Return to G3 if battery level is too low */ |
| if (charge_want_shutdown() || |
| tries > CHARGER_INITIALIZED_TRIES) { |
| CPRINTS("power-up inhibited"); |
| chipset_force_shutdown(); |
| return POWER_G3; |
| } |
| |
| /* Enable V5A */ |
| gpio_set_level(GPIO_V5A_EN, 1); |
| |
| if (power_wait_signals(IN_PGOOD_ALL_CORE)) { |
| chipset_force_shutdown(); |
| return POWER_G3; |
| } |
| |
| power_s5_up = 1; |
| return POWER_S5; |
| |
| case POWER_S5S3: |
| if (!power_has_signals(IN_PGOOD_ALL_CORE)) { |
| /* Required rail went away */ |
| chipset_force_shutdown(); |
| return POWER_S5G3; |
| } |
| |
| /* Call hooks now that rails are up */ |
| hook_notify(HOOK_CHIPSET_STARTUP); |
| return POWER_S3; |
| |
| case POWER_S3S0: |
| if (!power_has_signals(IN_PGOOD_ALL_CORE)) { |
| /* Required rail went away */ |
| chipset_force_shutdown(); |
| return POWER_S3S5; |
| } |
| |
| gpio_set_level(GPIO_ENABLE_BACKLIGHT, 1); |
| |
| /* 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); |
| |
| /* |
| * 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); |
| |
| return POWER_S0; |
| |
| case POWER_S0S3: |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SUSPEND); |
| |
| gpio_set_level(GPIO_ENABLE_BACKLIGHT, 0); |
| |
| /* 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; |
| |
| #ifdef CONFIG_POWER_S0IX |
| case POWER_S0S0ix: |
| /* call hooks before standby */ |
| hook_notify(HOOK_CHIPSET_SUSPEND); |
| |
| lpc_enable_wake_mask_for_lid_open(); |
| |
| /* |
| * Enable idle task deep sleep. Allow the low power idle task |
| * to go into deep sleep in S0ix. |
| */ |
| enable_sleep(SLEEP_MASK_AP_RUN); |
| |
| return POWER_S0ix; |
| |
| |
| case POWER_S0ixS0: |
| lpc_disable_wake_mask_for_lid_open(); |
| |
| /* 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; |
| #endif |
| |
| case POWER_S3S5: |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SHUTDOWN); |
| |
| /* Disable wireless */ |
| wireless_set_state(WIRELESS_OFF); |
| |
| /* Always enter into S5 state. The S5 state is required to |
| * correctly handle global resets which have a bit of delay |
| * while the SLP_Sx_L signals are asserted then deasserted. */ |
| power_s5_up = 0; |
| return POWER_S5; |
| |
| case POWER_S5G3: |
| chipset_force_shutdown(); |
| |
| /* Power up the platform again for forced cold reset */ |
| if (forcing_coldreset) { |
| forcing_coldreset = 0; |
| return POWER_G3S5; |
| } |
| |
| return POWER_G3; |
| |
| default: |
| break; |
| } |
| |
| return state; |
| } |
| |
| enum power_state power_handle_state(enum power_state state) |
| { |
| enum power_state new_state; |
| |
| /* Process RSMRST_L state changes. */ |
| handle_pass_through(state, GPIO_RSMRST_L_PGOOD, GPIO_PCH_RSMRST_L); |
| |
| /* Process ALL_SYS_PGOOD state changes. */ |
| handle_pass_through(state, GPIO_ALL_SYS_PGOOD, GPIO_PCH_SYS_PWROK); |
| |
| new_state = _power_handle_state(state); |
| |
| return new_state; |
| } |
| |
| #ifdef CONFIG_POWER_S0IX |
| static struct { |
| int required; /* indicates de-bounce required. */ |
| int done; /* debounced */ |
| } slp_s0_debounce = { |
| .required = 0, |
| .done = 1, |
| }; |
| |
| int chipset_get_ps_debounced_level(enum gpio_signal signal) |
| { |
| /* |
| * If power state is updated in power_update_signal() by any interrupts |
| * other than SLP_S0 during the 1 msec pulse(invalid SLP_S0 signal), |
| * reading SLP_S0 should be corrected with slp_s0_debounce.done flag. |
| */ |
| int level = gpio_get_level(signal); |
| |
| return (signal == GPIO_PCH_SLP_S0_L) ? |
| (level & slp_s0_debounce.done) : level; |
| } |
| |
| static void slp_s0_assertion_deferred(void) |
| { |
| int s0_level = gpio_get_level(GPIO_PCH_SLP_S0_L); |
| /* |
| (s0_level != 0) || |
| ((s0_level == 0) && (slp_s0_debounce.required == 0)) |
| */ |
| if (s0_level == slp_s0_debounce.required) { |
| if (s0_level) |
| slp_s0_debounce.done = 1; /* debounced! */ |
| |
| power_signal_interrupt(GPIO_PCH_SLP_S0_L); |
| } |
| |
| slp_s0_debounce.required = 0; |
| } |
| DECLARE_DEFERRED(slp_s0_assertion_deferred); |
| |
| void power_signal_interrupt_S0(enum gpio_signal signal) |
| { |
| if (gpio_get_level(GPIO_PCH_SLP_S0_L)) { |
| slp_s0_debounce.required = 1; |
| hook_call_deferred(slp_s0_assertion_deferred, 3 * MSEC); |
| } else if (slp_s0_debounce.required == 0) { |
| slp_s0_debounce.done = 0; |
| slp_s0_assertion_deferred(); |
| } |
| } |
| #endif |