summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 */