From 30a33e6b0412c132c375cc569f9629da6eb168f3 Mon Sep 17 00:00:00 2001 From: Randall Spangler Date: Wed, 9 May 2012 15:34:50 -0700 Subject: Drop DPWROK when system is off for more than 10 sec This saves ~70mw of power. To make this work, I also had to stretch the power button signal to give the system a chance to come back up when the user taps the power button. Signed-off-by: Randall Spangler BUG=chrome-os-partner:9574 TEST=manual For each of the following tests, wait ~15 sec after the system is powered off to give it a chance to drop DPWROK. 1) tap power button -> system turns on 2) hold power button 1 sec -> system turns on 3) open lid -> system turns on 4) silego reset (power+refresh, or power+esc on proto1) -> system stays off 5) silego recovery (power+esc+refresh) -> system turns on 6) hold down power button and type 'reboot' on EC console -> system turns on 7) type 'powerbtn' on EC console -> system turns on Change-Id: I781cf3e665104192521b7fb9ff75a3c3e7f43464 --- board/bds/board.c | 7 +- chip/lm4/power_button.c | 60 ++++++++++++----- common/gaia_power.c | 30 +++++---- common/usb_charge.c | 2 +- common/x86_power.c | 173 +++++++++++++++++++++++++++++++++--------------- include/chipset.h | 23 +++++-- 6 files changed, 201 insertions(+), 94 deletions(-) diff --git a/board/bds/board.c b/board/bds/board.c index 777e84c3e5..89bd337af3 100644 --- a/board/bds/board.c +++ b/board/bds/board.c @@ -86,12 +86,17 @@ const struct gpio_info gpio_list[GPIO_COUNT] = { /* BDS system is only half-wired to an x86 chipset, so it can't tell what state * the chipset is in. Rather than scatter ifdef's everywhere, put a mock * chipset interface here. */ -int chipset_in_state(enum chipset_state in_state) +int chipset_in_state(int state_mask) { return 1; /* Sure, I'm in whatever state you want. */ } +void chipset_exit_hard_off(void) +{ +} + + void configure_board(void) { } diff --git a/chip/lm4/power_button.c b/chip/lm4/power_button.c index b2841c77b1..836a663403 100644 --- a/chip/lm4/power_button.c +++ b/chip/lm4/power_button.c @@ -42,13 +42,14 @@ #define PWRBTN_DEBOUNCE_US 30000 /* Debounce time for power button */ #define PWRBTN_DELAY_T0 32000 /* 32ms (PCH requires >16ms) */ #define PWRBTN_DELAY_T1 (4000000 - PWRBTN_DELAY_T0) /* 4 secs - t0 */ -#define PWRBTN_RECOVERY_US 200000 /* Length of time to simulate power button - * press when booting into - * keyboard-controlled recovery mode */ +#define PWRBTN_INITIAL_US 200000 /* Length of time to stretch initial power + * button press to give chipset a chance to + * wake up (~100ms) and react to the press + * (~16ms). Also used as pulse length for + * simulated power button presses when the + * system is off. */ #define LID_DEBOUNCE_US 30000 /* Debounce time for lid switch */ -#define LID_PWRBTN_US PWRBTN_DELAY_T0 /* Length of time to simulate power - * button press on lid open */ enum power_button_state { PWRBTN_STATE_STOPPED = 0, @@ -59,6 +60,7 @@ enum power_button_state { PWRBTN_STATE_STOPPING, PWRBTN_STATE_BOOT_RESET, PWRBTN_STATE_BOOT_RECOVERY, + PWRBTN_STATE_WAS_OFF, }; static enum power_button_state pwrbtn_state = PWRBTN_STATE_STOPPED; @@ -146,10 +148,16 @@ static void state_machine(uint64_t tnow) switch (pwrbtn_state) { case PWRBTN_STATE_START: - if (chipset_in_state(CHIPSET_STATE_SOFT_OFF)) { - /* Chipset is off, so just pass the true power button - * state to the chipset. */ - pwrbtn_state = PWRBTN_STATE_HELD_DOWN; + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { + /* Chipset is off, so wake the chipset and send it a + * long enough pulse to wake up. After that we'll + * reflect the true power button state. If we don't + * stretch the pulse here, the user may release the + * power button before the chipset finishes waking from + * hard off state. */ + chipset_exit_hard_off(); + tnext_state = tnow + PWRBTN_INITIAL_US; + pwrbtn_state = PWRBTN_STATE_WAS_OFF; } else { /* Chipset is on, so send the chipset a pulse */ tnext_state = tnow + PWRBTN_DELAY_T0; @@ -166,10 +174,10 @@ static void state_machine(uint64_t tnow) /* If the chipset is already off, don't tell it the power * button is down; it'll just cause the chipset to turn on * again. */ - if (!chipset_in_state(CHIPSET_STATE_SOFT_OFF)) - set_pwrbtn_to_pch(0); - else + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) CPRINTF("[%T PB chipset already off]\n"); + else + set_pwrbtn_to_pch(0); pwrbtn_state = PWRBTN_STATE_HELD_DOWN; break; case PWRBTN_STATE_STOPPING: @@ -180,6 +188,17 @@ static void state_machine(uint64_t tnow) set_pwrbtn_to_pch(1); pwrbtn_state = PWRBTN_STATE_BOOT_RESET; break; + case PWRBTN_STATE_WAS_OFF: + if (get_power_button_pressed()) { + /* User is still holding the power button */ + pwrbtn_state = PWRBTN_STATE_HELD_DOWN; + } else { + /* Stop stretching the power button press */ + *memmap_switches &= ~EC_LPC_SWITCH_POWER_BUTTON_PRESSED; + keyboard_set_power_button(0); + pwrbtn_state = PWRBTN_STATE_STOPPING; + } + break; case PWRBTN_STATE_STOPPED: case PWRBTN_STATE_HELD_DOWN: case PWRBTN_STATE_BOOT_RESET: @@ -195,6 +214,9 @@ static void power_button_changed(uint64_t tnow) if (pwrbtn_state == PWRBTN_STATE_BOOT_RECOVERY) { /* Ignore all power button changes during the recovery pulse */ CPRINTF("[%T PB changed during recovery pulse]\n"); + } else if (pwrbtn_state == PWRBTN_STATE_WAS_OFF) { + /* Ignore all power button changes during an initial pulse */ + CPRINTF("[%T PB changed during initial pulse]\n"); } else if (get_power_button_pressed()) { /* Power button pressed */ CPRINTF("[%T PB pressed]\n"); @@ -233,12 +255,13 @@ static void lid_switch_open(uint64_t tnow) lpc_set_host_events(EC_LPC_HOST_EVENT_MASK( EC_LPC_HOST_EVENT_LID_OPEN)); - /* If the chipset is is soft-off, send a power button pulse to - * wake up the chipset. */ - if (chipset_in_state(CHIPSET_STATE_SOFT_OFF)) { + /* If the chipset is off, send a power button pulse to wake up the + * chipset. */ + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { + chipset_exit_hard_off(); set_pwrbtn_to_pch(0); pwrbtn_state = PWRBTN_STATE_STOPPING; - tnext_state = tnow + LID_PWRBTN_US; + tnext_state = tnow + PWRBTN_INITIAL_US; task_wake(TASK_ID_POWERBTN); } } @@ -317,7 +340,7 @@ static int power_button_init(void) * the PCH so it powers on. */ set_pwrbtn_to_pch(0); pwrbtn_state = PWRBTN_STATE_BOOT_RECOVERY; - tnext_state = get_time().val + PWRBTN_RECOVERY_US; + tnext_state = get_time().val + PWRBTN_INITIAL_US; } else { /* Keyboard-controlled reset, so don't let the PCH see * that the power button was pressed. Otherwise, it @@ -395,7 +418,7 @@ void power_button_task(void) static int command_powerbtn(int argc, char **argv) { - int ms = 100; /* Press duration in ms */ + int ms = PWRBTN_INITIAL_US / 1000; /* Press duration in ms */ char *e; if (argc > 1) { @@ -411,6 +434,7 @@ static int command_powerbtn(int argc, char **argv) * PCH. It does not simulate the full state machine which sends SMIs * and other events to other parts of the EC and chipset. */ ccprintf("Simulating %d ms power button press.\n", ms); + chipset_exit_hard_off(); set_pwrbtn_to_pch(0); usleep(ms * 1000); set_pwrbtn_to_pch(1); diff --git a/common/gaia_power.c b/common/gaia_power.c index 1683ecb006..58fa0e68cf 100644 --- a/common/gaia_power.c +++ b/common/gaia_power.c @@ -136,24 +136,28 @@ int gaia_power_init(void) /*****************************************************************************/ /* Chipset interface */ -/* Returns non-zero if the chipset is in the specified state. */ -int chipset_in_state(enum chipset_state in_state) +int chipset_in_state(int state_mask) { - switch (in_state) { - case CHIPSET_STATE_SOFT_OFF: - return ap_on == 0; - case CHIPSET_STATE_SUSPEND: - /* TODO: implement */ - return 0; - case CHIPSET_STATE_ON: - return ap_on; - } + /* If AP is off, match any off state for now */ + if ((state_mask & CHIPSET_STATE_ANY_OFF) && !ap_on) + return 1; + + /* If AP is on, match on state */ + if ((state_mask & CHIPSET_STATE_ON) && ap_on) + return 1; - /* Should never get here since we list all states above, but compiler - * doesn't seem to understand that. */ + /* TODO: detect suspend state */ + + /* In any other case, we don't have a match */ return 0; } + +void chipset_exit_hard_off(void) +{ + /* TODO: implement, if/when we take the AP down to a hard-off state */ +} + /*****************************************************************************/ void gaia_power_task(void) diff --git a/common/usb_charge.c b/common/usb_charge.c index eea5d3ec72..282c8ddbcb 100644 --- a/common/usb_charge.c +++ b/common/usb_charge.c @@ -139,7 +139,7 @@ DECLARE_CONSOLE_COMMAND(usbchargemode, command_set_mode); static int usb_charge_init(void) { - if (chipset_in_state(CHIPSET_STATE_SOFT_OFF)) + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) usb_charge_all_ports_off(); else usb_charge_all_ports_on(); diff --git a/common/x86_power.c b/common/x86_power.c index 50478c8bd0..135009cb71 100644 --- a/common/x86_power.c +++ b/common/x86_power.c @@ -24,6 +24,9 @@ * transition, just jump to the next state. */ #define DEFAULT_TIMEOUT 1000000 +/* Timeout for dropping back from S5 to G3 */ +#define S5_INACTIVITY_TIMEOUT 10000000 + enum x86_state { X86_G3 = 0, /* System is off (not technically all the * way into G3, which means totally @@ -38,6 +41,7 @@ enum x86_state { X86_S3S0, /* S3 -> S0 */ X86_S0S3, /* S0 -> S3 */ X86_S3S5, /* S3 -> S5 */ + X86_S5G3, /* S5 -> G3 */ }; static const char * const state_names[] = { @@ -50,6 +54,7 @@ static const char * const state_names[] = { "S3->S0", "S0->S3", "S3->S5", + "S5->G3", }; /* Input state flags */ @@ -87,7 +92,7 @@ static const char * const state_names[] = { static enum x86_state state; /* Current state */ static uint32_t in_signals; /* Current input signal states (IN_PGOOD_*) */ static uint32_t in_want; /* Input signal state we're waiting for */ - +static int want_g3_exit; /* Should we exit the G3 state? */ /* Update input signal state */ static void update_in_signals(void) @@ -210,22 +215,56 @@ void x86_power_reset(int cold_reset) /*****************************************************************************/ /* Chipset interface */ -/* Returns non-zero if the chipset is in the specified state. */ -/* TODO: change in_state to bitmask so multiple states can be checked */ -int chipset_in_state(enum chipset_state in_state) +int chipset_in_state(int state_mask) { - switch (in_state) { - case CHIPSET_STATE_SOFT_OFF: - return (state == X86_S5); - case CHIPSET_STATE_SUSPEND: - return (state == X86_S3); - case CHIPSET_STATE_ON: - return (state == X86_S0); + int need_mask = 0; + + /* TODO: what to do about state transitions? If the caller wants + * HARD_OFF|SOFT_OFF and we're in G3S5, we could still return + * non-zero. */ + switch (state) { + case X86_G3: + need_mask = CHIPSET_STATE_HARD_OFF; + break; + case X86_G3S5: + case X86_S5G3: + /* In between hard and soft off states. Match only if caller + * will accept both. */ + need_mask = CHIPSET_STATE_HARD_OFF | CHIPSET_STATE_SOFT_OFF; + break; + case X86_S5: + need_mask = CHIPSET_STATE_SOFT_OFF; + break; + case X86_S5S3: + case X86_S3S5: + need_mask = CHIPSET_STATE_SOFT_OFF | CHIPSET_STATE_SUSPEND; + break; + case X86_S3: + need_mask = CHIPSET_STATE_SUSPEND; + break; + case X86_S3S0: + case X86_S0S3: + need_mask = CHIPSET_STATE_SUSPEND | CHIPSET_STATE_ON; + break; + case X86_S0: + need_mask = CHIPSET_STATE_ON; + break; } - /* Should never get here since we list all states above, but compiler - * doesn't seem to understand that. */ - return 0; + /* Return non-zero if all needed bits are present */ + return (state_mask & need_mask) == need_mask; +} + + +void chipset_exit_hard_off(void) +{ + /* If not in the hard-off state nor headed there, nothing to do */ + if (state != X86_G3 && state != X86_S5G3) + return; + + /* Set a flag to leave G3, then wake the task */ + want_g3_exit = 1; + task_wake(TASK_ID_X86POWER); } /*****************************************************************************/ @@ -245,8 +284,10 @@ void x86_power_interrupt(enum gpio_signal signal) static int x86_power_init(void) { - /* Default to G3 state unless proven otherwise */ - state = X86_G3; + /* Default to moving towards S5 state unless proven otherwise. This + * supports booting the main processor during the boot process. We'll + * drop back to G3 if we stay inactive in S5.*/ + state = X86_G3S5; /* Update input state */ update_in_signals(); @@ -309,8 +350,60 @@ void x86_power_task(void) switch (state) { case X86_G3: - /* Move to S5 state on boot */ - state = X86_G3S5; + if (want_g3_exit) { + want_g3_exit = 0; + state = X86_G3S5; + break; + } + + /* Steady state; wait for a message */ + in_want = 0; + task_wait_event(-1); + break; + + case X86_S5: + if (gpio_get_level(GPIO_PCH_SLP_S5n) == 1) { + /* Power up to next state */ + state = X86_S5S3; + break; + } + + /* Wait for inactivity timeout */ + in_want = 0; + if (task_wait_event(S5_INACTIVITY_TIMEOUT) == + TASK_EVENT_TIMER) { + /* Drop to G3; wake not requested yet */ + want_g3_exit = 0; + state = X86_S5G3; + } + break; + + case X86_S3: + if (gpio_get_level(GPIO_PCH_SLP_S3n) == 1) { + /* Power up to next state */ + state = X86_S3S0; + break; + } else if (gpio_get_level(GPIO_PCH_SLP_S5n) == 0) { + /* Power down to next state */ + state = X86_S3S5; + break; + } + + /* Otherwise, steady state; wait for a message */ + in_want = 0; + task_wait_event(-1); + break; + + case X86_S0: + if (gpio_get_level(GPIO_PCH_SLP_S3n) == 0) { + /* Power down to next state */ + state = X86_S0S3; + break; + } + + /* Otherwise, steady state; wait for a message */ + in_want = 0; + task_wait_event(-1); break; case X86_G3S5: @@ -419,50 +512,22 @@ void x86_power_task(void) state = X86_S5; break; - case X86_S5: - if (gpio_get_level(GPIO_PCH_SLP_S5n) == 1) { - /* Power up to next state */ - state = X86_S5S3; - break; - } - - /* Otherwise, steady state; wait for a message */ - in_want = 0; - task_wait_event(-1); - break; + case X86_S5G3: + /* Deassert DPWROK, assert RSMRST# */ + gpio_set_level(GPIO_PCH_DPWROK, 0); + gpio_set_level(GPIO_PCH_RSMRSTn, 0); - case X86_S3: - if (gpio_get_level(GPIO_PCH_SLP_S3n) == 1) { - /* Power up to next state */ - state = X86_S3S0; - break; - } else if (gpio_get_level(GPIO_PCH_SLP_S5n) == 0) { - /* Power down to next state */ - state = X86_S3S5; - break; - } + /* Switch off +5V always-on */ + gpio_set_level(GPIO_ENABLE_5VALW, 0); - /* Otherwise, steady state; wait for a message */ - in_want = 0; - task_wait_event(-1); + state = X86_G3; break; - - case X86_S0: - if (gpio_get_level(GPIO_PCH_SLP_S3n) == 0) { - /* Power down to next state */ - state = X86_S0S3; - break; - } - - /* Otherwise, steady state; wait for a message */ - in_want = 0; - task_wait_event(-1); } } } /*****************************************************************************/ -/* Console commnands */ +/* Console commands */ static int command_x86power(int argc, char **argv) { diff --git a/include/chipset.h b/include/chipset.h index 8e126ccddc..ac192f29c9 100644 --- a/include/chipset.h +++ b/include/chipset.h @@ -13,20 +13,29 @@ #include "common.h" -/* Chipset state. +/* Chipset state mask * * Note that this is a non-exhaustive list of states which the main chipset can * be in, and is potentially one-to-many for real, underlying chipset states. * That's why chipset_in_state() asks "Is the chipset in something * approximating this state?" and not "Tell me what state the chipset is in and * I'll compare it myself with the state(s) I want." */ -enum chipset_state { - CHIPSET_STATE_SOFT_OFF, /* Soft off (S5) */ - CHIPSET_STATE_SUSPEND, /* Suspend (S3) */ - CHIPSET_STATE_ON, /* On (S0) */ +enum chipset_state_mask { + CHIPSET_STATE_HARD_OFF = 0x01, /* Hard off (G3) */ + CHIPSET_STATE_SOFT_OFF = 0x02, /* Soft off (S5) */ + CHIPSET_STATE_SUSPEND = 0x04, /* Suspend (S3) */ + CHIPSET_STATE_ON = 0x08, /* On (S0) */ + /* Common combinations */ + CHIPSET_STATE_ANY_OFF = (CHIPSET_STATE_HARD_OFF | + CHIPSET_STATE_SOFT_OFF), /* Any off state */ }; -/* Returns non-zero if the chipset is in the specified state. */ -int chipset_in_state(enum chipset_state in_state); +/* Return non-zero if the chipset is in one of the states specified in the + * mask. */ +int chipset_in_state(int state_mask); + +/* Ask the chipset to exit the hard off state. Does nothing if the chipset has + * already left the state, or was not in the state to begin with. */ +void chipset_exit_hard_off(void); #endif /* __CROS_EC_CHIPSET_H */ -- cgit v1.2.1