608 lines
16 KiB
C
608 lines
16 KiB
C
/* Copyright 2013 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.
|
|
*/
|
|
|
|
/* rk3399 chipset power control module for Chrome EC */
|
|
|
|
/*
|
|
* The description of each CONFIG_CHIPSET_POWER_SEQ_VERSION:
|
|
*
|
|
* Version 0: Initial/default revision for clamshell / convertible.
|
|
* Version 1: Simplified power tree for tablet / detachable.
|
|
*/
|
|
|
|
#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 "usb_charge.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)
|
|
|
|
/* Input state flags */
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
#define IN_PGOOD_PP1250_S3 POWER_SIGNAL_MASK(PP1250_S3_PWR_GOOD)
|
|
#define IN_PGOOD_PP900_S0 POWER_SIGNAL_MASK(PP900_S0_PWR_GOOD)
|
|
#else
|
|
#define IN_PGOOD_PP5000 POWER_SIGNAL_MASK(PP5000_PWR_GOOD)
|
|
#define IN_PGOOD_SYS POWER_SIGNAL_MASK(SYS_PWR_GOOD)
|
|
#endif
|
|
|
|
#define IN_PGOOD_AP POWER_SIGNAL_MASK(AP_PWR_GOOD)
|
|
#define IN_SUSPEND_DEASSERTED POWER_SIGNAL_MASK(SUSPEND_DEASSERTED)
|
|
|
|
/* Rails requires for S3 and S0 */
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
#define IN_PGOOD_S3 (IN_PGOOD_PP1250_S3)
|
|
#define IN_PGOOD_S0 (IN_PGOOD_S3 | IN_PGOOD_PP900_S0 | IN_PGOOD_AP)
|
|
/* This board can optionally wake-on-USB in S3 */
|
|
#define S3_USB_WAKE
|
|
/* This board has non-INT power signal pins */
|
|
#define POWER_SIGNAL_POLLING
|
|
/* This board supports CR50 deep sleep mode */
|
|
#define CR50_DEEP_SLEEP
|
|
/*
|
|
* If AP_PWR_GOOD assertion does not trigger an interrupt, poll the
|
|
* signal every 5ms, up to 200 times (~ 1 second timeout).
|
|
*/
|
|
#define PGOOD_S0_POLL_TIMEOUT (5 * MSEC)
|
|
#define PGOOD_S0_POLL_TRIES 200
|
|
#else
|
|
#define IN_PGOOD_S3 (IN_PGOOD_PP5000)
|
|
#define IN_PGOOD_S0 (IN_PGOOD_S3 | IN_PGOOD_AP | IN_PGOOD_SYS)
|
|
#endif
|
|
|
|
/* All inputs in the right state for S0 */
|
|
#define IN_ALL_S0 (IN_PGOOD_S0 | IN_SUSPEND_DEASSERTED)
|
|
|
|
/* Long power key press to force shutdown in S0 */
|
|
#define FORCED_SHUTDOWN_DELAY (8 * SECOND)
|
|
|
|
#define CHARGER_INITIALIZED_DELAY_MS 100
|
|
#define CHARGER_INITIALIZED_TRIES 40
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
static const struct power_seq_op s5s3_power_seq[] = {
|
|
{ GPIO_PP900_S3_EN, 1, 2 },
|
|
{ GPIO_PP3300_S3_EN, 1, 2 },
|
|
{ GPIO_PP1800_S3_EN, 1, 2 },
|
|
{ GPIO_PP1250_S3_EN, 1, 2 },
|
|
};
|
|
#else
|
|
static const struct power_seq_op s5s3_power_seq[] = {
|
|
{ GPIO_PPVAR_LOGIC_EN, 1, 0 },
|
|
{ GPIO_PP900_AP_EN, 1, 0 },
|
|
{ GPIO_PP900_PCIE_EN, 1, 2 },
|
|
{ GPIO_PP900_PMU_EN, 1, 0 },
|
|
{ GPIO_PP900_PLL_EN, 1, 0 },
|
|
{ GPIO_PP900_USB_EN, 1, 2 },
|
|
{ GPIO_SYS_RST_L, 0, 0 },
|
|
{ GPIO_PP1800_PMU_EN_L, 0, 2 },
|
|
{ GPIO_LPDDR_PWR_EN, 1, 2 },
|
|
{ GPIO_PP1800_USB_EN_L, 0, 2 },
|
|
{ GPIO_PP3300_USB_EN_L, 0, 0 },
|
|
{ GPIO_PP5000_EN, 1, 0 },
|
|
{ GPIO_PP3300_TRACKPAD_EN_L, 0, 1 },
|
|
{ GPIO_PP1800_LID_EN_L, 0, 0 },
|
|
{ GPIO_PP1800_SIXAXIS_EN_L, 0, 2 },
|
|
{ GPIO_PP1800_SENSOR_EN_L, 0, 0 },
|
|
};
|
|
#endif
|
|
|
|
/* The power sequence for POWER_S3S0 */
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
static const struct power_seq_op s3s0_power_seq[] = {
|
|
{ GPIO_AP_CORE_EN, 1, 2 },
|
|
{ GPIO_PP1800_S0_EN, 1, 0 },
|
|
};
|
|
#else
|
|
static const struct power_seq_op s3s0_power_seq[] = {
|
|
{ GPIO_PPVAR_CLOGIC_EN, 1, 2 },
|
|
{ GPIO_PP900_DDRPLL_EN, 1, 2 },
|
|
{ GPIO_PP1800_AP_AVDD_EN_L, 0, 2 },
|
|
{ GPIO_AP_CORE_EN, 1, 2 },
|
|
{ GPIO_PP1800_S0_EN_L, 0, 2 },
|
|
{ GPIO_PP3300_S0_EN_L, 0, 0 },
|
|
};
|
|
#endif
|
|
|
|
#ifdef S3_USB_WAKE
|
|
/* Sigs that may already be on in S3, if we need to wake-on-USB */
|
|
static const struct power_seq_op s3s0_usb_wake_power_seq[] = {
|
|
{ GPIO_PP900_S0_EN, 1, 2 },
|
|
{ GPIO_PP1800_USB_EN, 1, 2 },
|
|
{ GPIO_PP3300_S0_EN, 1, 2 },
|
|
};
|
|
#endif
|
|
|
|
/* The power sequence for POWER_S0S3 */
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
static const struct power_seq_op s0s3_power_seq[] = {
|
|
{ GPIO_AP_CORE_EN, 0, 20 },
|
|
};
|
|
#else
|
|
static const struct power_seq_op s0s3_power_seq[] = {
|
|
{ GPIO_PP3300_S0_EN_L, 1, 20 },
|
|
{ GPIO_PP1800_S0_EN_L, 1, 1 },
|
|
{ GPIO_AP_CORE_EN, 0, 20 },
|
|
{ GPIO_PP1800_AP_AVDD_EN_L, 1, 1 },
|
|
{ GPIO_PP900_DDRPLL_EN, 0, 1 },
|
|
{ GPIO_PPVAR_CLOGIC_EN, 0, 0 },
|
|
};
|
|
#endif
|
|
|
|
#ifdef S3_USB_WAKE
|
|
/* Sigs that need to be left on in S3, if we need to wake-on-USB */
|
|
static const struct power_seq_op s0s3_usb_wake_power_seq[] = {
|
|
{ GPIO_PP3300_S0_EN, 0, 20 },
|
|
{ GPIO_PP1800_S0_EN, 0, 1 },
|
|
{ GPIO_PP1800_USB_EN, 0, 1 },
|
|
{ GPIO_PP900_S0_EN, 0, 0 },
|
|
};
|
|
#endif
|
|
|
|
/* The power sequence for POWER_S3S5 */
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
static const struct power_seq_op s3s5_power_seq[] = {
|
|
{ GPIO_SYS_RST_L, 0, 0 },
|
|
{ GPIO_PP1250_S3_EN, 0, 2 },
|
|
{ GPIO_PP1800_S3_EN, 0, 2 },
|
|
{ GPIO_PP3300_S3_EN, 0, 2 },
|
|
{ GPIO_PP900_S3_EN, 0, 0 },
|
|
};
|
|
#else
|
|
static const struct power_seq_op s3s5_power_seq[] = {
|
|
{ GPIO_PP1800_SENSOR_EN_L, 1, 0},
|
|
{ GPIO_PP1800_SIXAXIS_EN_L, 1, 0},
|
|
{ GPIO_PP1800_LID_EN_L, 1, 0 },
|
|
{ GPIO_PP3300_TRACKPAD_EN_L, 1, 0 },
|
|
{ GPIO_PP5000_EN, 0, 0 },
|
|
{ GPIO_PP3300_USB_EN_L, 1, 20 },
|
|
{ GPIO_PP1800_USB_EN_L, 1, 10 },
|
|
{ GPIO_LPDDR_PWR_EN, 0, 20 },
|
|
{ GPIO_PP1800_PMU_EN_L, 1, 2 },
|
|
{ GPIO_PP900_PLL_EN, 0, 0 },
|
|
{ GPIO_PP900_PMU_EN, 0, 0 },
|
|
{ GPIO_PP900_USB_EN, 0, 6 },
|
|
{ GPIO_PP900_PCIE_EN, 0, 0 },
|
|
{ GPIO_PP900_AP_EN, 0, 0 },
|
|
{ GPIO_PPVAR_LOGIC_EN, 0, 0 },
|
|
};
|
|
#endif
|
|
|
|
static int forcing_shutdown;
|
|
|
|
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);
|
|
}
|
|
|
|
#define SYS_RST_HOLD_US (1 * MSEC)
|
|
void chipset_reset(enum chipset_reset_reason reason)
|
|
{
|
|
#ifdef CONFIG_CMD_RTC
|
|
/* Print out the RTC to help correlate resets in logs. */
|
|
print_system_rtc(CC_CHIPSET);
|
|
#endif
|
|
CPRINTS("%s(%d)", __func__, reason);
|
|
report_ap_reset(reason);
|
|
|
|
/* Pulse SYS_RST */
|
|
gpio_set_level(GPIO_SYS_RST_L, 0);
|
|
if (in_interrupt_context())
|
|
udelay(SYS_RST_HOLD_US);
|
|
else
|
|
usleep(SYS_RST_HOLD_US);
|
|
gpio_set_level(GPIO_SYS_RST_L, 1);
|
|
}
|
|
|
|
enum power_state power_chipset_init(void)
|
|
{
|
|
if (system_jumped_to_this_image()) {
|
|
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 (!(system_get_reset_flags() & EC_RESET_FLAG_AP_OFF))
|
|
/* Auto-power on */
|
|
chipset_exit_hard_off();
|
|
|
|
return POWER_G3;
|
|
}
|
|
|
|
static void force_shutdown(void)
|
|
{
|
|
forcing_shutdown = 1;
|
|
task_wake(TASK_ID_CHIPSET);
|
|
}
|
|
DECLARE_DEFERRED(force_shutdown);
|
|
|
|
/*
|
|
* Debounce PGOOD_AP if we lose it suddenly during S0, since output voltage
|
|
* transitions may cause spurious pulses.
|
|
*/
|
|
#define PGOOD_AP_DEBOUNCE_TIMEOUT (100 * MSEC)
|
|
|
|
/*
|
|
* The AP informs the EC of its S0 / S3 state through IN_SUSPEND_DEASSERTED /
|
|
* AP_EC_S3_S0_L. Latency between deassertion and power rails coming up must
|
|
* be minimized, so check for deassertion at various stages of our suspend
|
|
* power sequencing, and immediately transition out of suspend if necessary.
|
|
*/
|
|
#define SLEEP_INTERVAL_MS 5
|
|
#define MSLEEP_CHECK_ABORTED_SUSPEND(msec) \
|
|
do { \
|
|
int sleep_remain = msec; \
|
|
do { \
|
|
msleep(MIN(sleep_remain, SLEEP_INTERVAL_MS)); \
|
|
sleep_remain -= SLEEP_INTERVAL_MS; \
|
|
if (!forcing_shutdown && \
|
|
power_get_signals() & IN_SUSPEND_DEASSERTED) { \
|
|
CPRINTS("suspend aborted"); \
|
|
return POWER_S3S0; \
|
|
} \
|
|
} while (sleep_remain > 0); \
|
|
} while (0)
|
|
BUILD_ASSERT(POWER_S3S0 != 0);
|
|
|
|
/**
|
|
* 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.
|
|
* @return non-zero if suspend aborted during POWER_S0S3, 0 otherwise.
|
|
*/
|
|
static int 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;
|
|
if ((power_seq_ops == s0s3_power_seq)
|
|
#ifdef S3_USB_WAKE
|
|
|| (power_seq_ops == s0s3_usb_wake_power_seq)
|
|
#endif
|
|
)
|
|
MSLEEP_CHECK_ABORTED_SUSPEND(power_seq_ops[i].delay);
|
|
else
|
|
msleep(power_seq_ops[i].delay);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
enum power_state power_handle_state(enum power_state state)
|
|
{
|
|
#ifndef CR50_DEEP_SLEEP
|
|
static int sys_reset_asserted;
|
|
#endif
|
|
#ifdef S3_USB_WAKE
|
|
static int usb_wake_enabled;
|
|
#endif
|
|
int tries = 0;
|
|
|
|
switch (state) {
|
|
case POWER_G3:
|
|
break;
|
|
|
|
case POWER_S5:
|
|
if (forcing_shutdown)
|
|
return POWER_S5G3;
|
|
else
|
|
return POWER_S5S3;
|
|
break;
|
|
|
|
case POWER_S3:
|
|
if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown)
|
|
return POWER_S3S5;
|
|
else if (power_get_signals() & IN_SUSPEND_DEASSERTED)
|
|
return POWER_S3S0;
|
|
break;
|
|
|
|
case POWER_S0:
|
|
if (!power_has_signals(IN_PGOOD_S3) ||
|
|
forcing_shutdown ||
|
|
!(power_get_signals() & IN_SUSPEND_DEASSERTED))
|
|
return POWER_S0S3;
|
|
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION != 1
|
|
/*
|
|
* Wait up to PGOOD_AP_DEBOUNCE_TIMEOUT for IN_PGOOD_AP to
|
|
* come back before transitioning back to S3. PGOOD_SYS can
|
|
* also glitch, with a glitch duration < 1ms, so debounce
|
|
* it here as well.
|
|
*/
|
|
if (power_wait_signals_timeout(IN_PGOOD_AP | IN_PGOOD_SYS,
|
|
PGOOD_AP_DEBOUNCE_TIMEOUT)
|
|
== EC_ERROR_TIMEOUT)
|
|
return POWER_S0S3;
|
|
|
|
/*
|
|
* power_wait_signals_timeout() can block and consume task
|
|
* wake events, so re-verify the state of the world.
|
|
*/
|
|
if (!power_has_signals(IN_PGOOD_S3) ||
|
|
forcing_shutdown ||
|
|
!(power_get_signals() & IN_SUSPEND_DEASSERTED))
|
|
return POWER_S0S3;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case POWER_G3S5:
|
|
forcing_shutdown = 0;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* Power up to next state */
|
|
return POWER_S5;
|
|
|
|
case POWER_S5S3:
|
|
power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq));
|
|
|
|
#ifndef CR50_DEEP_SLEEP
|
|
/*
|
|
* Assert SYS_RST now, to be released in S3S0, to avoid
|
|
* resetting the TPM soon after power-on.
|
|
*/
|
|
sys_reset_asserted = 1;
|
|
#endif
|
|
|
|
if (power_wait_signals(IN_PGOOD_S3)) {
|
|
chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
|
|
return POWER_S3S5;
|
|
}
|
|
|
|
/* Call hooks now that rails are up */
|
|
hook_notify(HOOK_CHIPSET_STARTUP);
|
|
|
|
/* Power up to next state */
|
|
return POWER_S3;
|
|
|
|
case POWER_S3S0:
|
|
#ifdef S3_USB_WAKE
|
|
/* Bring up S3 USB wake rails, if they are down */
|
|
if (!usb_wake_enabled)
|
|
power_seq_run(s3s0_usb_wake_power_seq,
|
|
ARRAY_SIZE(s3s0_usb_wake_power_seq));
|
|
usb_wake_enabled = 0;
|
|
#endif
|
|
power_seq_run(s3s0_power_seq, ARRAY_SIZE(s3s0_power_seq));
|
|
|
|
#ifndef CR50_DEEP_SLEEP
|
|
/* Release SYS_RST if we came from S5 */
|
|
if (sys_reset_asserted) {
|
|
#endif
|
|
msleep(10);
|
|
gpio_set_level(GPIO_SYS_RST_L, 1);
|
|
|
|
#ifndef CR50_DEEP_SLEEP
|
|
sys_reset_asserted = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef POWER_SIGNAL_POLLING
|
|
/*
|
|
* Poll power signals every PGOOD_S0_POLL_TIMEOUT us, since
|
|
* AP_PWR_GOOD assertion doesn't trigger a power signal
|
|
* interrupt.
|
|
*/
|
|
while (power_wait_signals_timeout(IN_PGOOD_S0,
|
|
PGOOD_S0_POLL_TIMEOUT) == EC_ERROR_TIMEOUT &&
|
|
++tries < PGOOD_S0_POLL_TRIES)
|
|
;
|
|
|
|
if (tries >= PGOOD_S0_POLL_TRIES) {
|
|
CPRINTS("power timeout on input; "
|
|
"wanted 0x%04x, got 0x%04x",
|
|
IN_PGOOD_S0, power_get_signals() & IN_PGOOD_S0);
|
|
#else
|
|
if (power_wait_signals(IN_PGOOD_S0)) {
|
|
#endif /* POWER_SIGNAL_POLLING */
|
|
chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT);
|
|
return POWER_S0S3;
|
|
}
|
|
|
|
/* 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_S0S3:
|
|
/* Call hooks before we remove power rails */
|
|
hook_notify(HOOK_CHIPSET_SUSPEND);
|
|
MSLEEP_CHECK_ABORTED_SUSPEND(20);
|
|
|
|
if (power_seq_run(s0s3_power_seq, ARRAY_SIZE(s0s3_power_seq)))
|
|
return POWER_S3S0;
|
|
|
|
#ifdef S3_USB_WAKE
|
|
/* Leave up rails needed for S3 USB wake, if requested */
|
|
usb_wake_enabled = (power_get_host_sleep_state() ==
|
|
HOST_SLEEP_EVENT_S3_WAKEABLE_SUSPEND);
|
|
if (!usb_wake_enabled &&
|
|
power_seq_run(s0s3_usb_wake_power_seq,
|
|
ARRAY_SIZE(s0s3_usb_wake_power_seq)))
|
|
return POWER_S3S0;
|
|
#endif
|
|
|
|
/*
|
|
* 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(&force_shutdown_data, -1);
|
|
}
|
|
|
|
return POWER_S3;
|
|
|
|
case POWER_S3S5:
|
|
#ifdef S3_USB_WAKE
|
|
/* Make sure all S3 rails are off */
|
|
if (usb_wake_enabled) {
|
|
power_seq_run(s0s3_usb_wake_power_seq,
|
|
ARRAY_SIZE(s0s3_usb_wake_power_seq));
|
|
usb_wake_enabled = 0;
|
|
}
|
|
#endif
|
|
|
|
/* Call hooks before we remove power rails */
|
|
hook_notify(HOOK_CHIPSET_SHUTDOWN);
|
|
|
|
power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq));
|
|
|
|
/* Start shutting down */
|
|
return POWER_S5;
|
|
|
|
case POWER_S5G3:
|
|
return POWER_G3;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
static void power_button_changed(void)
|
|
{
|
|
static uint8_t tablet_boot_on_button_release;
|
|
|
|
if (power_button_is_pressed()) {
|
|
if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION != 1
|
|
/* Power up from off */
|
|
chipset_exit_hard_off();
|
|
#else
|
|
tablet_boot_on_button_release = 1;
|
|
#endif
|
|
}
|
|
/* Delayed power down from S0/S3, cancel on PB release */
|
|
hook_call_deferred(&force_shutdown_data,
|
|
FORCED_SHUTDOWN_DELAY);
|
|
} else {
|
|
#if CONFIG_CHIPSET_POWER_SEQ_VERSION == 1
|
|
if (tablet_boot_on_button_release) {
|
|
/* Power up from off */
|
|
chipset_exit_hard_off();
|
|
tablet_boot_on_button_release = 0;
|
|
}
|
|
#endif
|
|
/* Power button released, cancel deferred shutdown */
|
|
hook_call_deferred(&force_shutdown_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
|
|
|
|
#ifdef POWER_SIGNAL_POLLING
|
|
/*
|
|
* Polling for non-INT power signal pins.
|
|
* Call power_signal_interrupt() when the GPIO status of those pins changes.
|
|
*/
|
|
static void power_signal_changed(void)
|
|
{
|
|
static uint8_t in_signals; /* Current power signal status */
|
|
uint8_t inew = 0;
|
|
const struct power_signal_info *s = power_signal_list;
|
|
int i;
|
|
|
|
BUILD_ASSERT(POWER_SIGNAL_COUNT <= 8);
|
|
|
|
for (i = 0; i < POWER_SIGNAL_COUNT; i++, s++) {
|
|
/* Skip if this is an INT pin. */
|
|
if (s->gpio < GPIO_IH_COUNT)
|
|
continue;
|
|
|
|
if (power_signal_is_asserted(s))
|
|
inew |= 1 << i;
|
|
}
|
|
|
|
if (inew != in_signals) {
|
|
/*
|
|
* Pass a fake power gpio_signal to power_signal_interrupt().
|
|
* Note that here we make power_signal_interrupt() reentrant.
|
|
*/
|
|
power_signal_interrupt(POWER_SIGNAL_COUNT);
|
|
in_signals = inew;
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_TICK, power_signal_changed, HOOK_PRIO_DEFAULT);
|
|
#endif
|