| /* Copyright 2018 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* mt8183 chipset power control module for Chrome EC */ |
| |
| #include "builtin/assert.h" |
| #include "charge_state.h" |
| #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 "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| /* |
| * mt8183 has two different power sequence versions |
| * 0: for normal tablet and detachable form factor |
| * 1: for boards have GPIO_EN_PP1800_S5_L |
| * CONFIG_CHIPSET_POWER_SEQ_VERSION defaults to 0, re-define the power seq |
| * version if needed. |
| */ |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_CHIPSET, outstr) |
| #define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ##args) |
| |
| /* Input state flags */ |
| #define IN_PGOOD_PMIC POWER_SIGNAL_MASK(PMIC_PWR_GOOD) |
| #define IN_SUSPEND_ASSERTED POWER_SIGNAL_MASK(AP_IN_S3_L) |
| |
| /* Rails required for S3 and S0 */ |
| #define IN_PGOOD_S0 (IN_PGOOD_PMIC) |
| #define IN_PGOOD_S3 (IN_PGOOD_PMIC) |
| |
| /* All inputs in the right state for S0 */ |
| #define IN_ALL_S0 (IN_PGOOD_S0 & ~IN_SUSPEND_ASSERTED) |
| |
| /* Long power key press to force shutdown in S0. go/crosdebug */ |
| #ifdef VARIANT_KUKUI_JACUZZI |
| #define FORCED_SHUTDOWN_DELAY (8 * SECOND) |
| #else |
| #define FORCED_SHUTDOWN_DELAY (10 * SECOND) |
| #endif |
| |
| /* Long power key press to boot from S5/G3 state. */ |
| #ifndef POWERBTN_BOOT_DELAY |
| #define POWERBTN_BOOT_DELAY (1 * SECOND) |
| #endif |
| |
| #define CHARGER_INITIALIZED_DELAY_MS 100 |
| #define CHARGER_INITIALIZED_TRIES 40 |
| |
| #define PMIC_EN_PULSE_MS 50 |
| |
| /* Maximum time it should for PMIC to turn on after toggling PMIC_EN_ODL. */ |
| #define PMIC_EN_TIMEOUT (300 * MSEC) |
| |
| /* |
| * Amount of time we need to hold PMIC_FORCE_RESET_ODL to ensure PMIC is really |
| * off and will not restart on its own. |
| */ |
| #define PMIC_FORCE_RESET_TIME (10 * SECOND) |
| |
| /* Time delay in G3 to deassert EN_PP1800_S5_L */ |
| #define EN_PP1800_S5_L_DEASSERT_TIME (20 * MSEC) |
| |
| /* Data structure for a GPIO operation for power sequencing */ |
| struct power_seq_op { |
| /* enum gpio_signal in 8 bits */ |
| uint8_t signal; |
| uint8_t level; |
| /* Number of milliseconds to delay after setting signal to level */ |
| uint8_t delay; |
| }; |
| BUILD_ASSERT(GPIO_COUNT < 256); |
| |
| /* |
| * This is the power sequence for POWER_S5S3. |
| * The entries in the table are handled sequentially from the top |
| * to the bottom. |
| */ |
| |
| static const struct power_seq_op s5s3_power_seq[] = { |
| /* Release PMIC watchdog. */ |
| { GPIO_PMIC_WATCHDOG_L, 1, 0 }, |
| /* Turn on AP. */ |
| { GPIO_AP_SYS_RST_L, 1, 2 }, |
| }; |
| |
| /* The power sequence for POWER_S3S0 */ |
| static const struct power_seq_op s3s0_power_seq[] = {}; |
| |
| /* The power sequence for POWER_S0S3 */ |
| static const struct power_seq_op s0s3_power_seq[] = {}; |
| |
| /* The power sequence for POWER_S3S5 */ |
| static const struct power_seq_op s3s5_power_seq[] = { |
| /* Turn off AP. */ |
| { GPIO_AP_SYS_RST_L, 0, 0 }, |
| /* Assert watchdog to PMIC (there may be a 1.6ms debounce) */ |
| { GPIO_PMIC_WATCHDOG_L, 0, 3 }, |
| }; |
| |
| static int forcing_shutdown; |
| static int boot_from_cutoff; |
| |
| void chipset_reset_request_interrupt(enum gpio_signal signal) |
| { |
| chipset_reset(CHIPSET_RESET_AP_REQ); |
| } |
| |
| /* |
| * Triggers on falling edge of AP watchdog line only. The falling edge can |
| * happen in these 3 cases: |
| * - AP asserts watchdog while the AP is on: this is a real AP-initiated reset. |
| * - EC asserted GPIO_AP_SYS_RST_L, so the AP is in reset and AP watchdog falls |
| * as well. This is _not_ a watchdog reset. We mask these cases by disabling |
| * the interrupt just before shutting down the AP, and re-enabling it just |
| * after starting the AP. |
| * - PMIC has shut down (e.g. the AP powered off by itself), this is not a |
| * watchdog reset either. This should be covered by the case above if the |
| * EC reacts quickly enough, but we mask those cases as well by testing if |
| * the PMIC is still on when the watchdog line falls. |
| */ |
| void chipset_watchdog_interrupt(enum gpio_signal signal) |
| { |
| if (power_get_signals() & IN_PGOOD_PMIC) |
| chipset_reset(CHIPSET_RESET_AP_WATCHDOG); |
| } |
| |
| 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. |
| */ |
| forcing_shutdown = 1; |
| task_wake(TASK_ID_CHIPSET); |
| } |
| |
| void chipset_force_shutdown_button(void) |
| { |
| chipset_force_shutdown(CHIPSET_SHUTDOWN_BUTTON); |
| } |
| DECLARE_DEFERRED(chipset_force_shutdown_button); |
| |
| void chipset_exit_hard_off_button(void) |
| { |
| /* Power up from off */ |
| forcing_shutdown = 0; |
| chipset_exit_hard_off(); |
| } |
| DECLARE_DEFERRED(chipset_exit_hard_off_button); |
| |
| #ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE |
| static void power_reset_host_sleep_state(void) |
| { |
| power_set_host_sleep_state(HOST_SLEEP_EVENT_DEFAULT_RESET); |
| sleep_reset_tracking(); |
| power_chipset_handle_host_sleep_event(HOST_SLEEP_EVENT_DEFAULT_RESET, |
| NULL); |
| } |
| |
| static void handle_chipset_reset(void) |
| { |
| if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { |
| CPRINTS("Chipset reset: exit s3"); |
| power_reset_host_sleep_state(); |
| task_wake(TASK_ID_CHIPSET); |
| } |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_RESET, handle_chipset_reset, HOOK_PRIO_FIRST); |
| #endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */ |
| |
| /* If chipset needs to be reset, EC also reboots to RO. */ |
| void chipset_reset(enum chipset_shutdown_reason reason) |
| { |
| int flags = SYSTEM_RESET_HARD; |
| |
| CPRINTS("%s: %d", __func__, reason); |
| report_ap_reset(reason); |
| |
| cflush(); |
| if (reason == CHIPSET_RESET_AP_WATCHDOG) |
| flags |= SYSTEM_RESET_AP_WATCHDOG; |
| |
| system_reset(flags); |
| |
| /* This should not be reachable. */ |
| while (1) |
| ; |
| } |
| |
| enum power_state power_chipset_init(void) |
| { |
| /* Enable reboot / sleep control inputs from AP */ |
| gpio_enable_interrupt(GPIO_WARM_RESET_REQ); |
| gpio_enable_interrupt(GPIO_AP_IN_SLEEP_L); |
| |
| if (system_jumped_to_this_image()) { |
| if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) { |
| disable_sleep(SLEEP_MASK_AP_RUN); |
| gpio_enable_interrupt(GPIO_AP_EC_WATCHDOG_L); |
| CPRINTS("already in S0"); |
| return POWER_S0; |
| } |
| } else if (system_get_reset_flags() & EC_RESET_FLAG_AP_OFF) { |
| /* Force shutdown from S5 if the PMIC is already up. */ |
| if (power_get_signals() & IN_PGOOD_PMIC) { |
| forcing_shutdown = 1; |
| return POWER_S5; |
| } |
| } else { |
| /* Auto-power on */ |
| chipset_exit_hard_off(); |
| |
| if (system_get_reset_flags() == EC_RESET_FLAG_RESET_PIN) |
| boot_from_cutoff = 1; |
| } |
| |
| /* Start from S5 if the PMIC is already up. */ |
| if (power_get_signals() & IN_PGOOD_PMIC) |
| return POWER_S5; |
| |
| return POWER_G3; |
| } |
| |
| /* |
| * If we have to force reset the PMIC, we only need to do so for a few seconds, |
| * then we need to release the GPIO to prevent leakage in G3. |
| */ |
| static void release_pmic_force_reset(void) |
| { |
| CPRINTS("Releasing PMIC force reset"); |
| gpio_set_level(GPIO_PMIC_FORCE_RESET_ODL, 1); |
| } |
| DECLARE_DEFERRED(release_pmic_force_reset); |
| |
| /** |
| * Step through the power sequence table and do corresponding GPIO operations. |
| * |
| * @param power_seq_ops The pointer to the power sequence table. |
| * @param op_count The number of entries of power_seq_ops. |
| */ |
| static void power_seq_run(const struct power_seq_op *power_seq_ops, |
| int op_count) |
| { |
| int i; |
| |
| for (i = 0; i < op_count; i++) { |
| gpio_set_level(power_seq_ops[i].signal, power_seq_ops[i].level); |
| if (!power_seq_ops[i].delay) |
| continue; |
| msleep(power_seq_ops[i].delay); |
| } |
| } |
| |
| #if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1 |
| static void deassert_en_pp1800_s5_l(void) |
| { |
| gpio_set_level(GPIO_EN_PP1800_S5_L, 1); |
| } |
| DECLARE_DEFERRED(deassert_en_pp1800_s5_l); |
| #endif |
| |
| enum power_state power_handle_state(enum power_state state) |
| { |
| /* |
| * Set if we already had a rising edge on AP_SYS_RST_L. If so, any |
| * subsequent boot attempt will require an EC reset. |
| */ |
| static int booted; |
| |
| /* Retry S5->S3 transition, if not zero. */ |
| static int s5s3_retry; |
| |
| /* |
| * PMIC power went away (AP most likely decided to shut down): |
| * transition to S5, G3. |
| */ |
| static int ap_shutdown; |
| uint16_t tries = 0; |
| |
| switch (state) { |
| case POWER_G3: |
| |
| #if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1 |
| hook_call_deferred(&deassert_en_pp1800_s5_l_data, |
| EN_PP1800_S5_L_DEASSERT_TIME); |
| #endif |
| |
| /* Go back to S5->G3 if the PMIC unexpectedly starts again. */ |
| if (power_get_signals() & IN_PGOOD_PMIC) |
| return POWER_S5G3; |
| break; |
| |
| case POWER_S5: |
| boot_from_cutoff = 0; |
| |
| /* |
| * If AP initiated shutdown, PMIC is off, and we can transition |
| * to G3 immediately. |
| */ |
| if (ap_shutdown) { |
| ap_shutdown = 0; |
| return POWER_S5G3; |
| } else if (!forcing_shutdown) { |
| /* Powering up. */ |
| s5s3_retry = 1; |
| return POWER_S5S3; |
| } |
| |
| /* Forcing shutdown */ |
| |
| /* Long press has worked, transition to G3. */ |
| if (!(power_get_signals() & IN_PGOOD_PMIC)) |
| return POWER_S5G3; |
| |
| /* |
| * Try to force PMIC shutdown with a long press. This takes 8s, |
| * shorter than the common code S5->G3 timeout (10s). |
| */ |
| CPRINTS("Forcing shutdown with long press."); |
| gpio_set_level(GPIO_PMIC_EN_ODL, 0); |
| |
| /* |
| * Stay in S5, common code will drop to G3 after timeout |
| * if the long press does not work. |
| */ |
| return POWER_S5; |
| |
| case POWER_S3: |
| if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown) |
| return POWER_S3S5; |
| else if (!(power_get_signals() & IN_SUSPEND_ASSERTED)) |
| return POWER_S3S0; |
| break; |
| |
| case POWER_S0: |
| if (!power_has_signals(IN_PGOOD_S0) || forcing_shutdown || |
| power_get_signals() & IN_SUSPEND_ASSERTED) |
| return POWER_S0S3; |
| |
| break; |
| |
| case POWER_G3S5: |
| forcing_shutdown = 0; |
| |
| #ifdef CONFIG_BATTERY_SMART |
| /* |
| * b:148045048: With the adapter to activate the smart battery |
| * which is shutdown mode, will enable PMIC during activation |
| * and have heavy loading, which will prevent the system from |
| * powering on. Delay to boot system until the smart battry |
| * is ready. |
| */ |
| if (battery_hw_present() && boot_from_cutoff) { |
| static int total_sleep_ms; |
| |
| if (total_sleep_ms < 4000) { |
| msleep(10); |
| total_sleep_ms += 10; |
| return POWER_G3S5; |
| } |
| } |
| #endif |
| |
| /* |
| * Allow time 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( |
| CHIPSET_SHUTDOWN_BATTERY_INHIBIT); |
| return POWER_G3; |
| } |
| |
| #if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1 |
| hook_call_deferred(&deassert_en_pp1800_s5_l_data, -1); |
| #endif |
| hook_call_deferred(&release_pmic_force_reset_data, -1); |
| gpio_set_level(GPIO_PMIC_FORCE_RESET_ODL, 1); |
| #if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1 |
| gpio_set_level(GPIO_EN_PP1800_S5_L, 0); |
| #endif |
| |
| /* Power up to next state */ |
| return POWER_S5; |
| |
| case POWER_S5S3: |
| hook_notify(HOOK_CHIPSET_PRE_INIT); |
| |
| /* |
| * Release power button in case it was pressed by force shutdown |
| * sequence. |
| */ |
| gpio_set_level(GPIO_PMIC_EN_ODL, 1); |
| |
| /* If PMIC is off, switch it on by pulsing PMIC enable. */ |
| if (!(power_get_signals() & IN_PGOOD_PMIC)) { |
| msleep(PMIC_EN_PULSE_MS); |
| gpio_set_level(GPIO_PMIC_EN_ODL, 0); |
| msleep(PMIC_EN_PULSE_MS); |
| gpio_set_level(GPIO_PMIC_EN_ODL, 1); |
| } |
| |
| /* If EC jumped, or has already booted once, reboot to RO. */ |
| if (system_jumped_to_this_image() || booted) { |
| /* |
| * TODO(b:109850749): How quickly does the EC come back |
| * up? Would IN_PGOOD_PMIC be ready by the time we are |
| * back? According to PMIC spec, it should take ~158 ms |
| * after debounce (32 ms), minus PMIC_EN_PULSE_MS above. |
| * It would be good to avoid another _EN pulse above. |
| */ |
| chipset_reset(CHIPSET_RESET_AP_REQ); |
| } |
| |
| /* |
| * Wait for PMIC to bring up rails. Retry if it fails |
| * (it may take 2 attempts on restart after we use |
| * force reset). |
| */ |
| if (power_wait_signals_timeout(IN_PGOOD_PMIC, |
| PMIC_EN_TIMEOUT)) { |
| if (s5s3_retry) { |
| s5s3_retry = 0; |
| return POWER_S5S3; |
| } |
| /* Give up, go back to G3. */ |
| return POWER_S5G3; |
| } |
| |
| booted = 1; |
| /* Enable S3 power supplies, release AP reset. */ |
| power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq)); |
| gpio_enable_interrupt(GPIO_AP_EC_WATCHDOG_L); |
| |
| /* Call hooks now that rails are up */ |
| hook_notify(HOOK_CHIPSET_STARTUP); |
| |
| /* |
| * Clearing the sleep failure detection tracking on the path |
| * to S0 to handle any reset conditions. |
| */ |
| #ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION |
| power_reset_host_sleep_state(); |
| #endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ |
| |
| /* Power up to next state */ |
| return POWER_S3; |
| |
| case POWER_S3S0: |
| power_seq_run(s3s0_power_seq, ARRAY_SIZE(s3s0_power_seq)); |
| |
| if (power_wait_signals(IN_PGOOD_S0)) { |
| chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT); |
| return POWER_S0S3; |
| } |
| |
| /* Call hooks now that rails are up */ |
| hook_notify(HOOK_CHIPSET_RESUME); |
| |
| #ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION |
| sleep_resume_transition(); |
| #endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ |
| |
| /* |
| * 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); |
| |
| /* Power up to next state */ |
| return POWER_S0; |
| |
| case POWER_S0S3: |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SUSPEND); |
| |
| #ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION |
| sleep_suspend_transition(); |
| #endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ |
| |
| /* |
| * TODO(b:109850749): Check if we need some delay here to |
| * "debounce" entering suspend (rk3399 uses 20ms delay). |
| */ |
| |
| power_seq_run(s0s3_power_seq, ARRAY_SIZE(s0s3_power_seq)); |
| |
| /* |
| * 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); |
| |
| /* |
| * In case the power button is held awaiting power-off timeout, |
| * power off immediately now that we're entering S3. |
| */ |
| if (power_button_is_pressed()) { |
| forcing_shutdown = 1; |
| hook_call_deferred(&chipset_force_shutdown_button_data, |
| -1); |
| } |
| |
| return POWER_S3; |
| |
| case POWER_S3S5: |
| /* PMIC has shutdown, transition to G3. */ |
| if (!(power_get_signals() & IN_PGOOD_PMIC)) |
| ap_shutdown = 1; |
| |
| /* Call hooks before we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SHUTDOWN); |
| |
| gpio_disable_interrupt(GPIO_AP_EC_WATCHDOG_L); |
| power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq)); |
| |
| /* Call hooks after we remove power rails */ |
| hook_notify(HOOK_CHIPSET_SHUTDOWN_COMPLETE); |
| |
| /* Start shutting down */ |
| return POWER_S5; |
| |
| case POWER_S5G3: |
| /* Release the power button, in case it was long pressed. */ |
| if (forcing_shutdown) |
| gpio_set_level(GPIO_PMIC_EN_ODL, 1); |
| |
| /* |
| * If PMIC is still not off, assert PMIC_FORCE_RESET_ODL. |
| * This should only happen for forced shutdown where the AP is |
| * not able to send a command to the PMIC, and where the long |
| * power+home press did not work (if the PMIC is misconfigured). |
| * Also, PMIC will lose RTC state, in that case. |
| */ |
| if (power_get_signals() & IN_PGOOD_PMIC) { |
| CPRINTS("Forcing PMIC off"); |
| gpio_set_level(GPIO_PMIC_FORCE_RESET_ODL, 0); |
| msleep(5); |
| hook_call_deferred(&release_pmic_force_reset_data, |
| PMIC_FORCE_RESET_TIME); |
| |
| return POWER_S5G3; |
| } |
| |
| return POWER_G3; |
| |
| default: |
| CPRINTS("Unexpected power state %d", state); |
| ASSERT(0); |
| break; |
| } |
| |
| return state; |
| } |
| |
| #ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE |
| __override void power_chipset_handle_sleep_hang(enum sleep_hang_type hang_type) |
| { |
| CPRINTS("Warning: Detected sleep hang! Waking host up!"); |
| host_set_single_event(EC_HOST_EVENT_HANG_DETECT); |
| } |
| |
| __override void |
| power_chipset_handle_host_sleep_event(enum host_sleep_event state, |
| struct host_sleep_event_context *ctx) |
| { |
| CPRINTS("Handle sleep: %d", state); |
| |
| if (state == HOST_SLEEP_EVENT_S3_SUSPEND) { |
| /* |
| * Indicate to power state machine that a new host event for |
| * S3 suspend has been received and so chipset suspend |
| * notification needs to be sent to listeners. |
| */ |
| sleep_set_notify(SLEEP_NOTIFY_SUSPEND); |
| sleep_start_suspend(ctx); |
| |
| } else if (state == HOST_SLEEP_EVENT_S3_RESUME) { |
| /* |
| * Wake up chipset task and indicate to power state machine that |
| * listeners need to be notified of chipset resume. |
| */ |
| sleep_set_notify(SLEEP_NOTIFY_RESUME); |
| task_wake(TASK_ID_CHIPSET); |
| sleep_complete_resume(ctx); |
| } |
| } |
| #endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */ |
| |
| static void power_button_changed(void) |
| { |
| if (power_button_is_pressed()) { |
| if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) |
| hook_call_deferred(&chipset_exit_hard_off_button_data, |
| POWERBTN_BOOT_DELAY); |
| |
| /* Delayed power down from S0/S3, cancel on PB release */ |
| hook_call_deferred(&chipset_force_shutdown_button_data, |
| FORCED_SHUTDOWN_DELAY); |
| } else { |
| /* Power button released, cancel deferred shutdown/boot */ |
| hook_call_deferred(&chipset_exit_hard_off_button_data, -1); |
| hook_call_deferred(&chipset_force_shutdown_button_data, -1); |
| } |
| } |
| DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT); |
| |
| #ifdef CONFIG_LID_SWITCH |
| static void lid_changed(void) |
| { |
| /* Power-up from off on lid open */ |
| if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF)) |
| chipset_exit_hard_off(); |
| } |
| DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT); |
| #endif |