summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-09-11 14:40:27 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-09-16 23:31:07 +0000
commitcdd5c206cd2125983f83ef3a54470b5e99f82031 (patch)
treeaab6c92edc49fb51a13298feeba7a2afa3dc599f
parentb718dfc0598c324772171c1df94b68d5546893b5 (diff)
downloadchrome-ec-cdd5c206cd2125983f83ef3a54470b5e99f82031.tar.gz
stm32: Use DMA for UART receive
STM32 has a single-byte mailbox for UART I/O. When the core clock runs at 16Mhz we can service interrupts fast enough to handle 115200 baud input, but when we drop to 1MHz we drop characters. Using DMA to receive input solves this problem. The STM32 DMA engine can only generate interrupts when the transfer is half-done / all-done, so we need to poll the DMA receive-head-pointer to see if individual characters have been received. Do this in the tick task (every 250ms). When a character is received, poll more quickly for a bit (5 times before the next tick) so the input console is more responsive to typing. BUG=chrome-os-partner:20485 BRANCH=none TEST=Console is responsive to debug commands. For example, help -> prints help apshutdown -> shuts down AP arrow keys -> move cursor and scroll through command history Ctrl+Q, help, wait a second, Ctrl+S -> help output printed after Ctrl+S Then in chip/stm32/config_chip.h, comment out #define CONFIG_UART_RX_DMA and rebuild/reflash the EC. When the AP is up, the console works normally but after 'apshutdown', the EC drops to 1MHz core clock, and the arrow keys don't work. (This step confirms that adding DMA support did not change the behavior of systems where CONFIG_UART_RX_DMA is not defined.) Change-Id: I199448354824bd747c7b290ea7fd5ccf354c11bb Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/169406 Reviewed-by: Simon Glass <sjg@chromium.org>
-rw-r--r--chip/stm32/config_chip.h3
-rw-r--r--chip/stm32/uart.c52
-rw-r--r--common/uart_buffering.c106
-rw-r--r--include/config.h25
-rw-r--r--include/uart.h20
5 files changed, 170 insertions, 36 deletions
diff --git a/chip/stm32/config_chip.h b/chip/stm32/config_chip.h
index 8c0845f90e..3d887ec621 100644
--- a/chip/stm32/config_chip.h
+++ b/chip/stm32/config_chip.h
@@ -50,7 +50,8 @@
/* Use DMA */
#define CONFIG_DMA
-/* Use DMA for UART output */
+/* Use DMA for UART I/O */
+#define CONFIG_UART_RX_DMA
#define CONFIG_UART_TX_DMA
/* Flash protection applies to the next boot, not the current one */
diff --git a/chip/stm32/uart.c b/chip/stm32/uart.c
index 3d510c16f1..78bbcc3e58 100644
--- a/chip/stm32/uart.c
+++ b/chip/stm32/uart.c
@@ -31,6 +31,17 @@ static const struct dma_option dma_tx_option = {
#define UART_TX_INT_ENABLE STM32_USART_CR1_TXEIE
#endif
+#ifdef CONFIG_UART_RX_DMA
+/* DMA channel options; assumes UART1 */
+static const struct dma_option dma_rx_option = {
+ STM32_DMAC_USART1_RX, (void *)&STM32_USART_DR(UARTN),
+ STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT |
+ STM32_DMA_CCR_CIRC
+};
+
+static int dma_rx_len; /* Size of receive DMA circular buffer */
+#endif
+
static int init_done; /* Initialization done? */
static int should_stop; /* Last TX control action */
@@ -95,6 +106,23 @@ int uart_rx_available(void)
return STM32_USART_SR(UARTN) & STM32_USART_SR_RXNE;
}
+#ifdef CONFIG_UART_RX_DMA
+
+void uart_rx_dma_start(char *dest, int len)
+{
+ /* Start receiving */
+ dma_rx_len = len;
+ dma_start_rx(&dma_rx_option, len, dest);
+}
+
+int uart_rx_dma_head(void)
+{
+ return dma_bytes_done(dma_get_channel(STM32_DMAC_USART1_RX),
+ dma_rx_len);
+}
+
+#endif
+
void uart_write_char(char c)
{
/* Wait for space */
@@ -134,14 +162,21 @@ static void uart_interrupt(void)
STM32_USART_CR1(UARTN) &= ~STM32_USART_CR1_TXEIE;
#endif
- /* Read input FIFO until empty, then fill output FIFO */
+#ifndef CONFIG_UART_RX_DMA
+ /*
+ * Read input FIFO until empty. DMA-based receive does this from a
+ * hook in the UART buffering module.
+ */
uart_process_input();
+#endif
+
+ /* Fill output FIFO */
uart_process_output();
#ifndef CONFIG_UART_TX_DMA
/*
* Re-enable TX empty interrupt only if it was not disabled by
- * uart_process.
+ * uart_process_output().
*/
if (!should_stop)
STM32_USART_CR1(UARTN) |= STM32_USART_CR1_TXEIE;
@@ -194,11 +229,10 @@ void uart_init(void)
/*
* UART enabled, 8 Data bits, oversampling x16, no parity,
- * RXNE interrupt, TX and RX enabled.
+ * TX and RX enabled.
*/
STM32_USART_CR1(UARTN) =
- STM32_USART_CR1_UE | STM32_USART_CR1_RXNEIE |
- STM32_USART_CR1_TE | STM32_USART_CR1_RE;
+ STM32_USART_CR1_UE | STM32_USART_CR1_TE | STM32_USART_CR1_RE;
/* 1 stop bit, no fancy stuff */
STM32_USART_CR2(UARTN) = 0x0000;
@@ -211,6 +245,14 @@ void uart_init(void)
STM32_USART_CR3(UARTN) = 0x0000;
#endif
+#ifdef CONFIG_UART_RX_DMA
+ /* Enable DMA receiver */
+ STM32_USART_CR3(UARTN) |= STM32_USART_CR3_DMAR;
+#else
+ /* Enable receive-not-empty interrupt */
+ STM32_USART_CR1(UARTN) |= STM32_USART_CR1_RXNEIE;
+#endif
+
#ifdef CHIP_FAMILY_stm32l
/* Use single-bit sampling */
STM32_USART_CR3(UARTN) |= STM32_USART_CR3_ONEBIT;
diff --git a/common/uart_buffering.c b/common/uart_buffering.c
index 560e783c41..779835ae24 100644
--- a/common/uart_buffering.c
+++ b/common/uart_buffering.c
@@ -9,10 +9,12 @@
#include "common.h"
#include "console.h"
+#include "hooks.h"
#include "host_command.h"
#include "printf.h"
#include "system.h"
#include "task.h"
+#include "timer.h"
#include "uart.h"
#include "util.h"
@@ -28,6 +30,14 @@
/* ASCII control character; for example, CTRL('C') = ^C */
#define CTRL(c) ((c) - '@')
+/*
+ * Interval between rechecking the receive DMA head pointer, after a character
+ * of input has been detected by the normal tick task. There will be
+ * CONFIG_UART_RX_DMA_RECHECKS rechecks between this tick and the next tick.
+ */
+#define RX_DMA_RECHECK_INTERVAL (HOOK_TICK_INTERVAL / \
+ (CONFIG_UART_RX_DMA_RECHECKS + 1))
+
/* Transmit and receive buffers */
static volatile char tx_buf[CONFIG_UART_TX_BUF_SIZE];
static volatile int tx_buf_head;
@@ -70,7 +80,7 @@ static int __tx_char(void *context, int c)
/**
* Process UART output via DMA
*/
-static void uart_process_output_dma(void)
+void uart_process_output(void)
{
/* Size of current DMA transfer */
static int tx_dma_in_progress;
@@ -81,6 +91,9 @@ static void uart_process_output_dma(void)
*/
int head = tx_buf_head;
+ if (uart_suspended)
+ return;
+
/* If DMA is still busy, nothing to do. */
if(!uart_tx_dma_ready())
return;
@@ -108,16 +121,13 @@ static void uart_process_output_dma(void)
uart_tx_dma_start((char *)(tx_buf + tx_buf_tail), tx_dma_in_progress);
}
-#endif /* CONFIG_UART_TX_DMA */
+#else /* !CONFIG_UART_TX_DMA */
void uart_process_output(void)
{
if (uart_suspended)
return;
-#ifdef CONFIG_UART_TX_DMA
- uart_process_output_dma();
-#else
/* Copy output from buffer until TX fifo full or output buffer empty */
while (uart_tx_ready() && (tx_buf_head != tx_buf_tail)) {
uart_write_char(tx_buf[tx_buf_tail]);
@@ -127,9 +137,57 @@ void uart_process_output(void)
/* If output buffer is empty, disable transmit interrupt */
if (tx_buf_tail == tx_buf_head)
uart_tx_stop();
-#endif
}
+#endif /* !CONFIG_UART_TX_DMA */
+
+#ifdef CONFIG_UART_RX_DMA
+
+void uart_process_input(void)
+{
+ static int fast_rechecks;
+ int cur_head = rx_buf_head;
+
+ int i;
+
+ /* Update receive buffer head from current DMA receive pointer */
+ rx_buf_head = uart_rx_dma_head();
+
+ /* Handle software flow control characters */
+ for (i = cur_head; i != rx_buf_head; i = RX_BUF_NEXT(i)) {
+ int c = rx_buf[i];
+
+ if (c == CTRL('Q')) {
+ /* Software flow control - XOFF */
+ uart_suspended = 1;
+ uart_tx_stop();
+ } else if (c == CTRL('S')) {
+ /* Software flow control - XON */
+ uart_suspended = 0;
+ uart_tx_start();
+ }
+ }
+
+ if (rx_buf_head != cur_head) {
+ console_has_input();
+ fast_rechecks = CONFIG_UART_RX_DMA_RECHECKS;
+ }
+
+ /*
+ * Input is checked once a tick when the console is idle. When input
+ * is received, check more frequently for a bit, so that the console is
+ * more responsive.
+ */
+ if (fast_rechecks) {
+ fast_rechecks--;
+ hook_call_deferred(uart_process_input, RX_DMA_RECHECK_INTERVAL);
+ }
+}
+DECLARE_HOOK(HOOK_TICK, uart_process_input, HOOK_PRIO_DEFAULT);
+DECLARE_DEFERRED(uart_process_input);
+
+#else /* !CONFIG_UART_RX_DMA */
+
void uart_process_input(void)
{
int got_input = 0;
@@ -151,15 +209,16 @@ void uart_process_input(void)
/* Buffer all other input */
rx_buf[rx_buf_head] = c;
rx_buf_head = rx_buf_next;
+ got_input = 1;
}
-
- got_input = 1;
}
if (got_input)
console_has_input();
}
+#endif /* !CONFIG_UART_RX_DMA */
+
int uart_putc(int c)
{
int rv = __tx_char(NULL, c);
@@ -241,26 +300,27 @@ void uart_flush_output(void)
int uart_getc(void)
{
- int c;
-
- /* Disable interrupts */
- uart_disable_interrupt();
-
- /* Call interrupt handler to empty the hardware FIFO */
- uart_process_input();
-
- if (rx_buf_tail == rx_buf_head) {
- c = -1; /* No pending input */
- } else {
- c = rx_buf[rx_buf_tail];
+ /* Look for a non-flow-control character */
+ while(rx_buf_tail != rx_buf_head) {
+ int c = rx_buf[rx_buf_tail];
rx_buf_tail = RX_BUF_NEXT(rx_buf_tail);
+
+ if (c != CTRL('Q') && c != CTRL('S'))
+ return c;
}
- /* Re-enable interrupts */
- uart_enable_interrupt();
+ /* If we're still here, no input */
+ return -1;
+}
- return c;
+#ifdef CONFIG_UART_RX_DMA
+static void uart_rx_dma_init(void)
+{
+ /* Start receiving */
+ uart_rx_dma_start((char *)rx_buf, CONFIG_UART_RX_BUF_SIZE);
}
+DECLARE_HOOK(HOOK_INIT, uart_rx_dma_init, HOOK_PRIO_DEFAULT);
+#endif
/*****************************************************************************/
/* Host commands */
diff --git a/include/config.h b/include/config.h
index 31e923ed98..148b5921e4 100644
--- a/include/config.h
+++ b/include/config.h
@@ -613,6 +613,24 @@
#undef CONFIG_UART_HOST
/*
+ * 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.
+ */
+#define CONFIG_UART_RX_BUF_SIZE 128
+
+/* Use DMA for UART input */
+#undef CONFIG_UART_RX_DMA
+
+/*
+ * On some platforms, UART receive DMA can't trigger an interrupt when a single
+ * character is received. Those platforms poll for characters every HOOK_TICK.
+ * When a character is received, make this many additional checks between then
+ * and the next HOOK_TICK, to increase responsiveness of the console to input.
+ */
+#define CONFIG_UART_RX_DMA_RECHECKS 5
+
+/*
* UART transmit buffer size in bytes. Must be a power of 2 for macros in
* common/uart_buffering.c to work properly.
*/
@@ -621,13 +639,6 @@
/* Use DMA for UART output */
#undef CONFIG_UART_TX_DMA
-/*
- * 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.
- */
-#define CONFIG_UART_RX_BUF_SIZE 128
-
/*****************************************************************************/
/* Compile support for simple control of power to the device's USB ports */
diff --git a/include/uart.h b/include/uart.h
index 9e182002e1..762d885f08 100644
--- a/include/uart.h
+++ b/include/uart.h
@@ -119,6 +119,26 @@ void uart_tx_dma_start(const char *src, int len);
int uart_rx_available(void);
/**
+ * Start a UART receive DMA transfer.
+ *
+ * DMA will be configured in circular buffer mode, so received characters
+ * will be stored into the buffer continuously.
+ *
+ * @param dest Pointer to destination buffer
+ * @param len Length of buffer in bytes
+ */
+void uart_rx_dma_start(char *dest, int len);
+
+/**
+ * Return the head of the receive DMA transfer buffer
+ *
+ * This is the next offset in the buffer which will receive a character, and
+ * will be from 0..(len-1) where len is the buffer length passed to
+ * uart_rx_dma_start().
+ */
+int uart_rx_dma_head(void);
+
+/**
* Send a character to the UART data register.
*
* If the transmit FIFO is full, blocks until there is space.