diff --git a/board/cr50/board.c b/board/cr50/board.c index ebe2f65937..6263232349 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -6,6 +6,7 @@ #include "common.h" #include "console.h" #include "dcrypto/dcrypto.h" +#include "device_state.h" #include "ec_version.h" #include "flash_config.h" #include "gpio.h" @@ -15,6 +16,7 @@ #include "nvmem.h" #include "task.h" #include "trng.h" +#include "uartn.h" #include "usb_descriptor.h" #include "usb_hid.h" #include "util.h" @@ -218,3 +220,178 @@ void nvmem_compute_sha(uint8_t *p_buf, int num_bytes, sha1_digest); memcpy(p_sha, sha1_digest, sha_len); } + +static void device_state_changed(enum device_type device, + enum device_state state) +{ + device_set_state(device, state); + + /* Disable interrupts */ + gpio_disable_interrupt(device_states[device].detect_on); + gpio_disable_interrupt(device_states[device].detect_off); + + /* + * We've determined the device state, so cancel any deferred callbacks. + */ + hook_call_deferred(device_states[device].deferred, -1); +} + +/* + * If both UARTs are enabled we cant tell anything about the + * state of servo, so disable servo detection. + */ +static int servo_state_unknown(void) +{ + if (uartn_enabled(UART_AP) && uartn_enabled(UART_EC)) { + device_state_changed(DEVICE_SERVO_EC, DEVICE_STATE_UNKNOWN); + device_state_changed(DEVICE_SERVO_AP, DEVICE_STATE_UNKNOWN); + return 1; + } + return 0; +} + +static void servo_deferred(void) +{ + if (servo_state_unknown() || + (device_get_state(DEVICE_SERVO_AP) == DEVICE_STATE_ON) || + (device_get_state(DEVICE_SERVO_EC) == DEVICE_STATE_ON)) + return; + + device_state_changed(DEVICE_SERVO_AP, DEVICE_STATE_OFF); + device_state_changed(DEVICE_SERVO_EC, DEVICE_STATE_OFF); +} +DECLARE_DEFERRED(servo_deferred); + +static void ap_deferred(void) +{ + if (device_get_state(DEVICE_AP) == DEVICE_STATE_ON) + return; + + device_state_changed(DEVICE_AP, DEVICE_STATE_OFF); +} +DECLARE_DEFERRED(ap_deferred); + +static void ec_deferred(void) +{ + if (device_get_state(DEVICE_EC) == DEVICE_STATE_ON) + return; + + device_state_changed(DEVICE_EC, DEVICE_STATE_OFF); +} +DECLARE_DEFERRED(ec_deferred); + +struct device_config device_states[] = { + [DEVICE_SERVO_AP] = { + .deferred = &servo_deferred_data, + .detect_on = GPIO_SERVO_UART1_ON, + .detect_off = GPIO_SERVO_UART1_OFF + }, + [DEVICE_SERVO_EC] = { + .deferred = &servo_deferred_data, + .detect_on = GPIO_SERVO_UART2_ON, + .detect_off = GPIO_SERVO_UART2_OFF + }, + [DEVICE_AP] = { + .deferred = &ap_deferred_data, + .detect_on = GPIO_AP_ON, + .detect_off = GPIO_AP_OFF + }, + [DEVICE_EC] = { + .deferred = &ec_deferred_data, + .detect_on = GPIO_EC_ON, + .detect_off = GPIO_EC_OFF + }, +}; +BUILD_ASSERT(ARRAY_SIZE(device_states) == DEVICE_COUNT); + +void device_state_on(enum gpio_signal signal) +{ + switch (signal) { + case GPIO_AP_ON: + device_state_changed(DEVICE_AP, DEVICE_STATE_ON); + break; + case GPIO_EC_ON: + device_state_changed(DEVICE_EC, DEVICE_STATE_ON); + break; + default: + if (servo_state_unknown()) + return; + device_state_changed(DEVICE_SERVO_EC, DEVICE_STATE_ON); + device_state_changed(DEVICE_SERVO_AP, DEVICE_STATE_ON); + } +} + +void device_state_off(enum gpio_signal signal) +{ + switch (signal) { + case GPIO_AP_OFF: + board_update_device_state(DEVICE_AP); + break; + case GPIO_EC_OFF: + board_update_device_state(DEVICE_EC); + break; + default: + board_update_device_state(DEVICE_SERVO_EC); + board_update_device_state(DEVICE_SERVO_AP); + } +} + +void board_update_device_state(enum device_type device) +{ + int state; + + if (device == DEVICE_SERVO_EC || device == DEVICE_SERVO_AP) { + /* + * If either AP UART TX or EC UART TX are pulled high when + * cr50 uart is not enabled, then servo is attached + */ + state = (!uartn_enabled(UART_AP) && + gpio_get_level(GPIO_SERVO_UART1_ON)) || + (!uartn_enabled(UART_EC) && + gpio_get_level(GPIO_SERVO_UART2_ON)); + } else + state = gpio_get_level(device_states[device].detect_on); + + /* + * If the device is currently on set its state immediately. If it + * thinks the device is powered off debounce the signal. + */ + if (state) + device_state_changed(device, DEVICE_STATE_ON); + else { + device_set_state(device, DEVICE_STATE_UNKNOWN); + + gpio_enable_interrupt(device_states[device].detect_on); + /* + * Wait a bit. If cr50 detects this device is ever powered on + * during this time then the status wont be set to powered off. + */ + hook_call_deferred(device_states[device].deferred, 50); + } +} + +static int command_devices(int argc, char **argv) +{ + int state = device_get_state(DEVICE_AP); + + ccprintf("AP %s\n", + state == DEVICE_STATE_ON ? "on" : + state == DEVICE_STATE_OFF ? "off" : "unknown"); + state = device_get_state(DEVICE_SERVO_AP); + ccprintf("SERVO_AP %s\n", + state == DEVICE_STATE_ON ? "on" : + state == DEVICE_STATE_OFF ? "off" : "unknown"); + state = device_get_state(DEVICE_SERVO_EC); + ccprintf("SERVO_EC %s\n", + state == DEVICE_STATE_ON ? "on" : + state == DEVICE_STATE_OFF ? "off" : "unknown"); + state = device_get_state(DEVICE_EC); + ccprintf("EC %s\n", + state == DEVICE_STATE_ON ? "on" : + state == DEVICE_STATE_OFF ? "off" : "unknown"); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(devices, command_devices, + "", + "Get the AP, EC, and servo device states", + NULL); diff --git a/board/cr50/board.h b/board/cr50/board.h index 56fdb7ab91..0aeffdc55b 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -35,6 +35,9 @@ /* Go to sleep when nothing else is happening */ #define CONFIG_LOW_POWER_IDLE +/* Detect the states of other devices */ +#define CONFIG_DEVICE_STATE + /* Enable debug cable detection */ #define CONFIG_RDD @@ -100,8 +103,20 @@ enum usb_strings { USB_STR_COUNT }; +/* Device indexes */ +enum device_type { + DEVICE_SERVO_AP = 0, + DEVICE_SERVO_EC, + DEVICE_AP, + DEVICE_EC, + + DEVICE_COUNT +}; + /* Interrupt handler */ void sys_rst_asserted(enum gpio_signal signal); +void device_state_on(enum gpio_signal signal); +void device_state_off(enum gpio_signal signal); #endif /* !__ASSEMBLER__ */ diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc index 20bda41d98..47a16478bc 100644 --- a/board/cr50/gpio.inc +++ b/board/cr50/gpio.inc @@ -14,8 +14,16 @@ * TODO: Remove this internal pullup at some point. It's only here so that * boards that don't have an external pullup don't trigger due to noise. */ -GPIO_INT(SYS_RST_L_IN, PIN(1, 0), GPIO_INT_FALLING | GPIO_PULL_UP, +GPIO_INT(SYS_RST_L_IN, PIN(1, 0), GPIO_INT_FALLING | GPIO_PULL_UP, sys_rst_asserted) +GPIO_INT(AP_ON, PIN(1, 1), GPIO_INT_RISING, device_state_on) +GPIO_INT(EC_ON, PIN(1, 2), GPIO_INT_RISING, device_state_on) +GPIO_INT(SERVO_UART1_ON, PIN(1, 3), GPIO_INT_RISING, device_state_on) +GPIO_INT(SERVO_UART2_ON, PIN(1, 4), GPIO_INT_RISING, device_state_on) +GPIO_INT(AP_OFF, PIN(1, 5), GPIO_INT_FALLING, device_state_off) +GPIO_INT(EC_OFF, PIN(1, 6), GPIO_INT_FALLING, device_state_off) +GPIO_INT(SERVO_UART1_OFF, PIN(1, 7), GPIO_INT_FALLING, device_state_off) +GPIO_INT(SERVO_UART2_OFF, PIN(1, 8), GPIO_INT_FALLING, device_state_off) /* Pull this low to interrupt the AP */ GPIO(INT_AP_L, PIN(0, 0), GPIO_INPUT) @@ -74,6 +82,15 @@ PINMUX(FUNC(UART0_RX), A1, DIO_INPUT | DIO_WAKE_LOW) PINMUX(FUNC(UART1_RX), A3, DIO_INPUT) /* AP console */ PINMUX(FUNC(UART2_RX), B6, DIO_INPUT) /* EC console */ +PINMUX(GPIO(AP_ON), A3, DIO_INPUT) +PINMUX(GPIO(AP_OFF), A3, DIO_INPUT) +PINMUX(GPIO(EC_ON), B6, DIO_INPUT) +PINMUX(GPIO(EC_OFF), B6, DIO_INPUT) +PINMUX(GPIO(SERVO_UART1_ON), A7, DIO_INPUT) +PINMUX(GPIO(SERVO_UART1_OFF), A7, DIO_INPUT) +PINMUX(GPIO(SERVO_UART2_ON), B5, DIO_INPUT) +PINMUX(GPIO(SERVO_UART2_OFF), B5, DIO_INPUT) + /* I2C pins are bi-directional */ PINMUX(FUNC(I2C0_SCL), B0, DIO_INPUT) PINMUX(FUNC(I2C0_SDA), B1, DIO_INPUT) diff --git a/board/cr50/rdd.c b/board/cr50/rdd.c index ed30094bf3..6fd76146cd 100644 --- a/board/cr50/rdd.c +++ b/board/cr50/rdd.c @@ -7,8 +7,17 @@ #include "gpio.h" #include "rdd.h" #include "registers.h" +#include "uartn.h" #include "usb_api.h" +/* If the UART TX is enabled the pinmux select will have a non-zero value */ +int uartn_enabled(int uart) +{ + if (uart == UART_AP) + return GREAD(PINMUX, DIOA7_SEL); + return GREAD(PINMUX, DIOB5_SEL); +} + static void usart_tx_connect(void) { GWRITE(PINMUX, DIOA7_SEL, GC_PINMUX_UART1_TX_SEL); @@ -17,7 +26,7 @@ static void usart_tx_connect(void) static void usart_tx_disconnect(void) { - GWRITE(PINMUX, DIOA7_SEL, GC_PINMUX_DIOA3_SEL_DEFAULT); + GWRITE(PINMUX, DIOA7_SEL, GC_PINMUX_DIOA7_SEL_DEFAULT); GWRITE(PINMUX, DIOB5_SEL, GC_PINMUX_DIOB5_SEL_DEFAULT); } @@ -44,19 +53,17 @@ void rdd_detached(void) static int command_uart(int argc, char **argv) { - static int enabled; - if (argc > 1) { if (!strcasecmp("enable", argv[1])) { - enabled = 1; usart_tx_connect(); } else if (!strcasecmp("disable", argv[1])) { - enabled = 0; usart_tx_disconnect(); } } - ccprintf("UART %s\n", enabled ? "enabled" : "disabled"); + ccprintf("EC UART %s\nAP UART %s\n", + uartn_enabled(UART_EC) ? "enabled" : "disabled", + uartn_enabled(UART_AP) ? "enabled" : "disabled"); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(uart, command_uart, diff --git a/chip/g/uartn.h b/chip/g/uartn.h index 12d0f53bff..d6acc29210 100644 --- a/chip/g/uartn.h +++ b/chip/g/uartn.h @@ -74,4 +74,7 @@ void uartn_tx_start(int uart); * Disable the UART transmit interrupt. */ void uartn_tx_stop(int uart); + +/* Get UART output status */ +int uartn_enabled(int uart); #endif /* __CROS_EC_UARTN_H */ diff --git a/common/build.mk b/common/build.mk index be72a44979..bfb4e6aef7 100644 --- a/common/build.mk +++ b/common/build.mk @@ -38,6 +38,7 @@ common-$(CONFIG_COMMON_PANIC_OUTPUT)+=panic_output.o common-$(CONFIG_COMMON_RUNTIME)+=hooks.o main.o system.o shared_mem.o common-$(CONFIG_COMMON_TIMER)+=timer.o common-$(CONFIG_CRC8)+= crc8.o +common-$(CONFIG_DEVICE_STATE)+=device_state.o common-$(CONFIG_EXTENSION_COMMAND)+=extension.o common-$(CONFIG_EXTPOWER_GPIO)+=extpower_gpio.o common-$(CONFIG_FANS)+=fan.o pwm.o diff --git a/common/device_state.c b/common/device_state.c new file mode 100644 index 0000000000..2b3f969d4c --- /dev/null +++ b/common/device_state.c @@ -0,0 +1,29 @@ +/* Copyright 2016 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. + */ + +#include "device_state.h" +#include "hooks.h" + +int device_get_state(enum device_type device) +{ + return device_states[device].state; +} + +void device_set_state(enum device_type device, enum device_state state) +{ + if (device_states[device].state == state) + return; + + device_states[device].state = state; +} + +static void check_device_state(void) +{ + int i; + + for (i = 0; i < DEVICE_COUNT; i++) + board_update_device_state(i); +} +DECLARE_HOOK(HOOK_SECOND, check_device_state, HOOK_PRIO_DEFAULT); diff --git a/include/config.h b/include/config.h index bedb99a6a3..2c4fa6ccc4 100644 --- a/include/config.h +++ b/include/config.h @@ -738,6 +738,9 @@ /*****************************************************************************/ +/* Monitor the states of other devices */ +#undef CONFIG_DEVICE_STATE + /* Support DMA transfers inside the EC */ #undef CONFIG_DMA diff --git a/include/device_state.h b/include/device_state.h new file mode 100644 index 0000000000..1af3972bfb --- /dev/null +++ b/include/device_state.h @@ -0,0 +1,36 @@ +/* Copyright 2016 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. + */ + +#include "gpio.h" + +#ifndef __CROS_DEVICE_STATE_H +#define __CROS_DEVICE_STATE_H + +/* Device state indexes */ +enum device_state { + DEVICE_STATE_UNKNOWN = 0, + DEVICE_STATE_OFF, + DEVICE_STATE_ON, + DEVICE_STATE_COUNT, +}; + +struct device_config { + enum device_state state; + int state_change; + const struct deferred_data *deferred; + enum gpio_signal detect_on; + enum gpio_signal detect_off; +}; + +enum device_type; + +extern struct device_config device_states[]; + +int device_get_state(enum device_type device); + +void device_set_state(enum device_type device, enum device_state state); + +void board_update_device_state(enum device_type device); +#endif /* __CROS_DEVICE_STATE_H */