summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board/host/board.c18
-rw-r--r--board/host/board.h2
-rw-r--r--common/build.mk1
-rw-r--r--common/button.c127
-rw-r--r--common/keyboard_8042.c3
-rw-r--r--include/button.h20
-rw-r--r--include/config.h8
-rw-r--r--include/keyboard_8042.h8
-rw-r--r--test/build.mk3
-rw-r--r--test/button.c180
-rw-r--r--test/button.tasklist17
-rw-r--r--test/test_config.h4
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 */