diff options
-rw-r--r-- | chip/stm32/config_chip.h | 3 | ||||
-rw-r--r-- | chip/stm32/uart.c | 52 | ||||
-rw-r--r-- | common/uart_buffering.c | 106 | ||||
-rw-r--r-- | include/config.h | 25 | ||||
-rw-r--r-- | include/uart.h | 20 |
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. |