diff options
Diffstat (limited to 'chip')
-rw-r--r-- | chip/lm4/uart.c | 457 |
1 files changed, 52 insertions, 405 deletions
diff --git a/chip/lm4/uart.c b/chip/lm4/uart.c index 26373e21e5..7fa01dec7a 100644 --- a/chip/lm4/uart.c +++ b/chip/lm4/uart.c @@ -19,50 +19,62 @@ /* Baud rate for UARTs */ #define BAUD_RATE 115200 -/* Buffer sizes; should be power of 2 */ -#define TX_BUF_SIZE 512 -#define RX_BUF_SIZE 128 /* suggest larger than 80 to copy&paste script. */ - -/* Macros to advance in the circular transmit and receive buffers */ -#define TX_BUF_NEXT(i) (((i) + 1) & (TX_BUF_SIZE - 1)) -#define RX_BUF_NEXT(i) (((i) + 1) & (RX_BUF_SIZE - 1)) -#define RX_BUF_PREV(i) (((i) - 1) & (RX_BUF_SIZE - 1)) - -/* Transmit and receive buffers */ -static volatile char tx_buf[TX_BUF_SIZE]; -static volatile int tx_buf_head = 0; -static volatile int tx_buf_tail = 0; -static volatile char rx_buf[RX_BUF_SIZE]; -static volatile int rx_buf_head = 0; -static volatile int rx_buf_tail = 0; -static int last_rx_was_cr = 0; - -static int console_mode = 1; - -/* TODO: should have an API to set raw mode for the UART. In raw - * mode, we don't do CRLF translation or echo input. */ - - -/* Put a single character into the transmit buffer. Does not enable - * the transmit interrupt; assumes that happens elsewhere. Returns - * zero if the character was transmitted, 1 if it was dropped. */ -static int __tx_char(int c) +void uart_tx_start(void) { - int tx_buf_next; + /* Re-enable the transmit interrupt, then forcibly trigger the + * interrupt. This works around a hardware problem with the + * UART where the FIFO only triggers the interrupt when its + * threshold is _crossed_, not just met. */ + LM4_UART_IM(0) |= 0x20; + task_trigger_irq(LM4_IRQ_UART0); +} - /* Do newline to CRLF translation */ - if (console_mode && c == '\n' && __tx_char('\r')) - return 1; +void uart_tx_stop(void) +{ + LM4_UART_IM(0) &= ~0x20; +} - tx_buf_next = TX_BUF_NEXT(tx_buf_head); - if (tx_buf_next == tx_buf_tail) - return 1; +int uart_tx_stopped(void) +{ + return !(LM4_UART_IM(0) & 0x20); +} - tx_buf[tx_buf_head] = c; - tx_buf_head = tx_buf_next; - return 0; +void uart_tx_flush(void) +{ + /* Wait for transmit FIFO empty */ + while (!(LM4_UART_FR(0) & 0x80)) + ; } +int uart_tx_ready(void) +{ + return !(LM4_UART_FR(0) & 0x20); +} + +int uart_rx_available(void) +{ + return !(LM4_UART_FR(0) & 0x10); +} + +void uart_write_char(char c) +{ + LM4_UART_DR(0) = c; +} + +int uart_read_char(void) +{ + return LM4_UART_DR(0); +} + +void uart_disable_interrupt(void) +{ + task_disable_irq(LM4_IRQ_UART0); +} + +void uart_enable_interrupt(void) +{ + task_enable_irq(LM4_IRQ_UART0); +} /* Interrupt handler for UART0 */ static void uart_0_interrupt(void) @@ -70,64 +82,9 @@ static void uart_0_interrupt(void) /* Clear transmit and receive interrupt status */ LM4_UART_ICR(0) = 0x70; - /* Copy input from buffer until RX fifo empty */ - while (!(LM4_UART_FR(0) & 0x10)) { - int c = LM4_UART_DR(0); - - /* Handle console mode echoing and translation */ - if (console_mode) { - /* Translate CR and CRLF to LF (newline) */ - if (c == '\r') { - last_rx_was_cr = 1; - c = '\n'; - } else if (c == '\n' && last_rx_was_cr) { - last_rx_was_cr = 0; - continue; - } else { - last_rx_was_cr = 0; - } - - /* Echo characters directly to the transmit FIFO so we - * don't interfere with the transmit buffer. This - * means that if a lot of output is happening, input - * characters won't always be properly echoed. */ - if (console_mode && c == '\n') - LM4_UART_DR(0) = '\r'; - LM4_UART_DR(0) = c; - - /* Handle backspace if we can */ - if (c == '\b') { - if (rx_buf_head != rx_buf_tail) { - /* Delete the previous character (and - * space over it on the output) */ - LM4_UART_DR(0) = ' '; - LM4_UART_DR(0) = '\b'; - rx_buf_head = RX_BUF_PREV(rx_buf_head); - } - continue; - } - } - - rx_buf[rx_buf_head] = c; - rx_buf_head = RX_BUF_NEXT(rx_buf_head); - /* On overflow, discard oldest output */ - if (rx_buf_head == rx_buf_tail) - rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); - - /* Call console callback on newline, if in console mode */ - if (console_mode && c == '\n') - console_has_input(); - } - - /* Copy output from buffer until TX fifo full or output buffer empty */ - while (!(LM4_UART_FR(0) & 0x20) && (tx_buf_head != tx_buf_tail)) { - LM4_UART_DR(0) = tx_buf[tx_buf_tail]; - tx_buf_tail = TX_BUF_NEXT(tx_buf_tail); - } - /* If output buffer is empty, disable transmit interrupt */ - if (tx_buf_tail == tx_buf_head) - LM4_UART_IM(0) &= ~0x20; + /* Read input FIFO until empty, then fill output FIFO */ + uart_process(); } DECLARE_IRQ(LM4_IRQ_UART0, uart_0_interrupt, 1); @@ -227,316 +184,6 @@ int uart_init(void) } -void uart_set_console_mode(int enable) -{ - console_mode = enable; -} - - -int uart_puts(const char *outstr) -{ - int was_empty = (tx_buf_head == tx_buf_tail); - - /* Put all characters in the output buffer */ - while (*outstr) { - if (__tx_char(*outstr++) != 0) - break; - } - - if (was_empty) { - /* Re-enable the transmit interrupt, then forcibly trigger the - * interrupt. This works around a hardware problem with the - * UART where the FIFO only triggers the interrupt when its - * threshold is _crossed_, not just met. */ - LM4_UART_IM(0) |= 0x20; - task_trigger_irq(LM4_IRQ_UART0); - } - - /* Successful if we consumed all output */ - return *outstr ? EC_ERROR_OVERFLOW : EC_SUCCESS; -} - - -int uart_printf(const char *format, ...) -{ - static const char int_chars[] = "0123456789abcdef"; - static const char error_str[] = "ERROR"; - char intbuf[21]; /* Longest uint64 */ - int dropped_chars = 0; - int is_left; - int pad_zero; - int pad_width; - int was_empty = (tx_buf_head == tx_buf_tail); - va_list args; - char *vstr; - int vlen; - - va_start(args, format); - - while (*format && !dropped_chars) { - int c = *format++; - - /* Copy normal characters */ - if (c != '%') { - dropped_chars |= __tx_char(c); - continue; - } - - /* Get first format character */ - c = *format++; - - /* Send "%" for "%%" input */ - if (c == '%' || c == '\0') { - dropped_chars |= __tx_char('%'); - continue; - } - - /* Handle %c */ - if (c == 'c') { - c = va_arg(args, int); - dropped_chars |= __tx_char(c); - continue; - } - - /* Handle left-justification ("%-5s") */ - is_left = (c == '-'); - if (is_left) - c = *format++; - - /* Handle padding with 0's */ - pad_zero = (c == '0'); - if (pad_zero) - c = *format++; - - /* Count padding length */ - pad_width = 0; - while (c >= '0' && c <= '9') { - pad_width = (10 * pad_width) + c - '0'; - c = *format++; - } - if (pad_width > 80) { - /* Sanity check for width failed */ - format = error_str; - continue; - } - - if (c == 's') { - vstr = va_arg(args, char *); - if (vstr == NULL) - vstr = "(NULL)"; - } else { - uint32_t v; - int is_negative = 0; - int base = 10; - - /* TODO: (crosbug.com/p/7490) handle "%l" prefix for - * uint64_t */ - - v = va_arg(args, uint32_t); - - switch (c) { - case 'd': - if ((int)v < 0) { - is_negative = 1; - v = -v; - } - break; - case 'u': - break; - case 'x': - case 'p': - base = 16; - break; - default: - format = error_str; - } - if (format == error_str) - continue; /* Bad format specifier */ - - /* Convert integer to string, starting at end of - * buffer and working backwards. */ - vstr = intbuf + sizeof(intbuf) - 1; - *(vstr) = '\0'; - - if (!v) - *(--vstr) = '0'; - - while (v) { - *(--vstr) = int_chars[v % base]; - v /= base; - } - if (is_negative) - *(--vstr) = '-'; - } - - /* Copy string (or stringified integer) */ - vlen = strlen(vstr); - while (vlen < pad_width && !is_left) { - dropped_chars |= __tx_char(pad_zero ? '0' : ' '); - vlen++; - } - while (*vstr) - dropped_chars |= __tx_char(*vstr++); - while (vlen < pad_width && is_left) { - dropped_chars |= __tx_char(' '); - vlen++; - } - } - va_end(args); - - if (was_empty) { - /* Re-enable the transmit interrupt, then forcibly trigger the - * interrupt. This works around a hardware problem with the - * UART where the FIFO only triggers the interrupt when its - * threshold is _crossed_, not just met. */ - LM4_UART_IM(0) |= 0x20; - task_trigger_irq(LM4_IRQ_UART0); - } - - /* Successful if we consumed all output */ - return dropped_chars ? EC_ERROR_OVERFLOW : EC_SUCCESS; -} - - -void uart_flush_output(void) -{ - /* Wait for buffer to empty */ - while (tx_buf_head != tx_buf_tail) { - /* It's possible we're in some other interrupt, and the - * previous context was doing a printf() or puts() but hadn't - * enabled the UART interrupt. Check if the interrupt is - * disabled, and if so, re-enable and trigger it. Note that - * this check is inside the while loop, so we'll be safe even - * if the context switches away from us to another partial - * printf() and back. */ - if (!(LM4_UART_IM(0) & 0x20)) { - LM4_UART_IM(0) |= 0x20; - task_trigger_irq(LM4_IRQ_UART0); - } - } - - /* Wait for transmit FIFO empty */ - while (!(LM4_UART_FR(0) & 0x80)) {} -} - -void uart_emergency_flush(void) -{ - do { - /* Copy output from buffer until TX fifo full - * or output buffer empty - */ - while (!(LM4_UART_FR(0) & 0x20) && - (tx_buf_head != tx_buf_tail)) { - LM4_UART_DR(0) = tx_buf[tx_buf_tail]; - tx_buf_tail = TX_BUF_NEXT(tx_buf_tail); - } - /* Wait for transmit FIFO empty */ - while (!(LM4_UART_FR(0) & 0x80)) {} - } while (tx_buf_head != tx_buf_tail); -} - - -void uart_flush_input(void) -{ - /* Disable interrupts */ - task_disable_irq(LM4_IRQ_UART0); - - /* Call interrupt handler to empty the hardware FIFO */ - uart_0_interrupt(); - - /* Clear the input buffer */ - rx_buf_tail = rx_buf_head; - - /* Re-enable interrupts */ - task_enable_irq(LM4_IRQ_UART0); -} - - -int uart_peek(int c) -{ - int index = -1; - int i = 0; - - /* Disable interrupts while we pull characters out, because the - * interrupt handler can also modify the tail pointer. */ - task_disable_irq(LM4_IRQ_UART0); - - /* Call interrupt handler to empty the hardware FIFO. The minimum - * FIFO trigger depth is 1/8 (2 chars), so this is the only way to - * ensure we've pulled the very last character out of the FIFO. */ - uart_0_interrupt(); - - for (i = rx_buf_tail; i != rx_buf_head; i = RX_BUF_NEXT(i)) { - if (rx_buf[i] == c) { - index = (RX_BUF_SIZE + i - rx_buf_tail) & - (RX_BUF_SIZE - 1); - break; - } - } - - /* Re-enable interrupts */ - task_enable_irq(LM4_IRQ_UART0); - - return index; -} - - -int uart_getc(void) -{ - int c; - - /* Disable interrupts */ - task_disable_irq(LM4_IRQ_UART0); - - /* Call interrupt handler to empty the hardware FIFO */ - uart_0_interrupt(); - - if (rx_buf_tail == rx_buf_head) { - c = -1; /* No pending input */ - } else { - c = rx_buf[rx_buf_tail]; - rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); - } - - /* Re-enable interrupts */ - task_enable_irq(LM4_IRQ_UART0); - - return c; -} - - -int uart_gets(char *dest, int size) -{ - int got = 0; - int c; - - /* Disable interrupts while we pull characters out, because the - * interrupt handler can also modify the tail pointer. */ - task_disable_irq(LM4_IRQ_UART0); - - /* Call interrupt handler to empty the hardware FIFO */ - uart_0_interrupt(); - - /* Read characters */ - while (got < size - 1 && rx_buf_tail != rx_buf_head) { - c = rx_buf[rx_buf_tail]; - dest[got++] = c; - rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); - if (c == '\n') - break; /* Stop on newline */ - } - - /* Re-enable interrupts */ - task_enable_irq(LM4_IRQ_UART0); - - /* Null-terminate */ - dest[got] = '\0'; - - /* Return the length we got */ - return got; -} - - /*****************************************************************************/ /* COMx functions */ |