summaryrefslogtreecommitdiff
path: root/baseboard/honeybuns
diff options
context:
space:
mode:
authorScott Collyer <scollyer@google.com>2021-03-25 19:47:14 -0700
committerCommit Bot <commit-bot@chromium.org>2021-04-10 21:48:43 +0000
commit62aa709f472b6a4021ccb3396701b2a2036caea7 (patch)
tree29b73d765523ef973c0015fcfa67d34abe661ff6 /baseboard/honeybuns
parent8113e3d1f538039477ec4b1461031da770931055 (diff)
downloadchrome-ec-62aa709f472b6a4021ccb3396701b2a2036caea7.tar.gz
honeybuns: Add power button support
This CL adds support for the power button including a new task to allow for power sequencing delays. The power button is used for two purposes. First, to turn the dock on or off, and second, to allow for a user set preference of the MF for DP 2 or 4 lane selection. If the dock is off, the dock will be turned on as soon as the short press timer expires. If the dock is already on, then a short press action is only recognized on the release so a long press will only change the MF preference. BUG=b:164157329 BRANCH=quiche TEST=manaual short press -> turns dock on off as expected long press -> toggles MF preference and flashes LED Signed-off-by: Scott Collyer <scollyer@google.com> Change-Id: I8519c072a7f10657c369344ead6149fc7d31bb36 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2718268 Tested-by: Scott Collyer <scollyer@chromium.org> Reviewed-by: Sam Hurst <shurst@google.com> Commit-Queue: Scott Collyer <scollyer@chromium.org>
Diffstat (limited to 'baseboard/honeybuns')
-rw-r--r--baseboard/honeybuns/baseboard.c377
-rw-r--r--baseboard/honeybuns/baseboard.h4
2 files changed, 364 insertions, 17 deletions
diff --git a/baseboard/honeybuns/baseboard.c b/baseboard/honeybuns/baseboard.c
index 3b6d4ba626..3425417b81 100644
--- a/baseboard/honeybuns/baseboard.c
+++ b/baseboard/honeybuns/baseboard.c
@@ -11,24 +11,81 @@
#include "i2c.h"
#include "usb_pd.h"
#include "system.h"
+#include "task.h"
#include "timer.h"
+#include "usbc_ppc.h"
+#include "driver/tcpm/tcpm.h"
#include "util.h"
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args)
+#define POWER_BUTTON_SHORT_USEC (300 * MSEC)
+#define POWER_BUTTON_LONG_USEC (5000 * MSEC)
+#define POWER_BUTTON_DEBOUNCE_USEC (30)
+
+#define BUTTON_PRESSED_LEVEL 1
+#define BUTTON_RELEASED_LEVEL 0
+
+#define BUTTON_EVT_CHANGE BIT(0)
+#define BUTTON_EVT_INFO BIT(1)
+
+enum power {
+ POWER_OFF,
+ POWER_ON
+};
+
+enum button {
+ BUTTON_RELEASE,
+ BUTTON_PRESS,
+ BUTTON_PRESS_POWER_ON,
+ BUTTON_PRESS_SHORT,
+ BUTTON_PRESS_LONG,
+};
+
+#define LED_ON_OFF_BIT BIT(0)
+#define LED_COLOR_BIT BIT(2)
+#define LED_FLASH_SEQ_LENGTH 8
+
+enum led_color {
+ GREEN,
+ YELLOW,
+ OFF,
+};
+
+static enum power dock_state;
+#ifdef SECTION_IS_RW
+static int button_level;
+static int button_level_pending;
+static int dock_mf;
+static int led_count;
+#endif
+
/******************************************************************************/
-static void board_power_sequence(void)
+__maybe_unused static void board_power_sequence(int enable)
{
int i;
- for(i = 0; i < board_power_seq_count; i++) {
- gpio_set_level(board_power_seq[i].signal,
- board_power_seq[i].level);
- if (board_power_seq[i].delay_ms)
- msleep(board_power_seq[i].delay_ms);
+ if (enable) {
+ for(i = 0; i < board_power_seq_count; i++) {
+ gpio_set_level(board_power_seq[i].signal,
+ board_power_seq[i].level);
+ CPRINTS("power seq: rail = %d", i);
+ if (board_power_seq[i].delay_ms)
+ msleep(board_power_seq[i].delay_ms);
+ }
+ } else {
+ for(i = board_power_seq_count - 1; i >= 0; i--) {
+ gpio_set_level(board_power_seq[i].signal,
+ !board_power_seq[i].level);
+ CPRINTS("sequence[%d]: level = %d", i,
+ !board_power_seq[i].level);
+ }
}
+
+ dock_state = enable;
+ CPRINTS("board: Power rails %s", dock_state ? "on" : "off");
}
/******************************************************************************/
@@ -39,31 +96,92 @@ const struct i2c_port_t i2c_ports[] = {
};
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);
+#ifdef SECTION_IS_RW
+static void baseboard_set_led(enum led_color color)
+{
+ /*
+ * TODO(b/164157329): The power button feature should be connected to a
+ * 2 color LED which is part of the button. Currently, the power button
+ * LED is a single color LED which is controlled by on the of the power
+ * rails. Using the status LED now to demonstrate the LED behavior
+ * associated with a power button press.
+ */
+ CPRINTS("led: color = %d", color);
+ if (color == OFF) {
+ gpio_set_level(GPIO_EC_STATUS_LED1, 1);
+ gpio_set_level(GPIO_EC_STATUS_LED2, 1);
+ } else if (color == GREEN) {
+ gpio_set_level(GPIO_EC_STATUS_LED1, 1);
+ gpio_set_level(GPIO_EC_STATUS_LED2, 0);
+ } else if (color == YELLOW) {
+ gpio_set_level(GPIO_EC_STATUS_LED1, 0);
+ gpio_set_level(GPIO_EC_STATUS_LED2, 0);
+ }
+}
+
+static void baseboard_led_callback(void);
+DECLARE_DEFERRED(baseboard_led_callback);
-void baseboard_set_mst_lane_control(int dock_mf)
+static void baseboard_led_callback(void)
{
- /* Check if lane swich level is changing */
- if (dock_mf != gpio_get_level(GPIO_MST_HUB_LANE_SWITCH)) {
+ /*
+ * Flash LED on transition using a simple 3 bit counter. Bit 0 controls
+ * LED on/off and bit 2 controls which color to set during the on phase.
+ */
+ int color = led_count & LED_COLOR_BIT ? dock_mf : dock_mf ^ 1;
+
+ /*
+ * TODO(b/164157329): This function implements a simple flashing
+ * transition when the MF preference bit is changed via a long power
+ * button press sequence. This might need to move to the board function
+ * if not required/desired on all variants.
+ */
+
+ if (led_count & LED_ON_OFF_BIT)
+ baseboard_set_led(color);
+ else
+ baseboard_set_led(OFF);
+
+ /* Flash sequence is 8 steps */
+ if (++led_count < LED_FLASH_SEQ_LENGTH)
+ hook_call_deferred(&baseboard_led_callback_data, 150 * MSEC);
+}
+
+static void baseboard_change_mf_led(void)
+{
+ led_count = 0;
+ baseboard_led_callback();
+}
+
+void baseboard_set_mst_lane_control(int mf)
+{
+ /*
+ * The parameter mf reflects the desired lane control value. If the
+ * current value does not match the desired, then the MST hub must first
+ * be put into reset, so the MST hub will latch in the correct value
+ * when it's taken out of reset.
+ */
+ if (mf != gpio_get_level(GPIO_MST_HUB_LANE_SWITCH)) {
/* put MST into reset */
gpio_set_level(GPIO_MST_RST_L, 0);
msleep(1);
- gpio_set_level(GPIO_MST_HUB_LANE_SWITCH, dock_mf);
- CPRINTS("MST: lane control = %s", dock_mf ? "high" : "low");
+ gpio_set_level(GPIO_MST_HUB_LANE_SWITCH, mf);
+ CPRINTS("MST: lane control = %s", mf ? "high" : "low");
msleep(1);
/* lane control is set, take MST out of reset */
gpio_set_level(GPIO_MST_RST_L, 1);
}
}
+#endif /* SECTION_IS_RW */
static void baseboard_init(void)
{
#ifdef SECTION_IS_RW
- int rv;
uint32_t fw_config;
#endif
/* Turn on power rails */
- board_power_sequence();
+ board_power_sequence(1);
CPRINTS("board: Power rails enabled");
#ifdef SECTION_IS_RW
@@ -77,9 +195,16 @@ static void baseboard_init(void)
* field of the CBI. If this value is programmed, then make sure the
* MST_LANE_CONTROL gpio matches the mf bit.
*/
- rv = cbi_get_fw_config(&fw_config);
- if (!rv)
- baseboard_set_mst_lane_control(fw_config & 1);
+ if (cbi_get_fw_config(&fw_config)) {
+ dock_mf = CBI_FW_MF_PREFERENCE(fw_config);
+ baseboard_set_mst_lane_control(dock_mf);
+ }
+
+ /* Enable power button interrupt */
+ gpio_enable_interrupt(GPIO_PWR_BTN);
+ /* Set dock mf preference LED */
+ baseboard_set_led(dock_mf);
+
#else
/* Set up host port usbc to present Rd on CC lines */
if(baseboard_usbc_init(USB_PD_PORT_HOST))
@@ -91,3 +216,223 @@ static void baseboard_init(void)
* power sequencing as soon as I2C bus is initialized.
*/
DECLARE_HOOK(HOOK_INIT, baseboard_init, HOOK_PRIO_INIT_I2C + 1);
+
+#ifdef SECTION_IS_RW
+static void baseboard_power_on(void)
+{
+ int port_max = board_get_usb_pd_port_count();
+ int port;
+
+ /* Adjust system flags to full PPC init occurs */
+ system_clear_reset_flags(EC_RESET_FLAG_POWER_ON);
+ system_set_reset_flags(EC_RESET_FLAG_EFS);
+ /* Enable power rails and release reset signals */
+ board_power_sequence(1);
+ /*
+ * Lane control (realtek MST) must be set prior to releasing MST
+ * reset.
+ */
+ baseboard_set_mst_lane_control(dock_mf);
+ /*
+ * When the power to the PPC is turned off, then back on, the PPC will
+ * default into dead battery mode. Dead battery resistors are disabled
+ * as part of the full PPC intializaiton sequence. This is required to
+ * force a detach event with port parter which can be attached as usbc
+ * source when honeybuns power rails are off.
+ */
+ for (port = 0; port < port_max; port++) {
+ ppc_init(port);
+ msleep(1000);
+ /* Inform TC state machine that it can resume */
+ pd_set_suspend(port, 0);
+ }
+ /* Enable usbc interrupts */
+ board_enable_usbc_interrupts();
+}
+
+static void baseboard_power_off(void)
+{
+ int port_max = board_get_usb_pd_port_count();
+ int port;
+
+ /* Put ports in TC suspend state */
+ for (port = 0; port < port_max; port++)
+ pd_set_suspend(port, 1);
+
+ /* Disable ucpd peripheral (prevents interrupts) */
+ tcpm_release(USB_PD_PORT_HOST);
+ /* Disable PPC/TCPC interrupts */
+ board_disable_usbc_interrupts();
+ /* Go into power off state */
+ board_power_sequence(0);
+}
+
+static void baseboard_toggle_mf(void)
+{
+ uint32_t fw_config;
+
+ if (!cbi_get_fw_config(&fw_config)) {
+ /* Update the user MF preference stored in CBI */
+ fw_config ^= CBI_FW_MF_MASK;
+ cbi_set_fw_config(fw_config);
+ /* Update variable used to track user MF preference */
+ dock_mf = CBI_FW_MF_PREFERENCE(fw_config);
+ /* Flash led for visual indication of user MF change */
+ baseboard_change_mf_led();
+ /* Power the dock off, then on, to apply user preference */
+ baseboard_power_off();
+ baseboard_power_on();
+ }
+}
+
+/*
+ * Main task entry point for UCPD task
+ */
+void power_button_task(void *u)
+{
+ int timer_us = POWER_BUTTON_DEBOUNCE_USEC * 4;
+ enum button state = BUTTON_RELEASE;
+ uint32_t evt;
+
+ /*
+ * Capture current button level in case it's being pressed when the dock
+ * is powered on. Note timer_us is initialized for debounce time to
+ * double check.
+ */
+ button_level = gpio_get_level(GPIO_PWR_BTN);
+
+ while (1) {
+ evt = task_wait_event(timer_us);
+ timer_us = -1;
+
+ if (evt == BUTTON_EVT_INFO) {
+ /* Only used for console command for debug */
+ CPRINTS("pwrbtn: pwr = %d, state = %d, level = %d",
+ dock_state, state, button_level);
+ continue;
+ }
+
+ switch (state) {
+ case BUTTON_RELEASE:
+ /*
+ * Default wait state: Only need to check if the button
+ * is pressed and start the short press timer.
+ */
+ if (evt & BUTTON_EVT_CHANGE && button_level ==
+ BUTTON_PRESSED_LEVEL) {
+ state = BUTTON_PRESS;
+ timer_us = (POWER_BUTTON_SHORT_USEC -
+ POWER_BUTTON_DEBOUNCE_USEC);
+ }
+ break;
+ case BUTTON_PRESS:
+ /*
+ * Validate short press by ensuring that button is still
+ * pressed after short press timer expires.
+ */
+ if (evt & BUTTON_EVT_CHANGE &&
+ button_level == BUTTON_RELEASED_LEVEL) {
+ state = BUTTON_RELEASE;
+ } else {
+ /* Start long press timer */
+ timer_us = POWER_BUTTON_LONG_USEC -
+ POWER_BUTTON_SHORT_USEC;
+ /*
+ * If dock is currently off, then change to the
+ * power on state. If dock is already on, then
+ * advance to short press state.
+ */
+ if (dock_state == POWER_OFF) {
+ baseboard_power_on();
+ state = BUTTON_PRESS_POWER_ON;
+ } else {
+ state = BUTTON_PRESS_SHORT;
+ }
+ }
+ break;
+ case BUTTON_PRESS_POWER_ON:
+ /*
+ * Short press recognized and dock was just powered
+ * on. If button is no longer pressed, then just return
+ * to the default state. Else, button is still pressed
+ * after long press timer has expired.
+ */
+ if (evt & BUTTON_EVT_CHANGE &&
+ button_level == BUTTON_RELEASED_LEVEL) {
+ state = BUTTON_RELEASE;
+ } else {
+ state = BUTTON_PRESS_LONG;
+ baseboard_toggle_mf();
+ }
+ break;
+ case BUTTON_PRESS_SHORT:
+ /*
+ * Short press was recognized and dock power state was
+ * already on. If button is now released, then turn dock
+ * off.
+ */
+ if (evt & BUTTON_EVT_CHANGE &&
+ button_level == BUTTON_RELEASED_LEVEL) {
+ state = BUTTON_RELEASE;
+ baseboard_power_off();
+ } else {
+ state = BUTTON_PRESS_LONG;
+ baseboard_toggle_mf();
+ }
+ break;
+ case BUTTON_PRESS_LONG:
+ if (evt & BUTTON_EVT_CHANGE &&
+ button_level == BUTTON_RELEASED_LEVEL) {
+ state = BUTTON_RELEASE;
+ }
+ break;
+ }
+ }
+}
+
+static void baseboard_power_button_debounce(void)
+{
+ int level = gpio_get_level(GPIO_PWR_BTN);
+
+ /* Sanity check, level should be same after debounce interval */
+ if (level != button_level_pending)
+ return;
+
+ button_level = level;
+ task_set_event(TASK_ID_POWER_BUTTON, BUTTON_EVT_CHANGE);
+}
+DECLARE_DEFERRED(baseboard_power_button_debounce);
+
+void baseboard_power_button_evt(int level)
+{
+ button_level_pending = level;
+
+ hook_call_deferred(&baseboard_power_button_debounce_data,
+ POWER_BUTTON_DEBOUNCE_USEC);
+}
+
+static int command_pwr_btn(int argc, char **argv)
+{
+
+ if (argc == 1) {
+ task_set_event(TASK_ID_POWER_BUTTON, BUTTON_EVT_INFO);
+ return EC_SUCCESS;
+ }
+
+ if (!strcasecmp(argv[1], "on")) {
+ baseboard_power_on();
+ } else if (!strcasecmp(argv[1], "off")) {
+ baseboard_power_off();
+ } else if (!strcasecmp(argv[1], "mf")) {
+ baseboard_toggle_mf();
+ } else {
+ return EC_ERROR_PARAM1;
+ }
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(pwr_btn, command_pwr_btn,
+ "<on|off|mf>",
+ "Simulate Power Button Press");
+
+#endif
diff --git a/baseboard/honeybuns/baseboard.h b/baseboard/honeybuns/baseboard.h
index 0faf727ab8..76bcaf490d 100644
--- a/baseboard/honeybuns/baseboard.h
+++ b/baseboard/honeybuns/baseboard.h
@@ -87,7 +87,8 @@
#define CONFIG_CROS_BOARD_INFO
#define CONFIG_BOARD_VERSION_CBI
#define CONFIG_CMD_CBI
-#define CONFIG_CMD_CBI_SET
+#define CBI_FW_MF_MASK BIT(0)
+#define CBI_FW_MF_PREFERENCE(val) (val & (CBI_FW_MF_MASK))
/* USB Configuration */
#define CONFIG_USB
@@ -228,6 +229,7 @@ enum adc_channel {
extern const struct power_seq board_power_seq[];
extern const size_t board_power_seq_count;
+void baseboard_power_button_evt(int level);
/*
* Configure the host port to present Rd on both CC lines. This function is