blob: 04c428f5be274d230aad3742470d290207bd761c [file] [log] [blame]
/* Copyright 2021 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* FalconLite 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"
#ifdef CONFIG_BRINGUP
#define GPIO_SET_LEVEL(signal, value) \
gpio_set_level_verbose(CC_CHIPSET, signal, value)
#else
#define GPIO_SET_LEVEL(signal, value) gpio_set_level(signal, value)
#endif
/* Console output macros */
#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ##args)
/* Long power key press to force shutdown in S0. go/crosdebug */
#define FORCED_SHUTDOWN_DELAY (8 * SECOND)
/* Long power key press to boot from S5/G3 state. */
#define POWERBTN_BOOT_DELAY (10 * MSEC)
#define SYS_RST_PULSE_LENGTH (30 * MSEC)
/* Masks for power signals */
#define IN_PG_S5 POWER_SIGNAL_MASK(FCL_PG_S5)
#define IN_PGOOD \
(POWER_SIGNAL_MASK(FCL_PG_VDD1_VDD2) | \
POWER_SIGNAL_MASK(FCL_PG_VDD_MEDIA_ML) | \
POWER_SIGNAL_MASK(FCL_PG_VDD_SOC) | \
POWER_SIGNAL_MASK(FCL_PG_VDD_DDR_OD) | POWER_SIGNAL_MASK(FCL_PG_S5))
#define IN_ALL_S0 IN_PGOOD
#define IN_ALL_S3 IN_PGOOD
/* Power signal list. Must match order of enum power_signal. */
const struct power_signal_info power_signal_list[] = {
[FCL_AP_WARM_RST_REQ] = { GPIO_AP_EC_WARM_RST_REQ,
POWER_SIGNAL_ACTIVE_HIGH, "AP_WARM_RST_REQ" },
[FCL_AP_SHUTDOWN_REQ] = { GPIO_AP_EC_SHUTDOWN_REQ_L,
POWER_SIGNAL_ACTIVE_LOW, "AP_SHUTDOWN_REQ" },
[FCL_AP_WATCHDOG] = { GPIO_AP_EC_WATCHDOG_L, POWER_SIGNAL_ACTIVE_LOW,
"AP_WDT" },
[FCL_PG_S5] = { GPIO_PG_S5_PWR_OD, POWER_SIGNAL_ACTIVE_HIGH, "PG_S5" },
[FCL_PG_VDD1_VDD2] = { GPIO_PG_VDD1_VDD2_OD, POWER_SIGNAL_ACTIVE_HIGH,
"PG_VDD1_VDD2" },
[FCL_PG_VDD_MEDIA_ML] = { GPIO_PG_VDD_MEDIA_ML_OD,
POWER_SIGNAL_ACTIVE_HIGH, "PG_VDD_MEDIA_ML" },
[FCL_PG_VDD_SOC] = { GPIO_PG_VDD_SOC_OD, POWER_SIGNAL_ACTIVE_HIGH,
"PG_VDD_SOC" },
[FCL_PG_VDD_DDR_OD] = { GPIO_PG_VDD_DDR_OD, POWER_SIGNAL_ACTIVE_HIGH,
"PG_VDD_DDR" },
};
/* Data structure for a GPIO operation for power sequencing */
struct power_seq_op {
enum gpio_signal signal;
uint8_t level;
/* Number of milliseconds to delay after setting signal to level */
uint32_t delay;
};
/*
* The entries in the table are handled sequentially from the top
* to the bottom.
*/
/* The power sequence for POWER_S3S5 */
static const struct power_seq_op s3s5_power_seq[] = {
{ GPIO_EN_VDD_CPU, 0, 0 }, { GPIO_EN_VDD_GPU, 0, 0 },
{ GPIO_EN_VDD_MEDIA_ML, 0, 4 },
{ GPIO_EN_VDDQ_VR_D, 0, 4 }, /* LPDDR */
{ GPIO_EN_VDD1_VDD2_VR, 0, 4 }, /* LPDDR */
{ GPIO_EN_VDD_DDR, 0, 4 },
{ GPIO_EN_PP3300A_IO_X, 0, 0 }, { GPIO_EN_PP3300_S3, 0, 4 },
{ GPIO_EN_PP1820A_IO_X, 0, 0 }, { GPIO_EN_PP1800_S3, 0, 0 },
};
/* The power sequence for POWER_G3S5 */
static const struct power_seq_op g3s5_power_seq[] = {
/* delay 10ms as PP1800_S5 uses PP1800_S5 as alaternative supply */
{ GPIO_EN_PP5000_S5, 1, 10 },
{ GPIO_EN_PP1800_S5, 1, 0 },
{ GPIO_EN_PP1800_VDDIO_PMC_X, 1, 4 },
{ GPIO_EN_PP0800_VDD_PMC_X, 1, 0 }, { GPIO_EN_VDD_SOC, 1, 4 },
{ GPIO_EN_PP1800_VDD33_PMC_X, 1, 0 },
};
/* This is the power sequence for POWER_S5S3. */
static const struct power_seq_op s5s3_power_seq[] = {
{ GPIO_EN_PP1800_S3, 1, 0 }, { GPIO_EN_PP1820A_IO_X, 1, 4 },
{ GPIO_EN_PP3300_S3, 1, 0 }, { GPIO_EN_PP3300A_IO_X, 1, 4 },
{ GPIO_EN_VDD_DDR, 1, 4 },
{ GPIO_EN_VDD1_VDD2_VR, 1, 4 }, /* LPDDR */
{ GPIO_EN_VDDQ_VR_D, 1, 4 }, /* LPDDR */
{ GPIO_EN_VDD_MEDIA_ML, 1, 0 }, { GPIO_EN_VDD_GPU, 1, 0 },
{ GPIO_EN_VDD_CPU, 1, 0 },
};
/* The power sequence for POWER_S5G3 */
static const struct power_seq_op s5g3_power_seq[] = {
{ GPIO_EN_PP1800_VDD33_PMC_X, 0, 4 },
{ GPIO_EN_VDD_SOC, 0, 0 },
{ GPIO_EN_PP0800_VDD_PMC_X, 0, 4 },
{ GPIO_EN_PP1800_VDDIO_PMC_X, 0, 4 },
{ GPIO_EN_PP1800_S5, 0, 4 },
{ GPIO_EN_PP5000_S5, 0, 4 },
};
/* most recently received sleep event */
static enum host_sleep_event ap_sleep_event;
/* indicator for shutdown AP */
static char ap_shutdown;
/* indicator for boot AP from off state */
static char boot_from_off;
static void reset_request_interrupt_deferred(void)
{
chipset_reset(CHIPSET_RESET_AP_REQ);
}
DECLARE_DEFERRED(reset_request_interrupt_deferred);
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.
*/
ap_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 */
ap_shutdown = 0;
boot_from_off = 1;
CPRINTS("PWRON:BTN");
chipset_exit_hard_off();
}
DECLARE_DEFERRED(chipset_exit_hard_off_button);
void chipset_reset_request_interrupt(enum gpio_signal signal)
{
/*
* indicator for the following reset is a reboot or a AP requested
* shutdown.
*/
static char want_reboot;
if (signal == GPIO_AP_EC_WARM_RST_REQ) {
CPRINTS("AP wants reboot");
hook_call_deferred(&reset_request_interrupt_deferred_data, 0);
want_reboot = 1;
} else if (signal == GPIO_AP_EC_SHUTDOWN_REQ_L) {
/*
* When AP_SHUTDOWN_REQ_L is asserted, we have to check if
* there is a AP_EC_WARM_RST_REQ interrupt prior to this one,
* and that would be a reboot request, rather than a
* shutdown. In the meantime, the WDT should not be asserted,
* or this is a WDT reset, which will be handled by AP.
*/
if (gpio_get_level(GPIO_AP_EC_WATCHDOG_L) &&
!gpio_get_level(signal) && !want_reboot) {
CPRINTS("AP wants shutdown");
ap_shutdown = 1;
}
want_reboot = 0;
}
power_signal_interrupt(signal);
}
enum power_state power_chipset_init(void)
{
uint32_t reset_flags = system_get_reset_flags();
int exit_hard_off = 1;
/* Enable reboot / sleep control inputs from AP */
gpio_enable_interrupt(GPIO_AP_EC_WARM_RST_REQ);
gpio_enable_interrupt(GPIO_AP_EC_SHUTDOWN_REQ_L);
if (reset_flags & EC_RESET_FLAG_SYSJUMP) {
if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
disable_sleep(SLEEP_MASK_AP_RUN);
CPRINTS("already in S0");
return POWER_S0;
}
} else if (reset_flags & EC_RESET_FLAG_AP_OFF) {
exit_hard_off = 0;
} else if ((reset_flags & EC_RESET_FLAG_HIBERNATE) &&
gpio_get_level(GPIO_AC_PRESENT)) {
/*
* If AC present, assume this is a wake-up by AC insert.
* Boot EC only.
*
* Note that extpower module is not initialized at this point,
* the only way is to ask GPIO_AC_PRESENT directly.
*/
exit_hard_off = 0;
}
if (battery_is_present() == BP_YES)
/*
* (crosbug.com/p/28289): Wait battery stable.
* Some batteries use clock stretching feature, which requires
* more time to be stable.
*/
battery_wait_for_stable();
if (exit_hard_off) {
CPRINTS("PWRON:0x%x", reset_flags);
ap_shutdown = 0;
boot_from_off = 1;
/* Auto-power on */
chipset_exit_hard_off();
}
/* Start from S5 if the rail is already up. */
if (power_get_signals() & IN_PG_S5) {
/* Force shutdown from S5 if the rails is already up. */
if (!exit_hard_off)
ap_shutdown = 1;
return POWER_S5;
}
return POWER_G3;
}
void chipset_reset(enum chipset_shutdown_reason reason)
{
CPRINTS("%s: %d", __func__, reason);
report_ap_reset(reason);
GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0);
usleep(SYS_RST_PULSE_LENGTH);
GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1);
}
/**
* 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);
}
}
enum power_state power_handle_state(enum power_state state)
{
/* Retry S5->S3 transition, if not zero. */
static int s5s3_retry;
switch (state) {
case POWER_G3:
break;
case POWER_S5:
if (boot_from_off) {
s5s3_retry = 1;
return POWER_S5S3;
}
/*
* 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) || ap_shutdown)
return POWER_S3S5;
else if (ap_sleep_event == HOST_SLEEP_EVENT_S3_RESUME ||
boot_from_off)
return POWER_S3S0;
break;
case POWER_S0:
if (ap_sleep_event == HOST_SLEEP_EVENT_S3_SUSPEND ||
!power_has_signals(IN_ALL_S0) || ap_shutdown)
return POWER_S0S3;
break;
case POWER_G3S5:
ap_shutdown = 0;
power_seq_run(g3s5_power_seq, ARRAY_SIZE(g3s5_power_seq));
/* Power up to next state, or go back */
if (power_get_signals() & IN_PG_S5)
return POWER_S5;
else
return POWER_G3;
break;
case POWER_S5S3:
hook_notify(HOOK_CHIPSET_PRE_INIT);
power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq));
/*
* Wait for rails up. Retry if it fails
* (it may take 2 attempts on restart after we use
* force reset).
*/
if (!power_has_signals(IN_ALL_S3)) {
if (s5s3_retry) {
s5s3_retry = 0;
return POWER_S5S3;
}
boot_from_off = 0;
/* Give up, go back to G3. */
return POWER_S5G3;
}
GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1);
/* Call hooks now that rails are up */
hook_notify(HOOK_CHIPSET_STARTUP);
/* Power up to next state */
return POWER_S3;
case POWER_S3S5:
/* Call hooks before we remove power rails */
hook_notify(HOOK_CHIPSET_SHUTDOWN);
GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0);
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_S0S3:
/* Call hooks before we remove power rails */
hook_notify(HOOK_CHIPSET_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);
/*
* 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()) {
ap_shutdown = 1;
hook_call_deferred(&chipset_force_shutdown_button_data,
-1);
}
return POWER_S3;
case POWER_S3S0:
if (power_wait_signals(IN_ALL_S0)) {
chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
return POWER_S0S3;
}
boot_from_off = 0;
/* 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);
/* Power up to next state */
return POWER_S0;
case POWER_S5G3:
power_seq_run(s5g3_power_seq, ARRAY_SIZE(s5g3_power_seq));
return POWER_G3;
default:
CPRINTS("Unexpected power state %d", state);
ASSERT(0);
break;
}
return 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_POWER_TRACK_HOST_SLEEP_STATE
__override void
power_chipset_handle_host_sleep_event(enum host_sleep_event state,
struct host_sleep_event_context *ctx)
{
CPRINTS("Handle sleep: %d", state);
ap_sleep_event = state;
if (state == HOST_SLEEP_EVENT_S3_RESUME ||
state == HOST_SLEEP_EVENT_S3_SUSPEND)
task_wake(TASK_ID_CHIPSET);
}
#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */
#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)) {
CPRINTS("PWRON:LIDOPEN");
ap_shutdown = 0;
boot_from_off = 1;
chipset_exit_hard_off();
}
}
DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT);
#endif