summaryrefslogtreecommitdiff
path: root/chip/lm4/uart.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/lm4/uart.c')
-rw-r--r--chip/lm4/uart.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/chip/lm4/uart.c b/chip/lm4/uart.c
new file mode 100644
index 0000000000..49115e418c
--- /dev/null
+++ b/chip/lm4/uart.c
@@ -0,0 +1,563 @@
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* UART module for Chrome EC */
+
+#include <stdarg.h>
+
+#include "board.h"
+#include "console.h"
+#include "registers.h"
+#include "task.h"
+#include "uart.h"
+#include "util.h"
+
+/* 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)
+{
+ int tx_buf_next;
+
+ /* Do newline to CRLF translation */
+ if (console_mode && c == '\n' && __tx_char('\r'))
+ return 1;
+
+ tx_buf_next = TX_BUF_NEXT(tx_buf_head);
+ if (tx_buf_next == tx_buf_tail)
+ return 1;
+
+ tx_buf[tx_buf_head] = c;
+ tx_buf_head = tx_buf_next;
+ return 0;
+}
+
+
+/* Interrupt handler for UART0 */
+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;
+}
+DECLARE_IRQ(5, uart_0_interrupt, 1);
+
+
+/* Interrupt handler for UART1 */
+static void uart_1_interrupt(void)
+{
+ /* Clear transmit and receive interrupt status */
+ LM4_UART_ICR(1) = 0x70;
+
+ /* TODO: handle input */
+
+ /* If we have space in our FIFO and a character is pending in LPC,
+ * grab it.
+ *
+ * TODO: move UART1 interrupt to the LPC module? */
+ if (!(LM4_UART_FR(1) & 0x20) && (LM4_LPC_ST(7) & 0x02)) {
+ /* TODO: this clears the receive-ready interrupt too,
+ * which will be ok once we're handing input as well.
+ * But we're not yet. */
+ LM4_LPC_LPCDMACX = LM4_LPC_LPCDMACX;
+ /* Copy the next byte */
+ LM4_UART_DR(1) = LPC_POOL_COMX[0];
+ /* Disable transmit interrupt */
+ LM4_UART_IM(1) &= ~0x20;
+ }
+}
+/* Must be same prio as LPC interrupt handler so they don't preempt */
+DECLARE_IRQ(6, uart_1_interrupt, 2);
+
+
+/* Configure GPIOs for the UART module. */
+/* TODO: board-dependent; on Link UART1 is PC4/PC5, not PB0/PB1. */
+static void configure_gpio(void)
+{
+ volatile uint32_t scratch __attribute__((unused));
+
+ /* Enable clocks to GPIO blocks A and B, then delay a few clocks. */
+ LM4_SYSTEM_RCGCGPIO |= 0x0003;
+
+ /* UART0 setup; RX and TX are GPIO PA0 and PA1 */
+ /* Enable alternate function */
+ LM4_GPIO_AFSEL(A) |= 0x03;
+ /* Alternate function 1 */
+ LM4_GPIO_PCTL(A) = (LM4_GPIO_PCTL(A) & 0xffffff00) | 0x11;
+ /* Enable digital function */
+ LM4_GPIO_DEN(A) |= 0x03;
+
+ /* UART1 setup; RX and TX are GPIO PB0 and PB1 */
+ /* Enable alternate function */
+ LM4_GPIO_AFSEL(B) |= 0x03;
+ /* Alternate function 1 */
+ LM4_GPIO_PCTL(B) = (LM4_GPIO_PCTL(B) & 0xffffff00) | 0x11;
+ /* Enable digital function */
+ LM4_GPIO_DEN(B) = 0x03;
+}
+
+
+int uart_init(void)
+{
+ volatile uint32_t scratch __attribute__((unused));
+
+ /* Enable UART0 and UART1 and delay a few clocks */
+ LM4_SYSTEM_RCGCUART |= 0x03;
+ scratch = LM4_SYSTEM_RCGCUART;
+
+ /* Configure GPIOs */
+ configure_gpio();
+
+ /* UART0 setup */
+ /* Disable the port via UARTCTL */
+ LM4_UART_CTL(0) = 0x0300;
+ /* Set the baud rate divisor */
+ LM4_UART_IBRD(0) = (CPU_CLOCK / 16) / BAUD_RATE;
+ LM4_UART_FBRD(0) =
+ (((CPU_CLOCK / 16) % BAUD_RATE) * 64 + BAUD_RATE / 2) /
+ BAUD_RATE;
+ /* Set UARTLCRH to 8-N-1, FIFO enabled. Must be done after setting
+ * the divisor for the new divisor to take effect. */
+ LM4_UART_LCRH(0) = 0x70;
+ /* Interrupt when RX fifo at minimum (>= 1/8 full), and TX fifo when
+ * <= 1/4 full */
+ LM4_UART_IFLS(0) = 0x01;
+ /* Unmask receive-FIFO, receive-timeout. We need
+ * receive-timeout because the minimum RX FIFO depth is 1/8 =
+ * 2 bytes; without the receive-timeout we'd never be notified
+ * about single received characters. */
+ LM4_UART_IM(0) = 0x50;
+ /* Enable the port */
+ LM4_UART_CTL(0) |= 0x0001;
+
+ /* UART1 setup */
+ /* Disable the port via UARTCTL */
+ LM4_UART_CTL(1) = 0x0300;
+ /* Set the baud rate divisor */
+ LM4_UART_IBRD(1) = (CPU_CLOCK / 16) / BAUD_RATE;
+ LM4_UART_FBRD(1) =
+ (((CPU_CLOCK / 16) % BAUD_RATE) * 64 + BAUD_RATE / 2) /
+ BAUD_RATE;
+ /* Set UARTLCRH to 8-N-1, FIFO enabled. Must be done after setting
+ * the divisor for the new divisor to take effect. */
+ LM4_UART_LCRH(1) = 0x70;
+ /* Unmask receive-FIFO, receive-timeout. We need
+ * receive-timeout because the minimum RX FIFO depth is 1/8 =
+ * 2 bytes; without the receive-timeout we'd never be notified
+ * about single received characters. */
+ LM4_UART_IM(1) = 0x50;
+#ifdef USE_UART_DMA
+ /* No interrupts on UART1 */
+ LM4_UART_IM(1) = 0x00;
+ /* Enable RX and TX DMA */
+ LM4_UART_DMACTL(1) = 0x03;
+#endif
+ /* Enable the port */
+ LM4_UART_CTL(1) |= 0x0001;
+
+ /* Print hello on UART1 for debugging */
+ {
+ const char *c = "Hello on UART1\r\n";
+ while (*c)
+ LM4_UART_DR(1) = *c++;
+ }
+
+ return EC_SUCCESS;
+}
+
+
+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;
+ LM4_NVIC_SWTRIG = 5;
+ }
+
+ /* 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: 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;
+ LM4_NVIC_SWTRIG = 5;
+ }
+
+ /* 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;
+ LM4_NVIC_SWTRIG = 5;
+ }
+ }
+
+ /* 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 */
+ LM4_NVIC_DIS(0) = (1 << 5);
+
+ /* Call interrupt handler to empty the hardware FIFO */
+ uart_0_interrupt();
+
+ /* Clear the input buffer */
+ rx_buf_tail = rx_buf_head;
+
+ /* Re-enable interrupts */
+ LM4_NVIC_EN(0) = (1 << 5);
+}
+
+
+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. */
+ LM4_NVIC_DIS(0) = (1 << 5);
+
+ /* 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 */
+ LM4_NVIC_EN(0) = (1 << 5);
+
+ return index;
+}
+
+
+int uart_getc(void)
+{
+ int c;
+
+ /* Disable interrupts */
+ LM4_NVIC_DIS(0) = (1 << 5);
+
+ /* 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 */
+ LM4_NVIC_EN(0) = (1 << 5);
+
+ 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. */
+ LM4_NVIC_DIS(0) = (1 << 5);
+
+ /* 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 */
+ LM4_NVIC_EN(0) = (1 << 5);
+
+ /* Null-terminate */
+ dest[got] = '\0';
+
+ /* Return the length we got */
+ return got;
+}