diff options
Diffstat (limited to 'chip/npcx/uart.c')
-rw-r--r-- | chip/npcx/uart.c | 245 |
1 files changed, 234 insertions, 11 deletions
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; |