diff options
Diffstat (limited to 'chip/lm4/switch.c')
-rw-r--r-- | chip/lm4/switch.c | 441 |
1 files changed, 31 insertions, 410 deletions
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) |