summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Boichat <drinkcat@chromium.org>2017-09-05 10:10:29 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-10-25 03:58:50 -0700
commitab9084fd2a70a505e0e43b39e0ad40876e4853fc (patch)
treebf29e85cfbb3203dbf148dc2aecb82d7e0f793c6
parent28a5ad1646b2994853e310354b604547950a55c0 (diff)
downloadchrome-ec-ab9084fd2a70a505e0e43b39e0ad40876e4853fc.tar.gz
chip/npcx: Add support for pad-switching UART
NPCX5* only has one UART controller, which can be switched between 2 pads. We keep the default pad for EC console, however, we allow switching to the alternate pad for short, infrequent, transactions. Both pads are assumed to use the same baudrate and other line settings. When switching pad, we first configure the new pad, then switch off the old one, to avoid having no pad selected at a given time, see b/65526215#c26. Because of the added complexity of npcx_gpio2uart (and the fact that it uses the global variable "pad" define in uart.c), we move the implementation to uart.c (npcx_uart2gpio is also moved for consistency). When the pad is switched to alternate pad, characters input and output on the EC console (default pad) would be lost. To compensate for this, we: - Switch back to main pad in case of EC panic, so that output is shown on EC console. - Immediately abort current alternate pad transaction if a character is received on the default pad. Note, however, that the first character will be lost (this can be worked around by telling user to press enter, and have servod/FAFT always send 2 blank lines (instead of just one) before sending a command). - Inhibit pad switching for 500ms after receiving a character on default pad. Assuming a reasonable typing speed, this should allow developers to type console commands relatively comfortably, while not starving the alternate pad communication for too long. The logic above could be simplified significantly by implementing software flow control (XON/XOFF, see b/67026316). BRANCH=none BUG=b:65526215 TEST=While follow-up CL that writes long 1k buffers, the following works fine: - type 'uart' in EC console - Read battery power consumption from servod, which "types" in the EC console: while true; do dut-control ppvar_vbat_mw; sleep 1; done no failure is seen. TEST=Add this test code in uart_alt_pad_read_write, after the pad has been switched, and check that panic information is consistently printed correctly: { static int t; if (t++ > 20) t = t / ret; } Change-Id: I18feed2f8ca4eb85f40389f77dac3a46315310e7 Signed-off-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/659458 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--chip/npcx/clock.c3
-rw-r--r--chip/npcx/gpio_chip.h11
-rw-r--r--chip/npcx/registers.h26
-rw-r--r--chip/npcx/uart.c245
-rw-r--r--core/cortex-m/panic.c4
-rw-r--r--include/config.h7
-rw-r--r--include/uart.h43
7 files changed, 301 insertions, 38 deletions
diff --git a/chip/npcx/clock.c b/chip/npcx/clock.c
index 6ba326645f..8021f3c4d6 100644
--- a/chip/npcx/clock.c
+++ b/chip/npcx/clock.c
@@ -11,6 +11,7 @@
#include "console.h"
#include "cpu.h"
#include "gpio.h"
+#include "gpio_chip.h"
#include "hooks.h"
#include "hwtimer.h"
#include "hwtimer_chip.h"
@@ -259,7 +260,7 @@ void clock_gpio2uart(void)
clock_refresh_console_in_use();
/* Disable MIWU for GPIO (UARTRX) */
uart_enable_wakeup(0);
- /* Go back CR_SIN*/
+ /* Go back CR_SIN */
npcx_gpio2uart();
/* Enable uart again */
task_enable_irq(NPCX_IRQ_UART);
diff --git a/chip/npcx/gpio_chip.h b/chip/npcx/gpio_chip.h
index 1595f6b20d..b5fcec20c8 100644
--- a/chip/npcx/gpio_chip.h
+++ b/chip/npcx/gpio_chip.h
@@ -32,6 +32,17 @@
NPCX_LVOL_CTRL_##ctrl##_6, \
NPCX_LVOL_CTRL_##ctrl##_7, }
+/**
+ * Switch NPCX UART pins back to normal GPIOs.
+ */
+void npcx_uart2gpio(void);
+
+/**
+ * Switch NPCX UART pins to UART mode (depending on the currently selected
+ * pad, see uart.c).
+ */
+void npcx_gpio2uart(void);
+
/*
* Include the MIWU, alternative and low-Voltage macro functions for GPIOs
* depends on Nuvoton chip series.
diff --git a/chip/npcx/registers.h b/chip/npcx/registers.h
index 491a59950d..c79b3d5cbb 100644
--- a/chip/npcx/registers.h
+++ b/chip/npcx/registers.h
@@ -1844,33 +1844,7 @@ static inline int npcx_is_uart(void)
#endif
}
-/* This routine switches the functionality from UART rx to GPIO */
-static inline void npcx_uart2gpio(void)
-{
-#if NPCX_UART_MODULE2
- UPDATE_BIT(NPCX_WKEDG(1, 6), 4, 1);
- CLEAR_BIT(NPCX_DEVALT(0x0C), NPCX_DEVALTC_UART_SL2);
-#else
- UPDATE_BIT(NPCX_WKEDG(1, 1), 0, 1);
- CLEAR_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
#endif
-}
-#endif
-
-/* This routine switches the functionality from GPIO to UART rx */
-static inline void npcx_gpio2uart(void)
-{
-#if NPCX_UART_MODULE2
- CLEAR_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
- SET_BIT(NPCX_DEVALT(0x0C), NPCX_DEVALTC_UART_SL2);
-#else
-#if defined(CHIP_FAMILY_NPCX7)
- /* UART module 1 belongs to KSO since wake-up functionality in npcx7. */
- CLEAR_BIT(NPCX_DEVALT(0x09), NPCX_DEVALT9_NO_KSO09_SL);
-#endif
- SET_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
-#endif
-}
/* Wake pin definitions, defined at board-level */
extern const enum gpio_signal hibernate_wake_pins[];
diff --git a/chip/npcx/uart.c b/chip/npcx/uart.c
index eb6f6a4920..150b1c7cad 100644
--- a/chip/npcx/uart.c
+++ b/chip/npcx/uart.c
@@ -9,16 +9,95 @@
#include "common.h"
#include "console.h"
#include "gpio.h"
+#include "hwtimer.h"
+#include "hwtimer_chip.h"
#include "lpc.h"
#include "registers.h"
#include "clock_chip.h"
#include "system.h"
#include "task.h"
+#include "timer.h"
#include "uart.h"
#include "util.h"
static int init_done;
+#ifdef CONFIG_UART_PAD_SWITCH
+
+/* Current pad: 0 for default pad, 1 for alternate. */
+static volatile enum uart_pad pad;
+
+/*
+ * When switched to alternate pad, read/write data according to the parameters
+ * below.
+ */
+static uint8_t *altpad_rx_buf;
+static volatile int altpad_rx_pos;
+static int altpad_rx_len;
+static uint8_t *altpad_tx_buf;
+static volatile int altpad_tx_pos;
+static int altpad_tx_len;
+
+/*
+ * Time we last received a byte on default UART, we do not allow use of
+ * alternate pad for block_alt_timeout_us after that, to make sure input
+ * characters are not lost (either interactively, or though servod/FAFT).
+ */
+static timestamp_t last_default_pad_rx_time;
+
+static const uint32_t block_alt_timeout_us = 500*MSEC;
+
+#else
+
+/* Default pad is always selected. */
+static const enum uart_pad pad = UART_DEFAULT_PAD;
+
+#endif /* CONFIG_UART_PAD_SWITCH */
+
+#if defined(CHIP_FAMILY_NPCX5)
+/* This routine switches the functionality from UART rx to GPIO */
+void npcx_uart2gpio(void)
+{
+#if NPCX_UART_MODULE2
+ UPDATE_BIT(NPCX_WKEDG(1, 6), 4, 1);
+#else
+ UPDATE_BIT(NPCX_WKEDG(1, 1), 0, 1);
+#endif
+ CLEAR_BIT(NPCX_DEVALT(0x0C), NPCX_DEVALTC_UART_SL2);
+ CLEAR_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
+}
+#endif /* CHIP_FAMILY_NPCX5 */
+
+/*
+ * This routine switches the functionality from GPIO to UART rx, depending
+ * on the global variable "pad". It also deactivates the previous pad.
+ *
+ * Note that, when switching pad, we first configure the new pad, then switch
+ * off the old one, to avoid having no pad selected at a given time, see
+ * b/65526215#c26.
+ */
+void npcx_gpio2uart(void)
+{
+ if ((NPCX_UART_MODULE2 && (pad == UART_DEFAULT_PAD)) ||
+ (!NPCX_UART_MODULE2 && (pad == UART_ALTERNATE_PAD))) {
+ SET_BIT(NPCX_DEVALT(0x0C), NPCX_DEVALTC_UART_SL2);
+ CLEAR_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
+ } else {
+ SET_BIT(NPCX_DEVALT(0x0A), NPCX_DEVALTA_UART_SL1);
+ CLEAR_BIT(NPCX_DEVALT(0x0C), NPCX_DEVALTC_UART_SL2);
+
+ if (pad == UART_DEFAULT_PAD) {
+#if defined(CHIP_FAMILY_NPCX7)
+ /*
+ * UART module 1 belongs to KSO since wake-up
+ * functionality in npcx7.
+ */
+ CLEAR_BIT(NPCX_DEVALT(0x09), NPCX_DEVALT9_NO_KSO09_SL);
+#endif
+ }
+ }
+}
+
int uart_init_done(void)
{
return init_done;
@@ -28,7 +107,7 @@ void uart_tx_start(void)
{
/* We needn't to switch uart from gpio again in npcx7. */
#if defined(CHIP_FAMILY_NPCX5)
- if (uart_is_enable_wakeup()) {
+ if (uart_is_enable_wakeup() && pad == UART_DEFAULT_PAD) {
/* disable MIWU */
uart_enable_wakeup(0);
/* Set pin-mask for UART */
@@ -87,17 +166,24 @@ int uart_tx_in_progress(void)
int uart_rx_available(void)
{
- uint8_t ctrl = NPCX_UICTRL;
+ int rx_available = NPCX_UICTRL & 0x02;
+
+ if (rx_available) {
#ifdef CONFIG_LOW_POWER_IDLE
- /*
- * Activity seen on UART RX pin while UART was disabled for deep sleep.
- * The console won't see that character because the UART is disabled,
- * so we need to inform the clock module of UART activity ourselves.
- */
- if (ctrl & 0x02)
+ /*
+ * Activity seen on UART RX pin while UART was disabled for deep
+ * sleep. The console won't see that character because the UART
+ * is disabled, so we need to inform the clock module of UART
+ * activity ourselves.
+ */
clock_refresh_console_in_use();
#endif
- return ctrl & 0x02; /* If RX FIFO is empty return '0'*/
+#ifdef CONFIG_UART_PAD_SWITCH
+ if (pad == UART_DEFAULT_PAD)
+ last_default_pad_rx_time = get_time();
+#endif
+ }
+ return rx_available; /* If RX FIFO is empty return '0'. */
}
void uart_write_char(char c)
@@ -114,7 +200,7 @@ int uart_read_char(void)
return NPCX_URBUF;
}
-static void uart_clear_rx_fifo(int channel)
+void uart_clear_rx_fifo(int channel)
{
int scratch __attribute__ ((unused));
if (channel == 0) { /* suppose '0' is EC UART*/
@@ -129,12 +215,146 @@ static void uart_clear_rx_fifo(int channel)
*/
void uart_ec_interrupt(void)
{
+#ifdef CONFIG_UART_PAD_SWITCH
+ if (pad == UART_ALTERNATE_PAD) {
+ if (uart_rx_available()) {
+ uint8_t c = uart_read_char();
+
+ if (altpad_rx_pos < altpad_rx_len)
+ altpad_rx_buf[altpad_rx_pos++] = c;
+ }
+ if (uart_tx_ready()) {
+ if (altpad_tx_pos < altpad_tx_len)
+ uart_write_char(altpad_tx_buf[altpad_tx_pos++]);
+ else
+ uart_tx_stop();
+ }
+ return;
+ }
+#endif
+
+ /* Default pad. */
/* Read input FIFO until empty, then fill output FIFO */
uart_process_input();
uart_process_output();
}
DECLARE_IRQ(NPCX_IRQ_UART, uart_ec_interrupt, 0);
+#ifdef CONFIG_UART_PAD_SWITCH
+/*
+ * Switch back to default UART pad, without flushing RX/TX buffers: If we are
+ * about to panic, we just want to switch immmediately, and we don't care if we
+ * output a bit of garbage.
+ */
+void uart_reset_default_pad_panic(void)
+{
+ pad = UART_DEFAULT_PAD;
+
+ /* Configure new pad. */
+ npcx_gpio2uart();
+
+ /* Wait for ~2 bytes, to help the receiver resync. */
+ udelay(200);
+}
+
+static void uart_set_pad(enum uart_pad newpad)
+{
+ NPCX_UICTRL = 0x00;
+ task_disable_irq(NPCX_IRQ_UART);
+
+ /* Flush the last byte */
+ uart_tx_flush();
+ uart_tx_stop();
+
+ pad = newpad;
+
+ /* Configure new pad. */
+ npcx_gpio2uart();
+
+ /* Re-enable receive interrupt. */
+ NPCX_UICTRL = 0x40;
+
+ /*
+ * If pad is switched while a byte is being received, the last byte may
+ * be corrupted, let's wait for ~1 byte (9/115200 = 78 us + margin),
+ * then flush the FIFO. See b/65526215.
+ */
+ udelay(100);
+ uart_clear_rx_fifo(0);
+
+ task_enable_irq(NPCX_IRQ_UART);
+}
+
+/* TODO(b:67026316): Remove this and replace with software flow control. */
+void uart_default_pad_rx_interrupt(enum gpio_signal signal)
+{
+ /*
+ * We received an interrupt on the primary pad, give up on the
+ * transaction and switch back.
+ */
+ gpio_disable_interrupt(GPIO_UART_MAIN_RX);
+ uart_set_pad(UART_DEFAULT_PAD);
+ last_default_pad_rx_time = get_time();
+}
+
+int uart_alt_pad_write_read(uint8_t *tx, int tx_len, uint8_t *rx, int rx_len,
+ int timeout_us)
+{
+ uint32_t start = __hw_clock_source_read();
+ int ret = 0;
+
+ if ((get_time().val - last_default_pad_rx_time.val)
+ < block_alt_timeout_us)
+ return -EC_ERROR_BUSY;
+
+ cflush();
+
+ altpad_rx_buf = rx;
+ altpad_rx_pos = 0;
+ altpad_rx_len = rx_len;
+ altpad_tx_buf = tx;
+ altpad_tx_pos = 0;
+ altpad_tx_len = tx_len;
+
+ uart_set_pad(UART_ALTERNATE_PAD);
+ gpio_clear_pending_interrupt(GPIO_UART_MAIN_RX);
+ gpio_enable_interrupt(GPIO_UART_MAIN_RX);
+ uart_tx_start();
+
+ do {
+ usleep(100);
+
+ /* Pad switched during transaction. */
+ if (pad != UART_ALTERNATE_PAD) {
+ ret = -EC_ERROR_BUSY;
+ goto out;
+ }
+
+ if (altpad_rx_pos == altpad_rx_len &&
+ altpad_tx_pos == altpad_tx_len)
+ break;
+ } while ((__hw_clock_source_read() - start) < timeout_us);
+
+ gpio_disable_interrupt(GPIO_UART_MAIN_RX);
+ uart_set_pad(UART_DEFAULT_PAD);
+
+ if (altpad_tx_pos == altpad_tx_len)
+ ret = altpad_rx_pos;
+ else
+ ret = -EC_ERROR_TIMEOUT;
+
+out:
+ altpad_rx_len = 0;
+ altpad_rx_pos = 0;
+ altpad_rx_buf = NULL;
+ altpad_tx_len = 0;
+ altpad_tx_pos = 0;
+ altpad_tx_buf = NULL;
+
+ return ret;
+}
+#endif
+
static void uart_config(void)
{
/* Configure pins from GPIOs to CR_UART */
@@ -161,7 +381,10 @@ static void uart_config(void)
#error "Unsupported apb2 clock for UART!"
#endif
- /* Fix baud rate to 115200 */
+ /*
+ * Fix baud rate to 115200. If this value is modified, please also
+ * modify the delay in uart_set_pad and uart_reset_default_pad_panic.
+ */
NPCX_UPSR = 0x38;
NPCX_UBAUD = 0x01;
diff --git a/core/cortex-m/panic.c b/core/cortex-m/panic.c
index 58887e5cb7..24174cfb2d 100644
--- a/core/cortex-m/panic.c
+++ b/core/cortex-m/panic.c
@@ -12,6 +12,7 @@
#include "system.h"
#include "task.h"
#include "timer.h"
+#include "uart.h"
#include "util.h"
#include "watchdog.h"
@@ -335,6 +336,9 @@ void __keep report_panic(void)
pdata->cm.hfsr = CPU_NVIC_HFSR;
pdata->cm.dfsr = CPU_NVIC_DFSR;
+#ifdef CONFIG_UART_PAD_SWITCH
+ uart_reset_default_pad_panic();
+#endif
panic_data_print(pdata);
#ifdef CONFIG_DEBUG_EXCEPTIONS
panic_show_process_stack(pdata);
diff --git a/include/config.h b/include/config.h
index ccae71bd79..eb08f14ac5 100644
--- a/include/config.h
+++ b/include/config.h
@@ -2416,6 +2416,13 @@
#undef CONFIG_UART_INPUT_FILTER
/*
+ * Allow switching the EC console UART to an alternate pad. This must be
+ * used for short transactions only, and EC is only able to receive data on
+ * that alternate pad after it has been explicitly switched.
+ */
+#undef CONFIG_UART_PAD_SWITCH
+
+/*
* UART receive buffer size in bytes. Must be a power of 2 for macros in
* common/uart_buffering.c to work properly. Must be larger than
* CONFIG_CONSOLE_INPUT_LINE_SIZE to copy and paste scripts.
diff --git a/include/uart.h b/include/uart.h
index c372e3c390..5de1af92d6 100644
--- a/include/uart.h
+++ b/include/uart.h
@@ -246,4 +246,47 @@ int uart_comx_putc_ok(void);
*/
void uart_comx_putc(int c);
+/*
+ * Functions for pad switching UART, only defined on some chips (npcx), and
+ * if CONFIG_UART_PAD_SWITCH is enabled.
+ */
+enum uart_pad {
+ UART_DEFAULT_PAD = 0,
+ UART_ALTERNATE_PAD = 1,
+};
+
+/**
+ * Reset UART pad to default pad, so that a panic information can be printed
+ * on the EC console.
+ */
+void uart_reset_default_pad_panic(void);
+
+/**
+ * Specialized function to write then read data on UART alternate pad.
+ * The transfer may be interrupted at any time if data is received on the main
+ * pad.
+ *
+ * @param tx Data to be sent
+ * @param tx_len Length of data to be sent
+ * @param rx Buffer to receive data
+ * @param rx_len Receive buffer length
+ * @param timeout_us Timeout in microseconds for the transaction to complete.
+ *
+ * @return The number of bytes read back (indicates a timeout if != rx_len).
+ * - -EC_ERROR_BUSY if the alternate pad cannot be used (e.g. default
+ * pad is currently being used), or if the transfer was interrupted.
+ * - -EC_ERROR_TIMEOUT in case tx_len bytes cannot be written in the
+ * time specified in timeout_us.
+ */
+int uart_alt_pad_write_read(uint8_t *tx, int tx_len, uint8_t *rx, int rx_len,
+ int timeout_us);
+
+/**
+ * Interrupt handler for default UART RX pin transition when UART is switched
+ * to alternate pad.
+ *
+ * @param signal Signal which triggered the interrupt.
+ */
+void uart_default_pad_rx_interrupt(enum gpio_signal signal);
+
#endif /* __CROS_EC_UART_H */