diff options
-rw-r--r-- | board/host/board.c | 18 | ||||
-rw-r--r-- | board/host/board.h | 2 | ||||
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/button.c | 127 | ||||
-rw-r--r-- | common/keyboard_8042.c | 3 | ||||
-rw-r--r-- | include/button.h | 20 | ||||
-rw-r--r-- | include/config.h | 8 | ||||
-rw-r--r-- | include/keyboard_8042.h | 8 | ||||
-rw-r--r-- | test/build.mk | 3 | ||||
-rw-r--r-- | test/button.c | 180 | ||||
-rw-r--r-- | test/button.tasklist | 17 | ||||
-rw-r--r-- | test/test_config.h | 4 |
12 files changed, 389 insertions, 2 deletions
diff --git a/board/host/board.c b/board/host/board.c index 3fdc75f163..5d47f0d8df 100644 --- a/board/host/board.c +++ b/board/host/board.c @@ -4,11 +4,13 @@ */ /* Emulator board-specific configuration */ +#include "button.h" #include "extpower.h" #include "gpio.h" #include "lid_switch.h" #include "power_button.h" #include "temp_sensor.h" +#include "timer.h" #include "util.h" #define MOCK_GPIO(x) {#x, 0, 0, 0, 0} @@ -23,6 +25,8 @@ const struct gpio_info gpio_list[] = { MOCK_GPIO_INT(AC_PRESENT, GPIO_INT_BOTH, extpower_interrupt), MOCK_GPIO(PCH_BKLTEN), MOCK_GPIO(ENABLE_BACKLIGHT), + MOCK_GPIO_INT(BUTTON_VOLUME_DOWN_L, GPIO_INT_BOTH, button_interrupt), + MOCK_GPIO_INT(BUTTON_VOLUME_UP, GPIO_INT_BOTH, button_interrupt), }; BUILD_ASSERT(ARRAY_SIZE(gpio_list) == GPIO_COUNT); @@ -44,3 +48,17 @@ const struct temp_sensor_t temp_sensors[] = { {"Battery", TEMP_SENSOR_TYPE_BOARD, dummy_temp_get_val, 3, 0}, }; BUILD_ASSERT(ARRAY_SIZE(temp_sensors) == TEMP_SENSOR_COUNT); + +test_mockable void button_interrupt(enum gpio_signal signal) +{ +}; + +#ifdef CONFIG_BUTTON_COUNT +const struct button_config buttons[] = { + {"Volume Down", KEYBOARD_BUTTON_VOLUME_DOWN, GPIO_BUTTON_VOLUME_DOWN_L, + 30 * MSEC, 0}, + {"Volume Up", KEYBOARD_BUTTON_VOLUME_UP, GPIO_BUTTON_VOLUME_UP, + 60 * MSEC, BUTTON_FLAG_ACTIVE_HIGH}, +}; +BUILD_ASSERT(ARRAY_SIZE(buttons) == CONFIG_BUTTON_COUNT); +#endif diff --git a/board/host/board.h b/board/host/board.h index 543f25b6b6..a0c47fcee7 100644 --- a/board/host/board.h +++ b/board/host/board.h @@ -29,6 +29,8 @@ enum gpio_signal { GPIO_AC_PRESENT, GPIO_PCH_BKLTEN, GPIO_ENABLE_BACKLIGHT, + GPIO_BUTTON_VOLUME_DOWN_L, + GPIO_BUTTON_VOLUME_UP, GPIO_COUNT }; diff --git a/common/build.mk b/common/build.mk index 21814a5d83..dd1b592828 100644 --- a/common/build.mk +++ b/common/build.mk @@ -20,6 +20,7 @@ common-$(CONFIG_BACKLIGHT_LID)+=backlight_lid.o # command? common-$(CONFIG_BATTERY_BQ27541)+=battery.o common-$(CONFIG_BATTERY_SMART)+=battery.o +common-$(CONFIG_BUTTON_COUNT)+=button.o common-$(CONFIG_CHARGER)+=charge_state.o charger.o # TODO(crosbug.com/p/23815): This is really the charge state machine # for ARM, not the charger driver for the tps65090. Rename. diff --git a/common/button.c b/common/button.c new file mode 100644 index 0000000000..2e48102dd3 --- /dev/null +++ b/common/button.c @@ -0,0 +1,127 @@ +/* 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 CPRINTF(format, args...) cprintf(CC_SWITCH, format, ## args) + +struct button_state_t { + uint64_t debounce_time; + int debounced_pressed; +}; + +static struct button_state_t state[CONFIG_BUTTON_COUNT]; + +static uint64_t 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; + + CPRINTF("[%T (re)initializing buttons and interrupts.]\n"); + 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; + CPRINTF("[%T Button '%s' was %s]\n", + buttons[i].name, new_pressed ? + "pressed" : "released"); + keyboard_update_button(buttons[i].type, + new_pressed); + } + + /* 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; + } +} diff --git a/common/keyboard_8042.c b/common/keyboard_8042.c index bf3cc3fdd9..ddce39973f 100644 --- a/common/keyboard_8042.c +++ b/common/keyboard_8042.c @@ -929,7 +929,8 @@ void keyboard_protocol_task(void) * @param button Type of button that changed * @param is_pressed Whether the button was pressed or released */ -void keyboard_update_button(enum keyboard_button_type button, int is_pressed) +test_mockable void keyboard_update_button(enum keyboard_button_type button, + int is_pressed) { /* TODO(crosbug.com/p/24956): Add typematic repeat support. */ diff --git a/include/button.h b/include/button.h index e8344b308f..eedf7974f8 100644 --- a/include/button.h +++ b/include/button.h @@ -21,4 +21,24 @@ enum keyboard_button_type { KEYBOARD_BUTTON_COUNT }; +struct button_config { + const char *name; + enum keyboard_button_type type; + enum gpio_signal gpio; + uint32_t debounce_us; + int flags; +}; + +/* + * Defined in board.c. Should be CONFIG_BUTTON_COUNT elements long. + */ +extern const struct button_config buttons[]; + +/* + * Interrupt handler for button. + * + * @param signal Signal which triggered the interrupt. + */ +void button_interrupt(enum gpio_signal signal); + #endif /* __CROS_EC_BUTTON_H */ diff --git a/include/config.h b/include/config.h index 4ae871112f..7bd77d0fa7 100644 --- a/include/config.h +++ b/include/config.h @@ -138,6 +138,14 @@ #undef CONFIG_BOOTCFG_VALUE /*****************************************************************************/ + +/* + * Number of extra buttons not on the keyboard scan matrix. Doesn't include + * the power button, which has its own handler. + */ +#undef CONFIG_BUTTON_COUNT + +/*****************************************************************************/ /* Charger config */ /* Compile common charge state code */ diff --git a/include/keyboard_8042.h b/include/keyboard_8042.h index b0a247893f..2d58c4c184 100644 --- a/include/keyboard_8042.h +++ b/include/keyboard_8042.h @@ -9,6 +9,14 @@ #define __CROS_EC_KEYBOARD_8042_H #include "common.h" +#include "button.h" + +/** + * Called by power button handler and button interrupt handler. + * + * This function sends the corresponding make or break code to the host. + */ +void button_state_changed(enum keyboard_button_type button, int is_pressed); /** * Notify the keyboard module when a byte is written by the host. diff --git a/test/build.mk b/test/build.mk index b1efd12134..7ea82cc00c 100644 --- a/test/build.mk +++ b/test/build.mk @@ -22,9 +22,10 @@ test-list-$(BOARD_SAMUS)= test-list-host=mutex pingpong utils kb_scan kb_mkbp lid_sw power_button hooks test-list-host+=thermal flash queue kb_8042 extpwr_gpio console_edit system test-list-host+=sbs_charging adapter host_command thermal_falco led_spring -test-list-host+=bklight_lid bklight_passthru interrupt timer_dos +test-list-host+=bklight_lid bklight_passthru interrupt timer_dos button adapter-y=adapter.o +button-y=button.o bklight_lid-y=bklight_lid.o bklight_passthru-y=bklight_passthru.o console_edit-y=console_edit.o diff --git a/test/button.c b/test/button.c new file mode 100644 index 0000000000..5f18effb0c --- /dev/null +++ b/test/button.c @@ -0,0 +1,180 @@ +/* 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. + * + * Test non-keyboard buttons. +* + * Using GPIOS and buttons[] defined in board/host/board.c + * Volume down is active low with a debounce time of 30 mSec. + * Volume up is active high with a debounce time of 60 mSec. + * + */ + +#include "button.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "test_util.h" +#include "timer.h" +#include "keyboard_protocol.h" + +#define INDEX_VOL_DOWN 0 +#define INDEX_VOL_UP 1 +#define UNCHANGED -1 + +static const struct button_config *button_vol_down = &buttons[INDEX_VOL_DOWN]; +static const struct button_config *button_vol_up = &buttons[INDEX_VOL_UP]; + +static int button_state[CONFIG_BUTTON_COUNT]; + +/* + * Callback from the button handling logic. + * This is normally implemented by a keyboard protocol handler. + */ +void keyboard_update_button(enum keyboard_button_type button, int is_pressed) +{ + int i; + + for (i = 0; i < CONFIG_BUTTON_COUNT; i++) { + if (buttons[i].type == button) { + button_state[i] = is_pressed; + break; + } + } +} + +/* Test pressing a button */ +static int test_button_press(void) +{ + gpio_set_level(button_vol_down->gpio, 0); + msleep(100); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + + return EC_SUCCESS; +} + +/* Test releasing a button */ +static int test_button_release(void) +{ + gpio_set_level(button_vol_up->gpio, 1); + msleep(100); + gpio_set_level(button_vol_up->gpio, 0); + msleep(100); + TEST_ASSERT(button_state[INDEX_VOL_UP] == 0); + + return EC_SUCCESS; +} + +/* A press shorter than the debounce time should not trigger an update */ +static int test_button_debounce_short_press(void) +{ + gpio_set_level(button_vol_down->gpio, 0); + msleep(10); + gpio_set_level(button_vol_down->gpio, 1); + msleep(100); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + + return EC_SUCCESS; +} + +/* A short bounce while pressing should still result in a button press */ +static int test_button_debounce_short_bounce(void) +{ + gpio_set_level(button_vol_down->gpio, 0); + msleep(10); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + gpio_set_level(button_vol_down->gpio, 1); + msleep(10); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + gpio_set_level(button_vol_down->gpio, 0); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + + return EC_SUCCESS; +} + +/* Button level must be stable for the entire debounce interval */ +static int test_button_debounce_stability(void) +{ + gpio_set_level(button_vol_down->gpio, 0); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + gpio_set_level(button_vol_down->gpio, 1); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + gpio_set_level(button_vol_down->gpio, 0); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + msleep(60); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + gpio_set_level(button_vol_down->gpio, 1); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + msleep(20); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 0); + msleep(60); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 0); + + return EC_SUCCESS; +} + +/* Test pressing both buttons at different times */ +static int test_button_press_both(void) +{ + gpio_set_level(button_vol_down->gpio, 0); + msleep(10); + gpio_set_level(button_vol_up->gpio, 1); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == UNCHANGED); + TEST_ASSERT(button_state[INDEX_VOL_UP] == UNCHANGED); + msleep(30); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + TEST_ASSERT(button_state[INDEX_VOL_UP] == UNCHANGED); + msleep(40); + TEST_ASSERT(button_state[INDEX_VOL_DOWN] == 1); + TEST_ASSERT(button_state[INDEX_VOL_UP] == 1); + + return EC_SUCCESS; +} + +static void button_init(void) +{ + int i; + + ccprintf("[%T Setting button GPIOs to inactive state.]\n"); + for (i = 0; i < CONFIG_BUTTON_COUNT; i++) + gpio_set_level(buttons[i].gpio, + !(buttons[i].flags & BUTTON_FLAG_ACTIVE_HIGH)); + + msleep(100); + for (i = 0; i < CONFIG_BUTTON_COUNT; i++) + button_state[i] = UNCHANGED; +} + +void run_test(void) +{ + test_reset(); + + button_init(); + RUN_TEST(test_button_press); + + button_init(); + RUN_TEST(test_button_release); + + button_init(); + RUN_TEST(test_button_debounce_short_press); + + button_init(); + RUN_TEST(test_button_debounce_short_bounce); + + button_init(); + RUN_TEST(test_button_debounce_stability); + + button_init(); + RUN_TEST(test_button_press_both); + + test_print_result(); +} diff --git a/test/button.tasklist b/test/button.tasklist new file mode 100644 index 0000000000..26cfc53453 --- /dev/null +++ b/test/button.tasklist @@ -0,0 +1,17 @@ +/* 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. + */ + +/** + * List of enabled tasks in the priority order + * + * The first one has the lowest priority. + * + * For each task, use the macro TASK_TEST(n, r, d, s) where : + * 'n' in the name of the task + * 'r' in the main routine of the task + * 'd' in an opaque parameter passed to the routine at startup + * 's' is the stack size in bytes; must be a multiple of 8 + */ +#define CONFIG_TEST_TASK_LIST /* No test task */ diff --git a/test/test_config.h b/test/test_config.h index d88e71ced9..336ab5443a 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -82,5 +82,9 @@ int board_discharge_on_ac(int enabled); #define I2C_PORT_MASTER 1 #endif +#ifdef TEST_BUTTON +#define CONFIG_BUTTON_COUNT 2 +#endif + #endif /* TEST_BUILD */ #endif /* __CROS_EC_TEST_CONFIG_H */ |