| /* 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 */ |