| /* Copyright 2016 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* UART module for ISH */ |
| #include "atomic.h" |
| #include "common.h" |
| #include "console.h" |
| #include "interrupts.h" |
| #include "math_util.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "task.h" |
| #include "uart.h" |
| #include "uart_defs.h" |
| |
| #define CPUTS(outstr) cputs(CC_LPC, outstr) |
| #define CPRINTS(format, args...) cprints(CC_LPC, format, ##args) |
| #define CPRINTF(format, args...) cprintf(CC_LPC, format, ##args) |
| |
| static const uint32_t baud_conf[][BAUD_TABLE_MAX] = { |
| { B9600, 9600 }, { B57600, 57600 }, { B115200, 115200 }, |
| { B921600, 921600 }, { B2000000, 2000000 }, { B3000000, 3000000 }, |
| { B3250000, 3250000 }, { B3500000, 3500000 }, { B4000000, 4000000 }, |
| { B19200, 19200 }, |
| }; |
| |
| static struct uart_ctx uart_ctx[UART_DEVICES] = { |
| { |
| .id = 0, |
| .base = UART0_BASE, |
| .input_freq = UART_ISH_INPUT_FREQ, |
| .addr_interval = UART_ISH_ADDR_INTERVAL, |
| .uart_state = UART_STATE_CG, |
| }, |
| { |
| .id = 1, |
| .base = UART1_BASE, |
| .input_freq = UART_ISH_INPUT_FREQ, |
| .addr_interval = UART_ISH_ADDR_INTERVAL, |
| .uart_state = UART_STATE_CG, |
| }, |
| { |
| .id = 2, |
| .base = UART2_BASE, |
| .input_freq = UART_ISH_INPUT_FREQ, |
| .addr_interval = UART_ISH_ADDR_INTERVAL, |
| .uart_state = UART_STATE_CG, |
| } |
| }; |
| |
| static int init_done; |
| |
| int uart_init_done(void) |
| { |
| return init_done; |
| } |
| |
| void uart_tx_start(void) |
| { |
| if (!IS_ENABLED(CONFIG_POLLING_UART)) { |
| if (IER(ISH_DEBUG_UART) & IER_TDRQ) |
| return; |
| |
| /* Do not allow deep sleep while transmit in progress */ |
| disable_sleep(SLEEP_MASK_UART); |
| |
| IER(ISH_DEBUG_UART) |= IER_TDRQ; |
| } |
| } |
| |
| void uart_tx_stop(void) |
| { |
| if (!IS_ENABLED(CONFIG_POLLING_UART)) { |
| /* Re-allow deep sleep */ |
| enable_sleep(SLEEP_MASK_UART); |
| |
| IER(ISH_DEBUG_UART) &= ~IER_TDRQ; |
| } |
| } |
| |
| void uart_tx_flush(void) |
| { |
| if (!IS_ENABLED(CONFIG_POLLING_UART)) { |
| while (!(LSR(ISH_DEBUG_UART) & LSR_TEMT)) |
| continue; |
| } |
| } |
| |
| int uart_tx_ready(void) |
| { |
| return LSR(ISH_DEBUG_UART) & LSR_TEMT; |
| } |
| |
| int uart_rx_available(void) |
| { |
| if (IS_ENABLED(CONFIG_POLLING_UART)) |
| return 0; |
| |
| return LSR(ISH_DEBUG_UART) & LSR_DR; |
| } |
| |
| void uart_write_char(char c) |
| { |
| /* Wait till receiver is ready */ |
| while (!uart_tx_ready()) |
| continue; |
| |
| THR(ISH_DEBUG_UART) = c; |
| } |
| |
| int uart_read_char(void) |
| { |
| return RBR(ISH_DEBUG_UART); |
| } |
| |
| static void uart_ec_interrupt(void) |
| { |
| /* Read input FIFO until empty, then fill output FIFO */ |
| uart_process_input(); |
| uart_process_output(); |
| } |
| #ifndef CONFIG_POLLING_UART |
| DECLARE_IRQ(ISH_DEBUG_UART_IRQ, uart_ec_interrupt); |
| #endif |
| |
| static int uart_return_baud_rate_by_id(int baud_rate_id) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(baud_conf); i++) { |
| if (baud_conf[i][BAUD_IDX] == baud_rate_id) |
| return baud_conf[i][BAUD_SPEED]; |
| } |
| |
| return -1; |
| } |
| |
| static void uart_hw_init(enum UART_PORT id) |
| { |
| uint32_t divisor; /* baud rate divisor */ |
| uint8_t mcr = 0; |
| uint8_t fcr = 0; |
| struct uart_ctx *ctx = &uart_ctx[id]; |
| uint8_t fraction; |
| |
| /* Calculate baud rate divisor */ |
| divisor = (ctx->input_freq / ctx->baud_rate) >> 4; |
| if (IS_ENABLED(CONFIG_ISH_DW_UART)) { |
| /* calculate the fractional part */ |
| fraction = ceil_for(ctx->input_freq, ctx->baud_rate) - |
| (divisor << 4); |
| } else { |
| MUL(ctx->id) = (divisor * ctx->baud_rate); |
| DIV(ctx->id) = (ctx->input_freq / 16); |
| PS(ctx->id) = 16; |
| } |
| |
| /* Set the DLAB to access the baud rate divisor registers */ |
| LCR(ctx->id) = LCR_DLAB; |
| DLL(ctx->id) = (divisor & 0xff); |
| DLH(ctx->id) = ((divisor >> 8) & 0xff); |
| if (IS_ENABLED(CONFIG_ISH_DW_UART)) |
| DLF(ctx->id) = fraction; |
| |
| /* 8 data bits, 1 stop bit, no parity, clear DLAB */ |
| LCR(ctx->id) = LCR_8BIT_CHR; |
| |
| if (ctx->client_flags & UART_CONFIG_HW_FLOW_CONTROL) |
| mcr = MCR_AUTO_FLOW_EN; |
| |
| /* needs to be set regardless of flow control */ |
| if (!IS_ENABLED(CONFIG_ISH_DW_UART)) |
| mcr |= MCR_INTR_ENABLE; |
| |
| mcr |= (MCR_RTS | MCR_DTR); |
| MCR(ctx->id) = mcr; |
| |
| if (IS_ENABLED(CONFIG_ISH_DW_UART)) |
| fcr = FCR_TET_EMPTY | FCR_RT_1CHAR; |
| else |
| fcr = FCR_FIFO_SIZE_64 | FCR_ITL_FIFO_64_BYTES_1; |
| |
| /* configure FIFOs */ |
| FCR(ctx->id) = (fcr | FCR_FIFO_ENABLE | FCR_RESET_RX | FCR_RESET_TX); |
| |
| if (!IS_ENABLED(CONFIG_ISH_DW_UART)) |
| /* enable UART unit */ |
| ABR(ctx->id) = ABR_UUE; |
| |
| /* clear the port */ |
| RBR(ctx->id); |
| |
| if (IS_ENABLED(CONFIG_POLLING_UART)) |
| IER(ctx->id) = 0x00; |
| else |
| IER(ctx->id) = IER_RECV; |
| } |
| |
| void uart_port_restore(void) |
| { |
| uart_hw_init(ISH_DEBUG_UART); |
| } |
| |
| void uart_to_idle(void) |
| { |
| int id; |
| |
| for (id = 0; id < UART_DEVICES; id++) { |
| LCR(id) = 0x80; |
| DLL(id) = 0x1; |
| DLH(id) = 0x0; |
| LCR(id) = 0x0; |
| } |
| } |
| |
| static void uart_stop_hw(enum UART_PORT id) |
| { |
| int i; |
| uint32_t fifo_len; |
| |
| if (!IS_ENABLED(CONFIG_ISH_DW_UART)) { |
| /* Manually clearing the fifo from possible noise. |
| * Entering D0i3 when fifo is not cleared may result in a hang. |
| */ |
| fifo_len = (FOR(id) & FOR_OCCUPANCY_MASK) >> FOR_OCCUPANCY_OFFS; |
| |
| for (i = 0; i < fifo_len; i++) |
| (void)RBR(id); |
| } |
| |
| /* No interrupts are enabled */ |
| IER(id) = 0; |
| MCR(id) = 0; |
| |
| /* Clear and disable FIFOs */ |
| FCR(id) = (FCR_RESET_RX | FCR_RESET_TX); |
| |
| if (!IS_ENABLED(CONFIG_ISH_DW_UART)) |
| /* Disable uart unit */ |
| ABR(id) = 0; |
| } |
| |
| static int uart_client_init(enum UART_PORT id, uint32_t baud_rate_id, int flags) |
| { |
| if ((uart_ctx[id].base == 0) || (id >= UART_DEVICES)) |
| return UART_ERROR; |
| |
| if (!bool_compare_and_swap_u32(&uart_ctx[id].is_open, 0, 1)) |
| return UART_BUSY; |
| |
| uart_ctx[id].baud_rate = uart_return_baud_rate_by_id(baud_rate_id); |
| |
| if ((uart_ctx[id].baud_rate == -1) || (uart_ctx[id].baud_rate == 0)) |
| uart_ctx[id].baud_rate = UART_DEFAULT_BAUD_RATE; |
| |
| uart_ctx[id].client_flags = flags; |
| |
| atomic_and(&uart_ctx[id].uart_state, ~UART_STATE_CG); |
| uart_hw_init(id); |
| |
| return EC_SUCCESS; |
| } |
| |
| static void uart_drv_init(void) |
| { |
| int i; |
| |
| /* Disable UART */ |
| for (i = 0; i < UART_DEVICES; i++) |
| uart_stop_hw(i); |
| |
| if (!IS_ENABLED(CONFIG_ISH_DW_UART)) |
| /* Enable HSU global interrupts (DMA/U0/U1) and set PMEN bit |
| * to allow PMU to clock gate ISH |
| */ |
| HSU_REG_GIEN = (GIEN_DMA_EN | GIEN_UART0_EN | GIEN_UART1_EN | |
| GIEN_PWR_MGMT); |
| |
| task_enable_irq(ISH_DEBUG_UART_IRQ); |
| } |
| |
| void uart_init(void) |
| { |
| uart_drv_init(); |
| uart_client_init(ISH_DEBUG_UART, B115200, 0); |
| init_done = 1; |
| } |