|  | /* Copyright (c) 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 |= (1 << 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 */ |