From 672057cb7e242574079b05e3028c0e337aa450e5 Mon Sep 17 00:00:00 2001 From: Randall Spangler Date: Fri, 12 Jul 2013 14:07:25 -0700 Subject: Split x86 power button logic out of switch.c Power button logic is common across all platforms and is not LM4-specific, so move it to its own module. Switch.c will eventually be moving to common/ and will common across all platforms (not just x86), and splitting out the x86 power button logic is needed before that too. BUG=chrome-os-partner:18343 BRANCH=none TEST=manual 1) power on system with both lid and power button. 2) power+refresh -> reboots 3) power+refresh+esc -> recovery mode 4) power+refresh+downarrow -> reboots, AP stays off 5) toggling recovery GPIO via servo should generate SW debug output showing bit 0x10 toggling Change-Id: I07714e2c035dceece66f90407983397d2697e7d5 Signed-off-by: Randall Spangler Reviewed-on: https://gerrit.chromium.org/gerrit/61780 --- board/bds/board.h | 3 + board/falco/board.h | 1 + board/falco/ec.tasklist | 2 +- board/link/board.h | 1 + board/link/ec.tasklist | 2 +- board/peppy/board.h | 1 + board/peppy/ec.tasklist | 2 +- board/slippy/board.h | 1 + board/slippy/ec.tasklist | 2 +- board/wolf/board.h | 1 + board/wolf/ec.tasklist | 2 +- chip/lm4/build.mk | 4 +- chip/lm4/config_chip.h | 1 + chip/lm4/switch.c | 441 ++++------------------------------------------ common/build.mk | 1 + common/charge_state.c | 9 +- common/power_button_x86.c | 399 +++++++++++++++++++++++++++++++++++++++++ include/switch.h | 9 +- 18 files changed, 456 insertions(+), 426 deletions(-) create mode 100644 common/power_button_x86.c diff --git a/board/bds/board.h b/board/bds/board.h index 0abb267637..00127fd8c0 100644 --- a/board/bds/board.h +++ b/board/bds/board.h @@ -21,6 +21,9 @@ #define CONFIG_EOPTION #define CONFIG_PSTORE +/* LM4 modules we want to exclude */ +#undef CONFIG_SWITCH + /* Write protect is active high */ #define CONFIG_WP_ACTIVE_HIGH diff --git a/board/falco/board.h b/board/falco/board.h index 9cecb1d000..7a9e8ce580 100644 --- a/board/falco/board.h +++ b/board/falco/board.h @@ -35,6 +35,7 @@ #define CONFIG_LPC #define CONFIG_PECI #define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM_FAN #define CONFIG_TEMP_SENSOR #define CONFIG_TEMP_SENSOR_G781 diff --git a/board/falco/ec.tasklist b/board/falco/ec.tasklist index 091e8b340e..40d5ec4f60 100644 --- a/board/falco/ec.tasklist +++ b/board/falco/ec.tasklist @@ -25,5 +25,5 @@ TASK_NOTEST(KEYPROTO, keyboard_protocol_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(SWITCH, switch_task, NULL, TASK_STACK_SIZE) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(KEYSCAN, keyboard_scan_task, NULL, TASK_STACK_SIZE) diff --git a/board/link/board.h b/board/link/board.h index 3c6900c497..abcdf04508 100644 --- a/board/link/board.h +++ b/board/link/board.h @@ -38,6 +38,7 @@ #define CONFIG_ONEWIRE_LED #define CONFIG_PECI #define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM_FAN #define CONFIG_PWM_KBLIGHT #define CONFIG_TEMP_SENSOR diff --git a/board/link/ec.tasklist b/board/link/ec.tasklist index 07a0eb1d29..2b3233e8e0 100644 --- a/board/link/ec.tasklist +++ b/board/link/ec.tasklist @@ -26,5 +26,5 @@ TASK_NOTEST(KEYPROTO, keyboard_protocol_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(SWITCH, switch_task, NULL, TASK_STACK_SIZE) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(KEYSCAN, keyboard_scan_task, NULL, TASK_STACK_SIZE) diff --git a/board/peppy/board.h b/board/peppy/board.h index d5ce4c54a7..6651aa8961 100644 --- a/board/peppy/board.h +++ b/board/peppy/board.h @@ -34,6 +34,7 @@ #define CONFIG_LPC #define CONFIG_PECI #define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM_FAN #define CONFIG_TEMP_SENSOR #define CONFIG_TEMP_SENSOR_G781 diff --git a/board/peppy/ec.tasklist b/board/peppy/ec.tasklist index 091e8b340e..40d5ec4f60 100644 --- a/board/peppy/ec.tasklist +++ b/board/peppy/ec.tasklist @@ -25,5 +25,5 @@ TASK_NOTEST(KEYPROTO, keyboard_protocol_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(SWITCH, switch_task, NULL, TASK_STACK_SIZE) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(KEYSCAN, keyboard_scan_task, NULL, TASK_STACK_SIZE) diff --git a/board/slippy/board.h b/board/slippy/board.h index 44913b9e12..d27d3ec54b 100644 --- a/board/slippy/board.h +++ b/board/slippy/board.h @@ -33,6 +33,7 @@ #define CONFIG_LPC #define CONFIG_PECI #define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM_FAN #define CONFIG_TEMP_SENSOR #define CONFIG_USB_PORT_POWER_DUMB diff --git a/board/slippy/ec.tasklist b/board/slippy/ec.tasklist index 091e8b340e..40d5ec4f60 100644 --- a/board/slippy/ec.tasklist +++ b/board/slippy/ec.tasklist @@ -25,5 +25,5 @@ TASK_NOTEST(KEYPROTO, keyboard_protocol_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(SWITCH, switch_task, NULL, TASK_STACK_SIZE) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(KEYSCAN, keyboard_scan_task, NULL, TASK_STACK_SIZE) diff --git a/board/wolf/board.h b/board/wolf/board.h index 98b036afbb..d015cec803 100644 --- a/board/wolf/board.h +++ b/board/wolf/board.h @@ -28,6 +28,7 @@ #define CONFIG_LPC #define CONFIG_PECI #define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_X86 #define CONFIG_PWM_FAN #define CONFIG_TEMP_SENSOR #define CONFIG_USB_PORT_POWER_DUMB diff --git a/board/wolf/ec.tasklist b/board/wolf/ec.tasklist index dc735958f0..848b56830c 100644 --- a/board/wolf/ec.tasklist +++ b/board/wolf/ec.tasklist @@ -25,5 +25,5 @@ TASK_NOTEST(KEYPROTO, keyboard_protocol_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(HOSTCMD, host_command_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) \ - TASK_ALWAYS(SWITCH, switch_task, NULL, TASK_STACK_SIZE) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, TASK_STACK_SIZE) \ TASK_NOTEST(KEYSCAN, keyboard_scan_task, NULL, TASK_STACK_SIZE) diff --git a/chip/lm4/build.mk b/chip/lm4/build.mk index a3178b88dc..39b14a5e77 100644 --- a/chip/lm4/build.mk +++ b/chip/lm4/build.mk @@ -23,6 +23,6 @@ chip-$(CONFIG_PECI)+=peci.o chip-$(CONFIG_PWM_FAN)+=pwm_fan.o chip-$(CONFIG_PWM_KBLIGHT)+=pwm_kblight.o chip-$(CONFIG_SPI)+=spi.o -chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o -chip-$(HAS_TASK_SWITCH)+=switch.o +chip-$(CONFIG_SWITCH)+=switch.o chip-$(CONFIG_WATCHDOG)+=watchdog.o +chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o diff --git a/chip/lm4/config_chip.h b/chip/lm4/config_chip.h index 849f68087d..1c7fdfb48b 100644 --- a/chip/lm4/config_chip.h +++ b/chip/lm4/config_chip.h @@ -104,6 +104,7 @@ #define CONFIG_FMAP #define CONFIG_FPU #define CONFIG_I2C +#define CONFIG_SWITCH #define CONFIG_WATCHDOG /* Compile for running from RAM instead of flash */ diff --git a/chip/lm4/switch.c b/chip/lm4/switch.c index 40b41a039d..73f1c16edb 100644 --- a/chip/lm4/switch.c +++ b/chip/lm4/switch.c @@ -3,123 +3,51 @@ * found in the LICENSE file. */ -/* Power button and lid switch module for Chrome EC */ +/* Switch module for Chrome EC */ -#include "charge_state.h" -#include "chipset.h" #include "common.h" #include "console.h" #include "flash.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" -#include "keyboard_protocol.h" -#include "keyboard_scan.h" #include "lid_switch.h" #include "power_button.h" -#include "pwm.h" #include "switch.h" -#include "system.h" -#include "task.h" -#include "timer.h" #include "util.h" /* Console output macros */ #define CPUTS(outstr) cputs(CC_SWITCH, outstr) #define CPRINTF(format, args...) cprintf(CC_SWITCH, format, ## args) -/* - * When chipset is on, we stretch the power button signal to it so chipset - * hard-reset is triggered at ~8 sec, not ~4 sec: - * - * PWRBTN# --- ---- - * to EC |______________________| - * - * - * PWRBTN# --- --------- ---- - * to PCH |__| |___________| - * t0 t1 held down - * - * scan code | | - * to host v v - * @S0 make code break code - */ -/* TODO: link to full power button / lid switch state machine description. */ -#define PWRBTN_DELAY_T0 (32 * MSEC) /* 32ms (PCH requires >16ms) */ -#define PWRBTN_DELAY_T1 (4 * SECOND - PWRBTN_DELAY_T0) /* 4 secs - t0 */ -/* - * 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 PWRBTN_INITIAL_US (200 * MSEC) - -enum power_button_state { - /* Button up; state machine idle */ - PWRBTN_STATE_IDLE = 0, - /* Button pressed; debouncing done */ - PWRBTN_STATE_PRESSED, - /* Button down, chipset on; sending initial short pulse */ - PWRBTN_STATE_T0, - /* Button down, chipset on; delaying until we should reassert signal */ - PWRBTN_STATE_T1, - /* Button down, signal asserted to chipset */ - PWRBTN_STATE_HELD, - /* Force pulse due to lid-open event */ - PWRBTN_STATE_LID_OPEN, - /* Button released; debouncing done */ - PWRBTN_STATE_RELEASED, - /* Ignore next button release */ - PWRBTN_STATE_EAT_RELEASE, - /* - * Need to power on system after init, but waiting to find out if - * sufficient battery power. - */ - PWRBTN_STATE_INIT_ON, - /* Forced pulse at EC boot due to keyboard controlled reset */ - PWRBTN_STATE_BOOT_KB_RESET, - /* Power button pressed when chipset was off; stretching pulse */ - PWRBTN_STATE_WAS_OFF, -}; -static enum power_button_state pwrbtn_state = PWRBTN_STATE_IDLE; - -static const char * const state_names[] = { - "idle", - "pressed", - "t0", - "t1", - "held", - "lid-open", - "released", - "eat-release", - "init-on", - "recovery", - "was-off", -}; - -/* - * Time for next state transition of power button state machine, or 0 if the - * state doesn't have a timeout. - */ -static uint64_t tnext_state; - -/* - * Debounce timeout for power button. 0 means the signal is stable (not being - * debounced). - */ -static uint64_t tdebounce_pwr; - static uint8_t *memmap_switches; /** * Update status of non-debounced switches. + * + * Note that deferred functions are called in the same context as lid and + * power button changes, so we don't need a mutex. */ -static void update_other_switches(void) +static void switch_update(void) { + static uint8_t prev; + /* Make sure this is safe to call before power_button_init() */ if (!memmap_switches) return; + prev = *memmap_switches; + + if (power_button_is_pressed()) + *memmap_switches |= EC_SWITCH_POWER_BUTTON_PRESSED; + else + *memmap_switches &= ~EC_SWITCH_POWER_BUTTON_PRESSED; + + if (lid_is_open()) + *memmap_switches |= EC_SWITCH_LID_OPEN; + else + *memmap_switches &= ~EC_SWITCH_LID_OPEN; + if ((flash_get_protect() & EC_FLASH_PROTECT_GPIO_ASSERTED) == 0) *memmap_switches |= EC_SWITCH_WRITE_PROTECT_DISABLED; else @@ -129,259 +57,13 @@ static void update_other_switches(void) *memmap_switches |= EC_SWITCH_DEDICATED_RECOVERY; else *memmap_switches &= ~EC_SWITCH_DEDICATED_RECOVERY; -} - -static void set_pwrbtn_to_pch(int high) -{ - /* - * If the battery is discharging and low enough we'd shut down the - * system, don't press the power button. - */ - if (!high && charge_want_shutdown()) { - CPRINTF("[%T PB PCH pwrbtn ignored due to battery level\n"); - high = 1; - } - - CPRINTF("[%T PB PCH pwrbtn=%s]\n", high ? "HIGH" : "LOW"); - gpio_set_level(GPIO_PCH_PWRBTN_L, high); -} - -/** - * Handle debounced power button down. - */ -static void power_button_pressed(uint64_t tnow) -{ - CPRINTF("[%T PB pressed]\n"); - pwrbtn_state = PWRBTN_STATE_PRESSED; - tnext_state = tnow; - *memmap_switches |= EC_SWITCH_POWER_BUTTON_PRESSED; -} - -/** - * Handle debounced power button up. - */ -static void power_button_released(uint64_t tnow) -{ - CPRINTF("[%T PB released]\n"); - pwrbtn_state = PWRBTN_STATE_RELEASED; - tnext_state = tnow; - *memmap_switches &= ~EC_SWITCH_POWER_BUTTON_PRESSED; -} -/** - * Set initial power button state. - */ -static void set_initial_pwrbtn_state(void) -{ - uint32_t reset_flags = system_get_reset_flags(); - - if (system_jumped_to_this_image() && - chipset_in_state(CHIPSET_STATE_ON)) { - /* - * Jumped to this image while the chipset was already on, so - * simply reflect the actual power button state. - */ - if (power_button_is_pressed()) { - *memmap_switches |= EC_SWITCH_POWER_BUTTON_PRESSED; - CPRINTF("[%T PB init-jumped-held]\n"); - set_pwrbtn_to_pch(0); - } else { - CPRINTF("[%T PB init-jumped]\n"); - } - } else if ((reset_flags & RESET_FLAG_AP_OFF) || - (keyboard_scan_get_boot_key() == BOOT_KEY_DOWN_ARROW)) { - /* - * Reset triggered by keyboard-controlled reset, and down-arrow - * was held down. Or reset flags request AP off. - * - * Leave the main processor off. This is a fail-safe - * combination for debugging failures booting the main - * processor. - * - * Don't let the PCH see that the power button was pressed. - * Otherwise, it might power on. - */ - CPRINTF("[%T PB init-off]\n"); - set_pwrbtn_to_pch(1); - if (power_button_is_pressed()) - pwrbtn_state = PWRBTN_STATE_EAT_RELEASE; - else - pwrbtn_state = PWRBTN_STATE_IDLE; - } else { - /* - * All other EC reset conditions power on the main processor so - * it can verify the EC. - */ - CPRINTF("[%T PB init-on]\n"); - pwrbtn_state = PWRBTN_STATE_INIT_ON; - } + if (prev != *memmap_switches) + CPRINTF("[%T SW 0x%02x]\n", *memmap_switches); } - -/*****************************************************************************/ -/* Task / state machine */ - -/** - * Power button state machine. - * - * @param tnow Current time from usec counter - */ -static void state_machine(uint64_t tnow) -{ - /* Not the time to move onto next state */ - if (tnow < tnext_state) - return; - - /* States last forever unless otherwise specified */ - tnext_state = 0; - - switch (pwrbtn_state) { - case PWRBTN_STATE_PRESSED: - 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; - pwrbtn_state = PWRBTN_STATE_T0; - } - set_pwrbtn_to_pch(0); - break; - case PWRBTN_STATE_T0: - tnext_state = tnow + PWRBTN_DELAY_T1; - pwrbtn_state = PWRBTN_STATE_T1; - set_pwrbtn_to_pch(1); - break; - case PWRBTN_STATE_T1: - /* - * 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_ANY_OFF)) - CPRINTF("[%T PB chipset already off]\n"); - else - set_pwrbtn_to_pch(0); - pwrbtn_state = PWRBTN_STATE_HELD; - break; - case PWRBTN_STATE_RELEASED: - case PWRBTN_STATE_LID_OPEN: - set_pwrbtn_to_pch(1); - pwrbtn_state = PWRBTN_STATE_IDLE; - break; - case PWRBTN_STATE_INIT_ON: - /* - * Don't do anything until the charger knows the battery level. - * Otherwise we could power on the AP only to shut it right - * back down due to insufficient battery. - */ -#ifdef HAS_TASK_CHARGER - if (charge_get_state() == PWR_STATE_INIT) - break; -#endif - - /* - * Power the system on if possible. Gating due to insufficient - * battery is handled inside set_pwrbtn_to_pch(). - */ - chipset_exit_hard_off(); - set_pwrbtn_to_pch(0); - tnext_state = get_time().val + PWRBTN_INITIAL_US; - - if (power_button_is_pressed()) { - *memmap_switches |= EC_SWITCH_POWER_BUTTON_PRESSED; - - if (system_get_reset_flags() & RESET_FLAG_RESET_PIN) - pwrbtn_state = PWRBTN_STATE_BOOT_KB_RESET; - else - pwrbtn_state = PWRBTN_STATE_WAS_OFF; - } else { - pwrbtn_state = PWRBTN_STATE_RELEASED; - } - - break; - - case PWRBTN_STATE_BOOT_KB_RESET: - /* Initial forced pulse is done. Ignore the actual power - * button until it's released, so that holding down the - * recovery combination doesn't cause the chipset to shut back - * down. */ - set_pwrbtn_to_pch(1); - if (power_button_is_pressed()) - pwrbtn_state = PWRBTN_STATE_EAT_RELEASE; - else - pwrbtn_state = PWRBTN_STATE_IDLE; - break; - case PWRBTN_STATE_WAS_OFF: - /* Done stretching initial power button signal, so show the - * true power button state to the PCH. */ - if (power_button_is_pressed()) { - /* User is still holding the power button */ - pwrbtn_state = PWRBTN_STATE_HELD; - } else { - /* Stop stretching the power button press */ - power_button_released(tnow); - } - break; - case PWRBTN_STATE_IDLE: - case PWRBTN_STATE_HELD: - case PWRBTN_STATE_EAT_RELEASE: - /* Do nothing */ - break; - } -} - -void switch_task(void) -{ - uint64_t t; - uint64_t tsleep; - - while (1) { - t = get_time().val; - - /* Handle non-debounced switches */ - update_other_switches(); - - /* Update state machine */ - CPRINTF("[%T PB task %d = %s, sw 0x%02x]\n", pwrbtn_state, - state_names[pwrbtn_state], *memmap_switches); - - state_machine(t); - - /* Sleep until our next timeout */ - tsleep = -1; - if (tdebounce_pwr && tdebounce_pwr < tsleep) - tsleep = tdebounce_pwr; - if (tnext_state && tnext_state < tsleep) - tsleep = tnext_state; - t = get_time().val; - if (tsleep > t) { - unsigned d = tsleep == -1 ? -1 : (unsigned)(tsleep - t); - /* - * (Yes, the conversion from uint64_t to unsigned could - * theoretically overflow if we wanted to sleep for - * more than 2^32 us, but our timeouts are small enough - * that can't happen - and even if it did, we'd just go - * back to sleep after deciding that we woke up too - * early.) - */ - CPRINTF("[%T PB task %d = %s, wait %d]\n", pwrbtn_state, - state_names[pwrbtn_state], d); - task_wait_event(d); - } - } -} - -/*****************************************************************************/ -/* Hooks */ +DECLARE_DEFERRED(switch_update); +DECLARE_HOOK(HOOK_LID_CHANGE, switch_update, HOOK_PRIO_DEFAULT); +DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, switch_update, HOOK_PRIO_DEFAULT); static void switch_init(void) { @@ -389,18 +71,18 @@ static void switch_init(void) memmap_switches = host_get_memmap(EC_MEMMAP_SWITCHES); *memmap_switches = 0; - if (lid_is_open()) - *memmap_switches |= EC_SWITCH_LID_OPEN; - - update_other_switches(); - set_initial_pwrbtn_state(); + switch_update(); /* Switch data is now present */ *host_get_memmap(EC_MEMMAP_SWITCHES_VERSION) = 1; /* Enable interrupts, now that we've initialized */ - gpio_enable_interrupt(GPIO_POWER_BUTTON_L); gpio_enable_interrupt(GPIO_RECOVERY_L); + + /* + * TODO(rspangler): It's weird that flash_common.c owns reading the + * write protect signal, but we enable the interrupt for it here. + */ #ifdef CONFIG_WP_ACTIVE_HIGH gpio_enable_interrupt(GPIO_WP); #else @@ -409,70 +91,9 @@ static void switch_init(void) } DECLARE_HOOK(HOOK_INIT, switch_init, HOOK_PRIO_DEFAULT); -/** - * Handle switch changes based on lid event. - */ -static void switch_lid_change(void) -{ - if (lid_is_open()) { - *memmap_switches |= EC_SWITCH_LID_OPEN; - - /* If the chipset is off, pulse the power button to wake it. */ - if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { - chipset_exit_hard_off(); - set_pwrbtn_to_pch(0); - pwrbtn_state = PWRBTN_STATE_LID_OPEN; - tnext_state = get_time().val + PWRBTN_INITIAL_US; - task_wake(TASK_ID_SWITCH); - } - - } else { - *memmap_switches &= ~EC_SWITCH_LID_OPEN; - } -} -DECLARE_HOOK(HOOK_LID_CHANGE, switch_lid_change, HOOK_PRIO_DEFAULT); - -/** - * Handle debounced power button changing state. - */ -static void power_button_changed(void) -{ - if (pwrbtn_state == PWRBTN_STATE_BOOT_KB_RESET || - pwrbtn_state == PWRBTN_STATE_INIT_ON || - pwrbtn_state == PWRBTN_STATE_LID_OPEN || - pwrbtn_state == PWRBTN_STATE_WAS_OFF) { - /* Ignore all power button changes during an initial pulse */ - CPRINTF("[%T PB ignoring change]\n"); - return; - } - - if (power_button_is_pressed()) { - /* Power button pressed */ - power_button_pressed(get_time().val); - } else { - /* Power button released */ - if (pwrbtn_state == PWRBTN_STATE_EAT_RELEASE) { - /* - * Ignore the first power button release if we already - * told the PCH the power button was released. - */ - CPRINTF("[%T PB ignoring release]\n"); - pwrbtn_state = PWRBTN_STATE_IDLE; - return; - } - - power_button_released(get_time().val); - } - - /* Wake the switch task */ - task_wake(TASK_ID_SWITCH); -} -DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT); - void switch_interrupt(enum gpio_signal signal) { - /* Wake task to handle change in non-debounced switches */ - task_wake(TASK_ID_SWITCH); + hook_call_deferred(switch_update, 0); } static int command_mmapinfo(int argc, char **argv) diff --git a/common/build.mk b/common/build.mk index 0476fcb8b1..b5d717438a 100644 --- a/common/build.mk +++ b/common/build.mk @@ -45,6 +45,7 @@ common-$(CONFIG_LID_SWITCH)+=lid_switch.o common-$(CONFIG_LPC)+=port80.o common-$(CONFIG_ONEWIRE_LED)+=onewire_led.o common-$(CONFIG_POWER_BUTTON)+=power_button.o +common-$(CONFIG_POWER_BUTTON_X86)+=power_button_x86.o common-$(CONFIG_PSTORE)+=pstore_commands.o common-$(CONFIG_REGULATOR_IR357X)+=regulator_ir357x.o common-$(CONFIG_SMART_BATTERY)+=smart_battery.o smart_battery_stub.o diff --git a/common/charge_state.c b/common/charge_state.c index 83df8ad8a5..02c556a54e 100644 --- a/common/charge_state.c +++ b/common/charge_state.c @@ -668,13 +668,18 @@ void charger_task(void) state_name[new_state]); } +#ifdef HAS_TASK_POWERBTN /* - * After first init, wake the switch task so it can + * After first init, wake the power button task so it can * power on the AP if necessary. + * + * TODO(rspangler): API, instead of assuming power button task + * exists */ if (ctx->prev.state == PWR_STATE_INIT && new_state != PWR_STATE_INIT) - task_wake(TASK_ID_SWITCH); + task_wake(TASK_ID_POWERBTN); +#endif switch (new_state) { case PWR_STATE_IDLE0: diff --git a/common/power_button_x86.c b/common/power_button_x86.c new file mode 100644 index 0000000000..2d353b74ed --- /dev/null +++ b/common/power_button_x86.c @@ -0,0 +1,399 @@ +/* Copyright (c) 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. + */ + +/* Power button state machine for x86 platforms */ + +#include "charge_state.h" +#include "chipset.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "host_command.h" +#include "keyboard_scan.h" +#include "lid_switch.h" +#include "power_button.h" +#include "switch.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_SWITCH, outstr) +#define CPRINTF(format, args...) cprintf(CC_SWITCH, format, ## args) + +/* + * When chipset is on, we stretch the power button signal to it so chipset + * hard-reset is triggered at ~8 sec, not ~4 sec: + * + * PWRBTN# --- ---- + * to EC |______________________| + * + * + * PWRBTN# --- --------- ---- + * to PCH |__| |___________| + * t0 t1 held down + * + * scan code | | + * to host v v + * @S0 make code break code + */ +/* TODO: link to full power button / lid switch state machine description. */ +#define PWRBTN_DELAY_T0 (32 * MSEC) /* 32ms (PCH requires >16ms) */ +#define PWRBTN_DELAY_T1 (4 * SECOND - PWRBTN_DELAY_T0) /* 4 secs - t0 */ +/* + * 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 PWRBTN_INITIAL_US (200 * MSEC) + +enum power_button_state { + /* Button up; state machine idle */ + PWRBTN_STATE_IDLE = 0, + /* Button pressed; debouncing done */ + PWRBTN_STATE_PRESSED, + /* Button down, chipset on; sending initial short pulse */ + PWRBTN_STATE_T0, + /* Button down, chipset on; delaying until we should reassert signal */ + PWRBTN_STATE_T1, + /* Button down, signal asserted to chipset */ + PWRBTN_STATE_HELD, + /* Force pulse due to lid-open event */ + PWRBTN_STATE_LID_OPEN, + /* Button released; debouncing done */ + PWRBTN_STATE_RELEASED, + /* Ignore next button release */ + PWRBTN_STATE_EAT_RELEASE, + /* + * Need to power on system after init, but waiting to find out if + * sufficient battery power. + */ + PWRBTN_STATE_INIT_ON, + /* Forced pulse at EC boot due to keyboard controlled reset */ + PWRBTN_STATE_BOOT_KB_RESET, + /* Power button pressed when chipset was off; stretching pulse */ + PWRBTN_STATE_WAS_OFF, +}; +static enum power_button_state pwrbtn_state = PWRBTN_STATE_IDLE; + +static const char * const state_names[] = { + "idle", + "pressed", + "t0", + "t1", + "held", + "lid-open", + "released", + "eat-release", + "init-on", + "recovery", + "was-off", +}; + +/* + * Time for next state transition of power button state machine, or 0 if the + * state doesn't have a timeout. + */ +static uint64_t tnext_state; + +static void set_pwrbtn_to_pch(int high) +{ + /* + * If the battery is discharging and low enough we'd shut down the + * system, don't press the power button. + */ + if (!high && charge_want_shutdown()) { + CPRINTF("[%T PB PCH pwrbtn ignored due to battery level\n"); + high = 1; + } + + CPRINTF("[%T PB PCH pwrbtn=%s]\n", high ? "HIGH" : "LOW"); + gpio_set_level(GPIO_PCH_PWRBTN_L, high); +} + +/** + * Handle debounced power button down. + */ +static void power_button_pressed(uint64_t tnow) +{ + CPRINTF("[%T PB pressed]\n"); + pwrbtn_state = PWRBTN_STATE_PRESSED; + tnext_state = tnow; +} + +/** + * Handle debounced power button up. + */ +static void power_button_released(uint64_t tnow) +{ + CPRINTF("[%T PB released]\n"); + pwrbtn_state = PWRBTN_STATE_RELEASED; + tnext_state = tnow; +} + +/** + * Set initial power button state. + */ +static void set_initial_pwrbtn_state(void) +{ + uint32_t reset_flags = system_get_reset_flags(); + + if (system_jumped_to_this_image() && + chipset_in_state(CHIPSET_STATE_ON)) { + /* + * Jumped to this image while the chipset was already on, so + * simply reflect the actual power button state. + */ + if (power_button_is_pressed()) { + CPRINTF("[%T PB init-jumped-held]\n"); + set_pwrbtn_to_pch(0); + } else { + CPRINTF("[%T PB init-jumped]\n"); + } + } else if ((reset_flags & RESET_FLAG_AP_OFF) || + (keyboard_scan_get_boot_key() == BOOT_KEY_DOWN_ARROW)) { + /* + * Reset triggered by keyboard-controlled reset, and down-arrow + * was held down. Or reset flags request AP off. + * + * Leave the main processor off. This is a fail-safe + * combination for debugging failures booting the main + * processor. + * + * Don't let the PCH see that the power button was pressed. + * Otherwise, it might power on. + */ + CPRINTF("[%T PB init-off]\n"); + set_pwrbtn_to_pch(1); + if (power_button_is_pressed()) + pwrbtn_state = PWRBTN_STATE_EAT_RELEASE; + else + pwrbtn_state = PWRBTN_STATE_IDLE; + } else { + /* + * All other EC reset conditions power on the main processor so + * it can verify the EC. + */ + CPRINTF("[%T PB init-on]\n"); + pwrbtn_state = PWRBTN_STATE_INIT_ON; + } +} + +/** + * Power button state machine. + * + * @param tnow Current time from usec counter + */ +static void state_machine(uint64_t tnow) +{ + /* Not the time to move onto next state */ + if (tnow < tnext_state) + return; + + /* States last forever unless otherwise specified */ + tnext_state = 0; + + switch (pwrbtn_state) { + case PWRBTN_STATE_PRESSED: + 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; + pwrbtn_state = PWRBTN_STATE_T0; + } + set_pwrbtn_to_pch(0); + break; + case PWRBTN_STATE_T0: + tnext_state = tnow + PWRBTN_DELAY_T1; + pwrbtn_state = PWRBTN_STATE_T1; + set_pwrbtn_to_pch(1); + break; + case PWRBTN_STATE_T1: + /* + * 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_ANY_OFF)) + CPRINTF("[%T PB chipset already off]\n"); + else + set_pwrbtn_to_pch(0); + pwrbtn_state = PWRBTN_STATE_HELD; + break; + case PWRBTN_STATE_RELEASED: + case PWRBTN_STATE_LID_OPEN: + set_pwrbtn_to_pch(1); + pwrbtn_state = PWRBTN_STATE_IDLE; + break; + case PWRBTN_STATE_INIT_ON: + /* + * Don't do anything until the charger knows the battery level. + * Otherwise we could power on the AP only to shut it right + * back down due to insufficient battery. + */ +#ifdef HAS_TASK_CHARGER + if (charge_get_state() == PWR_STATE_INIT) + break; +#endif + + /* + * Power the system on if possible. Gating due to insufficient + * battery is handled inside set_pwrbtn_to_pch(). + */ + chipset_exit_hard_off(); + set_pwrbtn_to_pch(0); + tnext_state = get_time().val + PWRBTN_INITIAL_US; + + if (power_button_is_pressed()) { + if (system_get_reset_flags() & RESET_FLAG_RESET_PIN) + pwrbtn_state = PWRBTN_STATE_BOOT_KB_RESET; + else + pwrbtn_state = PWRBTN_STATE_WAS_OFF; + } else { + pwrbtn_state = PWRBTN_STATE_RELEASED; + } + + break; + + case PWRBTN_STATE_BOOT_KB_RESET: + /* Initial forced pulse is done. Ignore the actual power + * button until it's released, so that holding down the + * recovery combination doesn't cause the chipset to shut back + * down. */ + set_pwrbtn_to_pch(1); + if (power_button_is_pressed()) + pwrbtn_state = PWRBTN_STATE_EAT_RELEASE; + else + pwrbtn_state = PWRBTN_STATE_IDLE; + break; + case PWRBTN_STATE_WAS_OFF: + /* Done stretching initial power button signal, so show the + * true power button state to the PCH. */ + if (power_button_is_pressed()) { + /* User is still holding the power button */ + pwrbtn_state = PWRBTN_STATE_HELD; + } else { + /* Stop stretching the power button press */ + power_button_released(tnow); + } + break; + case PWRBTN_STATE_IDLE: + case PWRBTN_STATE_HELD: + case PWRBTN_STATE_EAT_RELEASE: + /* Do nothing */ + break; + } +} + +void power_button_task(void) +{ + uint64_t t; + uint64_t tsleep; + + while (1) { + t = get_time().val; + + /* Update state machine */ + CPRINTF("[%T PB task %d = %s]\n", pwrbtn_state, + state_names[pwrbtn_state]); + + state_machine(t); + + /* Sleep until our next timeout */ + tsleep = -1; + if (tnext_state && tnext_state < tsleep) + tsleep = tnext_state; + t = get_time().val; + if (tsleep > t) { + unsigned d = tsleep == -1 ? -1 : (unsigned)(tsleep - t); + /* + * (Yes, the conversion from uint64_t to unsigned could + * theoretically overflow if we wanted to sleep for + * more than 2^32 us, but our timeouts are small enough + * that can't happen - and even if it did, we'd just go + * back to sleep after deciding that we woke up too + * early.) + */ + CPRINTF("[%T PB task %d = %s, wait %d]\n", pwrbtn_state, + state_names[pwrbtn_state], d); + task_wait_event(d); + } + } +} + +/*****************************************************************************/ +/* Hooks */ + +static void powerbtn_x86_init(void) +{ + set_initial_pwrbtn_state(); +} +DECLARE_HOOK(HOOK_INIT, powerbtn_x86_init, HOOK_PRIO_DEFAULT); + +/** + * Handle switch changes based on lid event. + */ +static void powerbtn_x86_lid_change(void) +{ + /* If chipset is off, pulse the power button on lid open to wake it. */ + if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF)) { + chipset_exit_hard_off(); + set_pwrbtn_to_pch(0); + pwrbtn_state = PWRBTN_STATE_LID_OPEN; + tnext_state = get_time().val + PWRBTN_INITIAL_US; + task_wake(TASK_ID_POWERBTN); + } +} +DECLARE_HOOK(HOOK_LID_CHANGE, powerbtn_x86_lid_change, HOOK_PRIO_DEFAULT); + +/** + * Handle debounced power button changing state. + */ +static void powerbtn_x86_changed(void) +{ + if (pwrbtn_state == PWRBTN_STATE_BOOT_KB_RESET || + pwrbtn_state == PWRBTN_STATE_INIT_ON || + pwrbtn_state == PWRBTN_STATE_LID_OPEN || + pwrbtn_state == PWRBTN_STATE_WAS_OFF) { + /* Ignore all power button changes during an initial pulse */ + CPRINTF("[%T PB ignoring change]\n"); + return; + } + + if (power_button_is_pressed()) { + /* Power button pressed */ + power_button_pressed(get_time().val); + } else { + /* Power button released */ + if (pwrbtn_state == PWRBTN_STATE_EAT_RELEASE) { + /* + * Ignore the first power button release if we already + * told the PCH the power button was released. + */ + CPRINTF("[%T PB ignoring release]\n"); + pwrbtn_state = PWRBTN_STATE_IDLE; + return; + } + + power_button_released(get_time().val); + } + + /* Wake the power button task */ + task_wake(TASK_ID_POWERBTN); +} +DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, powerbtn_x86_changed, HOOK_PRIO_DEFAULT); diff --git a/include/switch.h b/include/switch.h index 2cd732774e..f59e56df52 100644 --- a/include/switch.h +++ b/include/switch.h @@ -11,7 +11,7 @@ #include "common.h" #include "gpio.h" -#ifdef HAS_TASK_SWITCH +#ifdef CONFIG_SWITCH /** * Interrupt handler for switch inputs. * @@ -20,11 +20,6 @@ void switch_interrupt(enum gpio_signal signal); #else #define switch_interrupt NULL -#endif /* HAS_TASK_SWITCH */ - -/** - * Return non-zero if write protect signal is asserted. - */ -int switch_get_write_protect(void); +#endif /* CONFIG_SWITCH */ #endif /* __CROS_EC_SWITCH_H */ -- cgit v1.2.1