| /* Copyright (c) 2014 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. |
| */ |
| |
| /* Button module for Chrome EC */ |
| |
| #include "button.h" |
| #include "chipset.h" |
| #include "common.h" |
| #include "compile_time_macros.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "host_command.h" |
| #include "hooks.h" |
| #include "keyboard_protocol.h" |
| #include "led_common.h" |
| #include "power_button.h" |
| #include "system.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "watchdog.h" |
| |
| /* Console output macro */ |
| #define CPRINTS(format, args...) cprints(CC_SWITCH, format, ## args) |
| |
| struct button_state_t { |
| uint64_t debounce_time; |
| int debounced_pressed; |
| }; |
| |
| static struct button_state_t __bss_slow state[BUTTON_COUNT]; |
| |
| static uint64_t __bss_slow next_deferred_time; |
| |
| #ifdef CONFIG_CMD_BUTTON |
| static int siml_btn_presd; |
| |
| /* |
| * Flip state of associated button type in sim_button_state bitmask. |
| * In bitmask, if bit is 1, button is pressed. If bit is 0, button is |
| * released. |
| * |
| * Returns the appropriate GPIO value based on table below: |
| * +----------+--------+--------+ |
| * | state | active | return | |
| * +----------+--------+--------+ |
| * | pressed | high | 1 | |
| * | pressed | low | 0 | |
| * | released | high | 0 | |
| * | released | low | 1 | |
| * +----------+--------+--------+ |
| */ |
| static int simulated_button_pressed(const struct button_config *button) |
| { |
| /* bitmask to keep track of simulated state of each button */ |
| static int sim_button_state; |
| int button_mask = 1 << button->type; |
| int ret_val; |
| |
| /* flip the state of the button */ |
| sim_button_state = sim_button_state ^ button_mask; |
| ret_val = !!(sim_button_state & button_mask); |
| /* adjustment for active high/lo */ |
| if (!(button->flags & BUTTON_FLAG_ACTIVE_HIGH)) |
| ret_val = !ret_val; |
| return ret_val; |
| } |
| #endif |
| |
| /* |
| * Whether a button is currently pressed. |
| */ |
| static int raw_button_pressed(const struct button_config *button) |
| { |
| int raw_value = |
| #ifdef CONFIG_CMD_BUTTON |
| siml_btn_presd ? |
| simulated_button_pressed(button) : |
| #endif |
| gpio_get_level(button->gpio); |
| return button->flags & BUTTON_FLAG_ACTIVE_HIGH ? |
| raw_value : !raw_value; |
| } |
| |
| #ifdef CONFIG_BUTTON_TRIGGERED_RECOVERY |
| |
| #ifdef CONFIG_LED_COMMON |
| static void button_blink_hw_reinit_led(void) |
| { |
| int led_state = LED_STATE_ON; |
| timestamp_t deadline; |
| timestamp_t now = get_time(); |
| |
| /* Blink LED for 3 seconds. */ |
| deadline.val = now.val + (3 * SECOND); |
| |
| while (!timestamp_expired(deadline, &now)) { |
| led_control(EC_LED_ID_RECOVERY_HW_REINIT_LED, led_state); |
| led_state = !led_state; |
| watchdog_reload(); |
| msleep(100); |
| now = get_time(); |
| } |
| |
| /* Reset LED to default state. */ |
| led_control(EC_LED_ID_RECOVERY_HW_REINIT_LED, LED_STATE_RESET); |
| } |
| #endif |
| |
| /* |
| * Whether recovery button (or combination of equivalent buttons) is pressed |
| */ |
| static int is_recovery_button_pressed(void) |
| { |
| int i; |
| for (i = 0; i < recovery_buttons_count; i++) { |
| if (!raw_button_pressed(recovery_buttons[i])) |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* |
| * If the EC is reset and recovery is requested, then check if HW_REINIT is |
| * requested as well. Since the EC reset occurs after volup+voldn+power buttons |
| * are held down for 10 seconds, check the state of these buttons for 20 more |
| * seconds. If they are still held down all this time, then set host event to |
| * indicate HW_REINIT is requested. Also, make sure watchdog is reloaded in |
| * order to prevent watchdog from resetting the EC. |
| */ |
| static void button_check_hw_reinit_required(void) |
| { |
| timestamp_t deadline; |
| timestamp_t now = get_time(); |
| #ifdef CONFIG_LED_COMMON |
| uint8_t led_on = 0; |
| #endif |
| |
| deadline.val = now.val + (20 * SECOND); |
| |
| CPRINTS("Checking for HW_REINIT request"); |
| |
| while (!timestamp_expired(deadline, &now)) { |
| if (!is_recovery_button_pressed() || |
| !power_button_signal_asserted()) { |
| CPRINTS("No HW_REINIT request"); |
| #ifdef CONFIG_LED_COMMON |
| if (led_on) |
| led_control(EC_LED_ID_RECOVERY_HW_REINIT_LED, |
| LED_STATE_RESET); |
| #endif |
| return; |
| } |
| |
| #ifdef CONFIG_LED_COMMON |
| if (!led_on) { |
| led_control(EC_LED_ID_RECOVERY_HW_REINIT_LED, |
| LED_STATE_ON); |
| led_on = 1; |
| } |
| #endif |
| |
| now = get_time(); |
| watchdog_reload(); |
| } |
| |
| CPRINTS("HW_REINIT requested"); |
| host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT); |
| |
| #ifdef CONFIG_LED_COMMON |
| button_blink_hw_reinit_led(); |
| #endif |
| } |
| |
| static int is_recovery_boot(void) |
| { |
| if (system_jumped_to_this_image()) |
| return 0; |
| if (!(system_get_reset_flags() & |
| (RESET_FLAG_RESET_PIN | RESET_FLAG_POWER_ON))) |
| return 0; |
| if (!is_recovery_button_pressed()) |
| return 0; |
| return 1; |
| } |
| #endif /* CONFIG_BUTTON_TRIGGERED_RECOVERY */ |
| |
| /* |
| * Button initialization. |
| */ |
| void button_init(void) |
| { |
| int i; |
| |
| CPRINTS("init buttons"); |
| next_deferred_time = 0; |
| for (i = 0; i < BUTTON_COUNT; i++) { |
| state[i].debounced_pressed = raw_button_pressed(&buttons[i]); |
| state[i].debounce_time = 0; |
| gpio_enable_interrupt(buttons[i].gpio); |
| } |
| |
| #ifdef CONFIG_BUTTON_TRIGGERED_RECOVERY |
| if (is_recovery_boot()) { |
| system_clear_reset_flags(RESET_FLAG_AP_OFF); |
| host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY); |
| button_check_hw_reinit_required(); |
| } |
| #endif /* defined(CONFIG_BUTTON_TRIGGERED_RECOVERY) */ |
| } |
| |
| /* |
| * Handle debounced button changing state. |
| */ |
| |
| static void button_change_deferred(void); |
| DECLARE_DEFERRED(button_change_deferred); |
| |
| #ifdef CONFIG_EMULATED_SYSRQ |
| static void debug_mode_handle(void); |
| DECLARE_DEFERRED(debug_mode_handle); |
| DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, debug_mode_handle, HOOK_PRIO_LAST); |
| #endif |
| |
| static void button_change_deferred(void) |
| { |
| int i; |
| int new_pressed; |
| uint64_t soonest_debounce_time = 0; |
| uint64_t time_now = get_time().val; |
| |
| for (i = 0; i < BUTTON_COUNT; i++) { |
| /* Skip this button if we are not waiting to debounce */ |
| if (state[i].debounce_time == 0) |
| continue; |
| |
| if (state[i].debounce_time <= time_now) { |
| /* Check if the state has changed */ |
| new_pressed = raw_button_pressed(&buttons[i]); |
| if (state[i].debounced_pressed != new_pressed) { |
| state[i].debounced_pressed = new_pressed; |
| #ifdef CONFIG_EMULATED_SYSRQ |
| /* |
| * Calling deferred function for handling debug |
| * mode so that button change processing is not |
| * delayed. |
| */ |
| hook_call_deferred(&debug_mode_handle_data, 0); |
| #endif |
| CPRINTS("Button '%s' was %s", |
| buttons[i].name, new_pressed ? |
| "pressed" : "released"); |
| #if defined(HAS_TASK_KEYPROTO) || defined(CONFIG_KEYBOARD_PROTOCOL_MKBP) |
| keyboard_update_button(buttons[i].type, |
| new_pressed); |
| #endif |
| } |
| |
| /* Clear the debounce time to stop checking it */ |
| state[i].debounce_time = 0; |
| } else { |
| /* |
| * Make sure the next deferred call happens on or before |
| * each button needs it. |
| */ |
| soonest_debounce_time = (soonest_debounce_time == 0) ? |
| state[i].debounce_time : |
| MIN(soonest_debounce_time, |
| state[i].debounce_time); |
| } |
| } |
| |
| if (soonest_debounce_time != 0) { |
| next_deferred_time = soonest_debounce_time; |
| hook_call_deferred(&button_change_deferred_data, |
| next_deferred_time - time_now); |
| } |
| } |
| |
| /* |
| * Handle a button interrupt. |
| */ |
| void button_interrupt(enum gpio_signal signal) |
| { |
| int i; |
| uint64_t time_now = get_time().val; |
| |
| for (i = 0; i < BUTTON_COUNT; i++) { |
| if (buttons[i].gpio != signal) |
| continue; |
| |
| state[i].debounce_time = time_now + buttons[i].debounce_us; |
| if (next_deferred_time <= time_now || |
| next_deferred_time > state[i].debounce_time) { |
| next_deferred_time = state[i].debounce_time; |
| hook_call_deferred(&button_change_deferred_data, |
| next_deferred_time - time_now); |
| } |
| break; |
| } |
| } |
| |
| #ifdef CONFIG_CMD_BUTTON |
| static int button_present(enum keyboard_button_type type) |
| { |
| int i; |
| |
| for (i = 0; i < BUTTON_COUNT; i++) |
| if (buttons[i].type == type) |
| break; |
| |
| return i; |
| } |
| |
| static void button_interrupt_simulate(int button) |
| { |
| button_interrupt(buttons[button].gpio); |
| usleep(buttons[button].debounce_us >> 2); |
| button_interrupt(buttons[button].gpio); |
| } |
| |
| static int console_command_button(int argc, char **argv) |
| { |
| int press_ms = 50; |
| char *e; |
| int argv_idx; |
| int button; |
| int button_idx; |
| uint32_t button_mask = 0; |
| |
| if (argc < 2) |
| return EC_ERROR_PARAM_COUNT; |
| |
| for (argv_idx = 1; argv_idx < argc; argv_idx++) { |
| if (!strcasecmp(argv[argv_idx], "vup")) |
| button = button_present(KEYBOARD_BUTTON_VOLUME_UP); |
| else if (!strcasecmp(argv[argv_idx], "vdown")) |
| button = button_present(KEYBOARD_BUTTON_VOLUME_DOWN); |
| else if (!strcasecmp(argv[argv_idx], "rec")) |
| button = button_present(KEYBOARD_BUTTON_RECOVERY); |
| else { |
| /* If last parameter check if it is an integer. */ |
| if (argv_idx == argc - 1) { |
| press_ms = strtoi(argv[argv_idx], &e, 0); |
| /* If integer, break out of the loop. */ |
| if (!*e) |
| break; |
| } |
| button = BUTTON_COUNT; |
| } |
| |
| if (button == BUTTON_COUNT) |
| return EC_ERROR_PARAM1 + argv_idx - 1; |
| |
| button_mask |= (1 << button); |
| } |
| |
| if (!button_mask) |
| return EC_SUCCESS; |
| |
| siml_btn_presd = 1; |
| |
| /* Press the button(s) */ |
| for (button_idx = 0; button_idx < BUTTON_COUNT; button_idx++) |
| if (button_mask & (1 << button_idx)) |
| button_interrupt_simulate(button_idx); |
| |
| /* Hold the button(s) */ |
| msleep(press_ms); |
| |
| /* Release the button(s) */ |
| for (button_idx = 0; button_idx < BUTTON_COUNT; button_idx++) |
| if (button_mask & (1 << button_idx)) |
| button_interrupt_simulate(button_idx); |
| |
| /* Wait till button processing is finished */ |
| msleep(100); |
| |
| siml_btn_presd = 0; |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(button, console_command_button, |
| "vup|vdown msec", |
| "Simulate button press"); |
| #endif |
| |
| #ifdef CONFIG_EMULATED_SYSRQ |
| |
| #ifdef CONFIG_DEDICATED_RECOVERY_BUTTON |
| |
| /* |
| * Simplified sysrq handler |
| * |
| * In simplified sysrq, user can |
| * - press and release recovery button to send one sysrq event to the host |
| * - press and hold recovery button for 4 seconds to reset the AP (warm reset) |
| */ |
| static void debug_mode_handle(void) |
| { |
| static int recovery_button_pressed = 0; |
| |
| if (!recovery_button_pressed) { |
| if (is_recovery_button_pressed()) { |
| /* User pressed recovery button. Wait for 4 seconds |
| * to see if warm reset is requested. */ |
| recovery_button_pressed = 1; |
| hook_call_deferred(&debug_mode_handle_data, 4 * SECOND); |
| } |
| } else { |
| /* We come here when recovery button is released or when |
| * 4 sec elapsed with recovery button still pressed. */ |
| if (!is_recovery_button_pressed()) { |
| /* Cancel pending timer */ |
| hook_call_deferred(&debug_mode_handle_data, -1); |
| host_send_sysrq('x'); |
| CPRINTS("DEBUG MODE: sysrq-x sent"); |
| } else { |
| chipset_reset(CHIPSET_RESET_DBG_WARM_REBOOT); |
| CPRINTS("DEBUG MODE: Warm reset triggered"); |
| } |
| recovery_button_pressed = 0; |
| } |
| } |
| |
| #else /* CONFIG_DEDICATED_RECOVERY_BUTTON */ |
| |
| enum debug_state { |
| STATE_DEBUG_NONE, |
| STATE_DEBUG_CHECK, |
| STATE_STAGING, |
| STATE_DEBUG_MODE_ACTIVE, |
| STATE_SYSRQ_PATH, |
| STATE_WARM_RESET_PATH, |
| STATE_SYSRQ_EXEC, |
| STATE_WARM_RESET_EXEC, |
| }; |
| |
| #define DEBUG_BTN_POWER (1 << 0) |
| #define DEBUG_BTN_VOL_UP (1 << 1) |
| #define DEBUG_BTN_VOL_DN (1 << 2) |
| #define DEBUG_TIMEOUT (10 * SECOND) |
| |
| static enum debug_state curr_debug_state = STATE_DEBUG_NONE; |
| static enum debug_state next_debug_state = STATE_DEBUG_NONE; |
| static timestamp_t debug_state_deadline; |
| static int debug_button_hit_count; |
| |
| static int debug_button_mask(void) |
| { |
| int mask = 0; |
| |
| /* Get power button state */ |
| if (power_button_is_pressed()) |
| mask |= DEBUG_BTN_POWER; |
| |
| /* Get volume up state */ |
| if (state[BUTTON_VOLUME_UP].debounced_pressed) |
| mask |= DEBUG_BTN_VOL_UP; |
| |
| /* Get volume down state */ |
| if (state[BUTTON_VOLUME_DOWN].debounced_pressed) |
| mask |= DEBUG_BTN_VOL_DN; |
| |
| return mask; |
| } |
| |
| static int debug_button_pressed(int mask) |
| { |
| return debug_button_mask() == mask; |
| } |
| |
| static int debug_mode_blink_led(void) |
| { |
| return ((curr_debug_state != STATE_DEBUG_NONE) && |
| (curr_debug_state != STATE_DEBUG_CHECK)); |
| } |
| |
| static void debug_mode_transition(enum debug_state next_state) |
| { |
| timestamp_t now = get_time(); |
| #ifdef CONFIG_LED_COMMON |
| int curr_blink_state = debug_mode_blink_led(); |
| #endif |
| |
| /* Cancel any deferred calls. */ |
| hook_call_deferred(&debug_mode_handle_data, -1); |
| |
| /* Update current debug mode state. */ |
| curr_debug_state = next_state; |
| |
| /* Set deadline to 10seconds from current time. */ |
| debug_state_deadline.val = now.val + DEBUG_TIMEOUT; |
| |
| switch (curr_debug_state) { |
| case STATE_DEBUG_NONE: |
| /* |
| * Nothing is done here since some states can transition to |
| * STATE_DEBUG_NONE in this function. Wait until all other |
| * states are evaluated to take the action for STATE_NONE. |
| */ |
| break; |
| case STATE_DEBUG_CHECK: |
| case STATE_STAGING: |
| break; |
| case STATE_DEBUG_MODE_ACTIVE: |
| debug_button_hit_count = 0; |
| break; |
| case STATE_SYSRQ_PATH: |
| /* |
| * Increment debug_button_hit_count and ensure it does not go |
| * past 3. If it exceeds the limit transition to STATE_NONE. |
| */ |
| debug_button_hit_count++; |
| if (debug_button_hit_count == 4) |
| curr_debug_state = STATE_DEBUG_NONE; |
| break; |
| case STATE_WARM_RESET_PATH: |
| break; |
| case STATE_SYSRQ_EXEC: |
| /* |
| * Depending upon debug_button_hit_count, send appropriate |
| * number of sysrq events to host and transition to STATE_NONE. |
| */ |
| while (debug_button_hit_count) { |
| host_send_sysrq('x'); |
| CPRINTS("DEBUG MODE: sysrq-x sent"); |
| debug_button_hit_count--; |
| } |
| curr_debug_state = STATE_DEBUG_NONE; |
| break; |
| case STATE_WARM_RESET_EXEC: |
| /* Warm reset the host and transition to STATE_NONE. */ |
| chipset_reset(CHIPSET_RESET_DBG_WARM_REBOOT); |
| CPRINTS("DEBUG MODE: Warm reset triggered"); |
| curr_debug_state = STATE_DEBUG_NONE; |
| break; |
| default: |
| curr_debug_state = STATE_DEBUG_NONE; |
| } |
| |
| if (curr_debug_state != STATE_DEBUG_NONE) { |
| /* |
| * Schedule a deferred call after DEBUG_TIMEOUT to check for |
| * button state if it does not change during the timeout |
| * duration. |
| */ |
| hook_call_deferred(&debug_mode_handle_data, DEBUG_TIMEOUT); |
| return; |
| } |
| |
| /* If state machine reached initial state, reset all variables. */ |
| CPRINTS("DEBUG MODE: Exit!"); |
| next_debug_state = STATE_DEBUG_NONE; |
| debug_state_deadline.val = 0; |
| debug_button_hit_count = 0; |
| #ifdef CONFIG_LED_COMMON |
| if (curr_blink_state) |
| led_control(EC_LED_ID_SYSRQ_DEBUG_LED, LED_STATE_RESET); |
| #endif |
| } |
| |
| static void debug_mode_handle(void) |
| { |
| int mask; |
| |
| switch (curr_debug_state) { |
| case STATE_DEBUG_NONE: |
| /* |
| * If user pressed Vup+Vdn, check for next 10 seconds to see if |
| * user keeps holding the keys. |
| */ |
| if (debug_button_pressed(DEBUG_BTN_VOL_UP | DEBUG_BTN_VOL_DN)) |
| debug_mode_transition(STATE_DEBUG_CHECK); |
| break; |
| case STATE_DEBUG_CHECK: |
| /* |
| * If no key is pressed or any key combo other than Vup+Vdn is |
| * held, then quit debug check mode. |
| */ |
| if (!debug_button_pressed(DEBUG_BTN_VOL_UP | DEBUG_BTN_VOL_DN)) |
| debug_mode_transition(STATE_DEBUG_NONE); |
| else if (timestamp_expired(debug_state_deadline, NULL)) { |
| /* |
| * If Vup+Vdn are held down for 10 seconds, then its |
| * time to enter debug mode. |
| */ |
| CPRINTS("DEBUG MODE: Active!"); |
| next_debug_state = STATE_DEBUG_MODE_ACTIVE; |
| debug_mode_transition(STATE_STAGING); |
| } |
| break; |
| case STATE_STAGING: |
| mask = debug_button_mask(); |
| |
| /* If no button is pressed, transition to next state. */ |
| if (!mask) { |
| debug_mode_transition(next_debug_state); |
| return; |
| } |
| |
| /* Exit debug mode if keys are stuck for > 10 seconds. */ |
| if (timestamp_expired(debug_state_deadline, NULL)) |
| debug_mode_transition(STATE_DEBUG_NONE); |
| else { |
| timestamp_t now = get_time(); |
| |
| /* |
| * Schedule a deferred call in case timeout hasn't |
| * occurred yet. |
| */ |
| hook_call_deferred(&debug_mode_handle_data, |
| (debug_state_deadline.val - now.val)); |
| } |
| |
| break; |
| case STATE_DEBUG_MODE_ACTIVE: |
| mask = debug_button_mask(); |
| |
| /* |
| * Continue in this state if button is not pressed and timeout |
| * has not occurred. |
| */ |
| if (!mask && !timestamp_expired(debug_state_deadline, NULL)) |
| return; |
| |
| /* Exit debug mode if valid buttons are not pressed. */ |
| if ((mask != DEBUG_BTN_VOL_UP) && (mask != DEBUG_BTN_VOL_DN)) { |
| debug_mode_transition(STATE_DEBUG_NONE); |
| return; |
| } |
| |
| /* |
| * Transition to STAGING state with next state set to: |
| * 1. SYSRQ_PATH : If Vup was pressed. |
| * 2. WARM_RESET_PATH: If Vdn was pressed. |
| */ |
| if (mask == DEBUG_BTN_VOL_UP) |
| next_debug_state = STATE_SYSRQ_PATH; |
| else |
| next_debug_state = STATE_WARM_RESET_PATH; |
| |
| debug_mode_transition(STATE_STAGING); |
| break; |
| case STATE_SYSRQ_PATH: |
| mask = debug_button_mask(); |
| |
| /* |
| * Continue in this state if button is not pressed and timeout |
| * has not occurred. |
| */ |
| if (!mask && !timestamp_expired(debug_state_deadline, NULL)) |
| return; |
| |
| /* Exit debug mode if valid buttons are not pressed. */ |
| if ((mask != DEBUG_BTN_VOL_UP) && (mask != DEBUG_BTN_VOL_DN)) { |
| debug_mode_transition(STATE_DEBUG_NONE); |
| return; |
| } |
| |
| if (mask == DEBUG_BTN_VOL_UP) { |
| /* |
| * Else transition to STAGING state with next state set |
| * to SYSRQ_PATH. |
| */ |
| next_debug_state = STATE_SYSRQ_PATH; |
| } else { |
| /* |
| * Else if Vdn is pressed, transition to STAGING with |
| * next state set to SYSRQ_EXEC. |
| */ |
| next_debug_state = STATE_SYSRQ_EXEC; |
| } |
| debug_mode_transition(STATE_STAGING); |
| break; |
| case STATE_WARM_RESET_PATH: |
| mask = debug_button_mask(); |
| |
| /* |
| * Continue in this state if button is not pressed and timeout |
| * has not occurred. |
| */ |
| if (!mask && !timestamp_expired(debug_state_deadline, NULL)) |
| return; |
| |
| /* Exit debug mode if valid buttons are not pressed. */ |
| if (mask != DEBUG_BTN_VOL_UP) { |
| debug_mode_transition(STATE_DEBUG_NONE); |
| return; |
| } |
| |
| next_debug_state = STATE_WARM_RESET_EXEC; |
| debug_mode_transition(STATE_STAGING); |
| break; |
| case STATE_SYSRQ_EXEC: |
| case STATE_WARM_RESET_EXEC: |
| default: |
| debug_mode_transition(STATE_DEBUG_NONE); |
| break; |
| } |
| } |
| |
| #ifdef CONFIG_LED_COMMON |
| static void debug_led_tick(void) |
| { |
| static int led_state = LED_STATE_OFF; |
| |
| if (debug_mode_blink_led()) { |
| led_state = !led_state; |
| led_control(EC_LED_ID_SYSRQ_DEBUG_LED, led_state); |
| } |
| } |
| DECLARE_HOOK(HOOK_TICK, debug_led_tick, HOOK_PRIO_DEFAULT); |
| #endif /* CONFIG_LED_COMMON */ |
| |
| #endif /* !CONFIG_DEDICATED_RECOVERY_BUTTON */ |
| #endif /* CONFIG_EMULATED_SYSRQ */ |
| |
| #if defined(CONFIG_VOLUME_BUTTONS) && defined(CONFIG_DEDICATED_RECOVERY_BUTTON) |
| #error "A dedicated recovery button is not needed if you have volume buttons." |
| #endif /* defined(CONFIG_VOLUME_BUTTONS && CONFIG_DEDICATED_RECOVERY_BUTTON) */ |
| |
| const struct button_config buttons[BUTTON_COUNT] = { |
| #ifdef CONFIG_VOLUME_BUTTONS |
| [BUTTON_VOLUME_UP] = { |
| .name = "Volume Up", |
| .type = KEYBOARD_BUTTON_VOLUME_UP, |
| .gpio = GPIO_VOLUME_UP_L, |
| .debounce_us = 30 * MSEC, |
| .flags = 0, |
| }, |
| |
| [BUTTON_VOLUME_DOWN] = { |
| .name = "Volume Down", |
| .type = KEYBOARD_BUTTON_VOLUME_DOWN, |
| .gpio = GPIO_VOLUME_DOWN_L, |
| .debounce_us = 30 * MSEC, |
| .flags = 0, |
| }, |
| |
| #elif defined(CONFIG_DEDICATED_RECOVERY_BUTTON) |
| [BUTTON_RECOVERY] = { |
| .name = "Recovery", |
| .type = KEYBOARD_BUTTON_RECOVERY, |
| .gpio = GPIO_RECOVERY_L, |
| .debounce_us = 30 * MSEC, |
| .flags = 0, |
| } |
| #endif /* defined(CONFIG_DEDICATED_RECOVERY_BUTTON) */ |
| }; |
| |
| #ifdef CONFIG_BUTTON_TRIGGERED_RECOVERY |
| const struct button_config *recovery_buttons[] = { |
| #ifdef CONFIG_DEDICATED_RECOVERY_BUTTON |
| &buttons[BUTTON_RECOVERY], |
| |
| #elif defined(CONFIG_VOLUME_BUTTONS) |
| &buttons[BUTTON_VOLUME_DOWN], |
| &buttons[BUTTON_VOLUME_UP], |
| #endif /* defined(CONFIG_VOLUME_BUTTONS) */ |
| }; |
| const int recovery_buttons_count = ARRAY_SIZE(recovery_buttons); |
| #endif /* defined(CONFIG_BUTTON_TRIGGERED_RECOVERY) */ |