/* Copyright 2012 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 "clock.h" #include "common.h" #include "console.h" #include "gpio.h" #include "lpc.h" #include "registers.h" #include "system.h" #include "task.h" #include "uart.h" #include "util.h" #ifdef CONFIG_UART_HOST #define IRQ_UART_HOST CONCAT2(LM4_IRQ_UART, CONFIG_UART_HOST) #endif static int init_done; int uart_init_done(void) { return init_done; } void uart_tx_start(void) { /* If interrupt is already enabled, nothing to do */ if (LM4_UART_IM(0) & 0x20) return; /* Do not allow deep sleep while transmit in progress */ disable_sleep(SLEEP_MASK_UART); /* * 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); } void uart_tx_stop(void) { LM4_UART_IM(0) &= ~0x20; /* Re-allow deep sleep */ enable_sleep(SLEEP_MASK_UART); } 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_tx_in_progress(void) { /* Transmit is in progress if the TX busy bit is set. */ return LM4_UART_FR(0) & 0x08; } int uart_rx_available(void) { return !(LM4_UART_FR(0) & 0x10); } void uart_write_char(char c) { /* Wait for space in transmit FIFO. */ while (!uart_tx_ready()) ; LM4_UART_DR(0) = c; } int uart_read_char(void) { return LM4_UART_DR(0); } static void uart_clear_rx_fifo(int channel) { int scratch __attribute__ ((unused)); while (!(LM4_UART_FR(channel) & 0x10)) scratch = LM4_UART_DR(channel); } /** * Interrupt handler for UART0 */ void uart_ec_interrupt(void) { /* Clear transmit and receive interrupt status */ LM4_UART_ICR(0) = 0x70; /* Read input FIFO until empty, then fill output FIFO */ uart_process_input(); uart_process_output(); } DECLARE_IRQ(LM4_IRQ_UART0, uart_ec_interrupt, 1); #ifdef CONFIG_UART_HOST /** * Interrupt handler for Host UART */ void uart_host_interrupt(void) { /* Clear transmit and receive interrupt status */ LM4_UART_ICR(CONFIG_UART_HOST) = 0x70; #ifdef CONFIG_HOSTCMD_LPC /* * If we have space in our FIFO and a character is pending in LPC, * handle that character. */ if (!(LM4_UART_FR(CONFIG_UART_HOST) & 0x20) && lpc_comx_has_char()) { /* Copy the next byte then disable transmit interrupt */ LM4_UART_DR(CONFIG_UART_HOST) = lpc_comx_get_char(); LM4_UART_IM(CONFIG_UART_HOST) &= ~0x20; } /* * Handle received character. There is no flow control on input; * received characters are blindly forwarded to LPC. This is ok * because LPC is much faster than UART, and we don't have flow control * on the UART receive-side either. */ if (!(LM4_UART_FR(CONFIG_UART_HOST) & 0x10)) lpc_comx_put_char(LM4_UART_DR(CONFIG_UART_HOST)); #endif } /* Must be same prio as LPC interrupt handler so they don't preempt */ DECLARE_IRQ(IRQ_UART_HOST, uart_host_interrupt, 2); #endif /* CONFIG_UART_HOST */ static void uart_config(int port) { /* Disable the port */ LM4_UART_CTL(port) = 0x0300; /* Use the internal oscillator */ LM4_UART_CC(port) = 0x1; /* Set the baud rate divisor */ LM4_UART_IBRD(port) = (INTERNAL_CLOCK / 16) / CONFIG_UART_BAUD_RATE; LM4_UART_FBRD(port) = (((INTERNAL_CLOCK / 16) % CONFIG_UART_BAUD_RATE) * 64 + CONFIG_UART_BAUD_RATE / 2) / CONFIG_UART_BAUD_RATE; /* * 8-N-1, FIFO enabled. Must be done after setting * the divisor for the new divisor to take effect. */ LM4_UART_LCRH(port) = 0x70; /* * Interrupt when RX fifo at minimum (>= 1/8 full), and TX fifo * when <= 1/4 full */ LM4_UART_IFLS(port) = 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(port) = 0x50; /* Enable the port */ LM4_UART_CTL(port) |= 0x0001; } void uart_init(void) { uint32_t mask = 0; /* * Enable UART0 in run, sleep, and deep sleep modes. Enable the Host * UART in run and sleep modes. */ mask |= 1; clock_enable_peripheral(CGC_OFFSET_UART, mask, CGC_MODE_ALL); #ifdef CONFIG_UART_HOST mask |= BIT(CONFIG_UART_HOST); #endif clock_enable_peripheral(CGC_OFFSET_UART, mask, CGC_MODE_RUN | CGC_MODE_SLEEP); gpio_config_module(MODULE_UART, 1); /* Configure UARTs (identically) */ uart_config(0); #ifdef CONFIG_UART_HOST uart_config(CONFIG_UART_HOST); #endif /* * Enable interrupts for UART0 only. Host UART will have to wait * until the LPC bus is initialized. */ uart_clear_rx_fifo(0); task_enable_irq(LM4_IRQ_UART0); init_done = 1; } #ifdef CONFIG_LOW_POWER_IDLE void uart_enter_dsleep(void) { const struct gpio_info g = gpio_list[GPIO_UART0_RX]; /* Disable the UART0 module interrupt. */ task_disable_irq(LM4_IRQ_UART0); /* Disable UART0 peripheral in deep sleep. */ clock_disable_peripheral(CGC_OFFSET_UART, 0x1, CGC_MODE_DSLEEP); /* * Set the UART0 RX pin to be a generic GPIO with the flags defined * in the board.c file. */ gpio_reset(GPIO_UART0_RX); /* Clear any pending GPIO interrupts on the UART0 RX pin. */ LM4_GPIO_ICR(g.port) = g.mask; /* Enable GPIO interrupts on the UART0 RX pin. */ gpio_enable_interrupt(GPIO_UART0_RX); } void uart_exit_dsleep(void) { const struct gpio_info g = gpio_list[GPIO_UART0_RX]; /* * If the UART0 RX GPIO interrupt has not fired, then no edge has been * detected. Disable the GPIO interrupt so that switching the pin over * to a UART pin doesn't inadvertently cause a GPIO edge interrupt. * Note: we can't disable this interrupt if it has already fired * because then the IRQ will not get called. */ if (!(LM4_GPIO_MIS(g.port) & g.mask)) gpio_disable_interrupt(GPIO_UART0_RX); /* Configure UART0 pins for use in UART peripheral. */ gpio_config_module(MODULE_UART, 1); /* Clear pending interrupts on UART peripheral and enable interrupts. */ uart_clear_rx_fifo(0); task_enable_irq(LM4_IRQ_UART0); /* Enable UART0 peripheral in deep sleep */ clock_enable_peripheral(CGC_OFFSET_UART, 0x1, CGC_MODE_DSLEEP); } void uart_deepsleep_interrupt(enum gpio_signal signal) { /* * 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(); /* Disable interrupts on UART0 RX pin to avoid repeated interrupts. */ gpio_disable_interrupt(GPIO_UART0_RX); } #endif /* CONFIG_LOW_POWER_IDLE */ /*****************************************************************************/ /* COMx functions */ #ifdef CONFIG_UART_HOST void uart_comx_enable(void) { uart_clear_rx_fifo(CONFIG_UART_HOST); task_enable_irq(IRQ_UART_HOST); } int uart_comx_putc_ok(void) { if (LM4_UART_FR(CONFIG_UART_HOST) & 0x20) { /* * FIFO is full, so enable transmit interrupt to let us know * when it empties. */ LM4_UART_IM(CONFIG_UART_HOST) |= 0x20; return 0; } else { return 1; } } void uart_comx_putc(int c) { LM4_UART_DR(CONFIG_UART_HOST) = c; } #endif /* CONFIG_UART_HOST */ /*****************************************************************************/ /* Console commands */ #ifdef CONFIG_CMD_COMXTEST /** * Write a character to COMx, waiting for space in the output buffer if * necessary. */ static void uart_comx_putc_wait(int c) { while (!uart_comx_putc_ok()) ; uart_comx_putc(c); } static int command_comxtest(int argc, char **argv) { /* Put characters to COMX port */ const char *c = argc > 1 ? argv[1] : "testing comx output!"; ccprintf("Writing \"%s\\r\\n\" to COMx UART...\n", c); while (*c) uart_comx_putc_wait(*c++); uart_comx_putc_wait('\r'); uart_comx_putc_wait('\n'); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(comxtest, command_comxtest, "[string]", "Write test data to COMx uart"); #endif /* CONFIG_CMD_COMXTEST */