| /* 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 "common.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "keyboard_protocol.h" |
| #include "timer.h" |
| #include "util.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[CONFIG_BUTTON_COUNT]; |
| |
| static uint64_t __bss_slow next_deferred_time; |
| |
| /* |
| * Whether a button is currently pressed. |
| */ |
| static int raw_button_pressed(const struct button_config *button) |
| { |
| int raw_value = gpio_get_level(button->gpio); |
| |
| return button->flags & BUTTON_FLAG_ACTIVE_HIGH ? |
| raw_value : !raw_value; |
| } |
| |
| /* |
| * Button initialization. |
| */ |
| static void button_init(void) |
| { |
| int i; |
| |
| CPRINTS("(re)initializing buttons and interrupts"); |
| next_deferred_time = 0; |
| for (i = 0; i < CONFIG_BUTTON_COUNT; i++) { |
| state[i].debounced_pressed = raw_button_pressed(&buttons[i]); |
| state[i].debounce_time = 0; |
| gpio_enable_interrupt(buttons[i].gpio); |
| } |
| } |
| DECLARE_HOOK(HOOK_INIT, button_init, HOOK_PRIO_DEFAULT); |
| |
| /* |
| * Handle debounced button changing state. |
| */ |
| 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 < CONFIG_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; |
| CPRINTS("Button '%s' was %s", |
| buttons[i].name, new_pressed ? |
| "pressed" : "released"); |
| #ifdef HAS_TASK_KEYPROTO |
| 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, |
| next_deferred_time - time_now); |
| } |
| } |
| DECLARE_DEFERRED(button_change_deferred); |
| |
| /* |
| * Handle a button interrupt. |
| */ |
| void button_interrupt(enum gpio_signal signal) |
| { |
| int i; |
| uint64_t time_now = get_time().val; |
| |
| for (i = 0; i < CONFIG_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, |
| next_deferred_time - time_now); |
| } |
| break; |
| } |
| } |