317 lines
7.5 KiB
C
317 lines
7.5 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <hypervisor.h>
|
|
#include "uart16550.h"
|
|
#include "serial_internal.h"
|
|
|
|
/* Mapping of 16c550 write-only registers to appropriate structure members */
|
|
#define THR_IDX RBR_IDX
|
|
#define IIR_IDX FCR_IDX
|
|
#define DLL_IDX RBR_IDX
|
|
#define DLM_IDX IER_IDX
|
|
|
|
#if defined(CONFIG_SERIAL_PIO_BASE)
|
|
static int serial_port_mapped = 1;
|
|
static int uart_enabled = 1;
|
|
#define UART_BASE_ADDRESS CONFIG_SERIAL_PIO_BASE
|
|
#elif defined(CONFIG_SERIAL_MMIO_BASE)
|
|
static int serial_port_mapped;
|
|
static int uart_enabled = 1;
|
|
#define UART_BASE_ADDRESS CONFIG_SERIAL_MMIO_BASE
|
|
#else
|
|
#warning "no uart base configure, please check!"
|
|
static int serial_port_mapped;
|
|
static int uart_enabled;
|
|
#define UART_BASE_ADDRESS 0
|
|
#endif
|
|
|
|
typedef uint32_t uart_reg_t;
|
|
|
|
enum UART_REG_IDX{
|
|
RBR_IDX, /* 0 */
|
|
IER_IDX, /* 1 */
|
|
FCR_IDX, /* 2 */
|
|
LCR_IDX, /* 3 */
|
|
MCR_IDX, /* 4 */
|
|
ISR_IDX, /* 5 */
|
|
MSR_IDX, /* 6 */
|
|
SPR_IDX, /* 7 */
|
|
MDR1_IDX, /* 8 */
|
|
REG9_IDX, /* 9 */
|
|
REGA_IDX, /* A */
|
|
REGB_IDX, /* B */
|
|
REGC_IDX, /* C */
|
|
REGD_IDX, /* D */
|
|
REGE_IDX, /* E */
|
|
UASR_IDX, /* F */
|
|
SCR_IDX, /* 10*/
|
|
SSR_IDX, /* 11*/
|
|
REG12_IDX, /* 12*/
|
|
OSC_12M_SEL_IDX, /* 13*/
|
|
};
|
|
|
|
/* CPU oscillator clock */
|
|
#define CPU_OSC_CLOCK 1843200 /* 1.8432 MHz */
|
|
|
|
/* UART hardware definitions */
|
|
#define UART_CLOCK_RATE CPU_OSC_CLOCK
|
|
#define UART_BUFFER_SIZE 2048
|
|
|
|
static inline uint32_t uart16550_read_reg(uint64_t base, uint32_t reg_idx)
|
|
{
|
|
if (serial_port_mapped) {
|
|
return io_read_byte((uint16_t)base + reg_idx);
|
|
} else {
|
|
return mmio_read_long((void*)((uint32_t*)HPA2HVA(base) + reg_idx));
|
|
}
|
|
}
|
|
|
|
static inline void uart16550_write_reg(uint64_t base,
|
|
uint32_t val, uint32_t reg_idx)
|
|
{
|
|
if (serial_port_mapped) {
|
|
io_write_byte(val, (uint16_t)base + reg_idx);
|
|
} else {
|
|
mmio_write_long(val, (void*)((uint32_t*)HPA2HVA(base) + reg_idx));
|
|
}
|
|
}
|
|
|
|
static void uart16550_enable(__unused struct tgt_uart *tgt_uart)
|
|
{
|
|
}
|
|
|
|
static int uart16550_calc_baud_div(__unused struct tgt_uart *tgt_uart,
|
|
uint32_t ref_freq, uint32_t *baud_div_ptr, uint32_t baud_rate)
|
|
{
|
|
uint32_t baud_multiplier = baud_rate < BAUD_460800 ? 16 : 13;
|
|
|
|
*baud_div_ptr = ref_freq / (baud_multiplier * baud_rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uart16550_set_baud_rate(struct tgt_uart *tgt_uart,
|
|
uint32_t baud_rate)
|
|
{
|
|
int status;
|
|
uint32_t baud_div, duart_clock = CPU_OSC_CLOCK;
|
|
uart_reg_t temp_reg;
|
|
|
|
/* Calculate baud divisor */
|
|
status = uart16550_calc_baud_div(
|
|
tgt_uart, duart_clock, &baud_div, baud_rate);
|
|
|
|
if (status == 0) {
|
|
/* Enable DLL and DLM registers for setting the Divisor */
|
|
temp_reg = uart16550_read_reg(tgt_uart->base_address, LCR_IDX);
|
|
temp_reg |= LCR_DLAB;
|
|
uart16550_write_reg(tgt_uart->base_address, temp_reg, LCR_IDX);
|
|
|
|
/* Write the appropriate divisor value */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
((baud_div >> 8) & 0xFF), DLM_IDX);
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
(baud_div & 0xFF), DLL_IDX);
|
|
|
|
/* Disable DLL and DLM registers */
|
|
temp_reg &= ~LCR_DLAB;
|
|
uart16550_write_reg(tgt_uart->base_address, temp_reg, LCR_IDX);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int uart16550_init(struct tgt_uart *tgt_uart)
|
|
{
|
|
int status = 0;
|
|
|
|
if (!uart_enabled) {
|
|
/*uart will not be used */
|
|
status = -ENODEV;
|
|
} else {
|
|
if (strcmp(tgt_uart->uart_id, "STDIO") == 0) {
|
|
atomic_store(&tgt_uart->open_count, 0);
|
|
} else {
|
|
/* set open count to 1 to prevent open */
|
|
atomic_store(&tgt_uart->open_count, 1);
|
|
status = -EINVAL;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int uart16550_open(struct tgt_uart *tgt_uart,
|
|
struct uart_config *config)
|
|
{
|
|
uint32_t temp32;
|
|
int status = 0;
|
|
|
|
if (strcmp(tgt_uart->uart_id, "STDIO") == 0) {
|
|
if (atomic_cmpxchg(&tgt_uart->open_count, 0, 1) != 0)
|
|
return -EBUSY;
|
|
|
|
/* Call UART setup function */
|
|
/* Enable TX and RX FIFOs */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
FCR_FIFOE | FCR_RFR | FCR_TFR, FCR_IDX);
|
|
|
|
/* Set parity value */
|
|
if (config->parity_bits == PARITY_ODD) {
|
|
/* Odd parity */
|
|
temp32 = LCR_PARITY_ODD;
|
|
} else if (config->parity_bits == PARITY_EVEN) {
|
|
/* Even parity */
|
|
temp32 = LCR_PARITY_EVEN;
|
|
} else {
|
|
/* No parity */
|
|
temp32 = LCR_PARITY_NONE;
|
|
}
|
|
|
|
/* Set Data length */
|
|
if (config->data_bits == DATA_7) {
|
|
/* Set bits for 7 data bits */
|
|
temp32 |= LCR_WL7;
|
|
} else {
|
|
/* Set bits for 8 data bits */
|
|
temp32 |= LCR_WL8;
|
|
}
|
|
|
|
/* Check for 1 stop bit */
|
|
if (config->stop_bits == STOP_1) {
|
|
/* Set bits for 1 stop bit */
|
|
temp32 |= LCR_NB_STOP_BITS_1;
|
|
} else {
|
|
/* Set bits for 2 stop bits */
|
|
temp32 |= LCR_NB_STOP_BITS_2;
|
|
}
|
|
|
|
/* Set-up data bits / parity / stop bits. */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
temp32, LCR_IDX);
|
|
|
|
/* Disable interrupts (we use polling) */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
UART_IER_DISABLE_ALL, IER_IDX);
|
|
|
|
/* Set baud rate */
|
|
uart16550_set_baud_rate(tgt_uart, config->baud_rate);
|
|
|
|
/* Data terminal ready + Request to send */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
MCR_RTS | MCR_DTR, MCR_IDX);
|
|
|
|
/* Enable the UART hardware */
|
|
uart16550_enable(tgt_uart);
|
|
} else {
|
|
status = -ENODEV;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int uart16550_get_rx_err(uint32_t rx_data)
|
|
{
|
|
int rx_status = SD_RX_NO_ERROR;
|
|
|
|
/* Check for RX overrun error */
|
|
if ((rx_data & LSR_OE))
|
|
rx_status |= SD_RX_OVERRUN_ERROR;
|
|
|
|
/* Check for RX parity error */
|
|
if ((rx_data & LSR_PE))
|
|
rx_status |= SD_RX_PARITY_ERROR;
|
|
|
|
/* Check for RX frame error */
|
|
if ((rx_data & LSR_FE))
|
|
rx_status |= SD_RX_FRAME_ERROR;
|
|
|
|
/* Return the rx status */
|
|
return rx_status;
|
|
}
|
|
|
|
static void uart16550_close(struct tgt_uart *tgt_uart)
|
|
{
|
|
if (tgt_uart != NULL) {
|
|
if (atomic_cmpxchg(&tgt_uart->open_count, 1, 0) == 1) {
|
|
/* TODO: Add logic to disable the UART */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void uart16550_read(struct tgt_uart *tgt_uart, void *buffer,
|
|
uint32_t *bytes_read)
|
|
{
|
|
/* If a character has been received, read it */
|
|
if ((uart16550_read_reg(tgt_uart->base_address, ISR_IDX) & LSR_DR)
|
|
== LSR_DR) {
|
|
/* Read a character */
|
|
*(uint8_t *)buffer =
|
|
uart16550_read_reg(tgt_uart->base_address, RBR_IDX);
|
|
|
|
/* Read 1 byte */
|
|
*bytes_read = 1;
|
|
} else {
|
|
*bytes_read = 0;
|
|
}
|
|
}
|
|
|
|
static void uart16550_write(struct tgt_uart *tgt_uart,
|
|
const void *buffer, uint32_t *bytes_written)
|
|
{
|
|
/* Ensure there are no further Transmit buffer write requests */
|
|
do {
|
|
} while (!(uart16550_read_reg(tgt_uart->base_address,
|
|
ISR_IDX) & LSR_THRE));
|
|
|
|
/* Transmit the character. */
|
|
uart16550_write_reg(tgt_uart->base_address,
|
|
*(uint8_t *)buffer, THR_IDX);
|
|
|
|
if (bytes_written != NULL)
|
|
*bytes_written = 1;
|
|
}
|
|
|
|
static bool uart16550_tx_is_busy(struct tgt_uart *tgt_uart)
|
|
{
|
|
return ((uart16550_read_reg(tgt_uart->base_address, ISR_IDX) &
|
|
(LSR_TEMT)) == 0) ? true : false;
|
|
}
|
|
|
|
static bool uart16550_rx_data_is_avail(struct tgt_uart *tgt_uart,
|
|
uint32_t *lsr_reg)
|
|
{
|
|
*(uart_reg_t *)lsr_reg =
|
|
uart16550_read_reg(tgt_uart->base_address, ISR_IDX);
|
|
return ((*(uart_reg_t *)lsr_reg & LSR_DR) == LSR_DR) ? true : false;
|
|
}
|
|
|
|
struct tgt_uart Tgt_Uarts[SERIAL_MAX_DEVS] = {
|
|
{
|
|
.uart_id = "STDIO",
|
|
.base_address = UART_BASE_ADDRESS,
|
|
.clock_frequency = UART_CLOCK_RATE,
|
|
.buffer_size = UART_BUFFER_SIZE,
|
|
.init = uart16550_init,
|
|
.open = uart16550_open,
|
|
.close = uart16550_close,
|
|
.read = uart16550_read,
|
|
.write = uart16550_write,
|
|
.tx_is_busy = uart16550_tx_is_busy,
|
|
.rx_data_is_avail = uart16550_rx_data_is_avail,
|
|
.get_rx_err = uart16550_get_rx_err,
|
|
|
|
}
|
|
};
|
|
|
|
void uart16550_set_property(int enabled, int port_mapped, uint64_t base_addr)
|
|
{
|
|
uart_enabled = enabled;
|
|
serial_port_mapped = port_mapped;
|
|
Tgt_Uarts[0].base_address = base_addr;
|
|
}
|