318 lines
7.9 KiB
C
318 lines
7.9 KiB
C
/*
|
|
* uart-8250.c -- polling driver for 32-bit 8250 UART.
|
|
* Provides _uart_8250_setup(), _read() and _write(). Please note that
|
|
* _read() and _write() from this file can only be used for console access.
|
|
*
|
|
* Copyright (c) 2024 Synopsys Inc.
|
|
*
|
|
* The authors hereby grant permission to use, copy, modify, distribute,
|
|
* and license this software and its documentation for any purpose, provided
|
|
* that existing copyright notices are retained in all copies and that this
|
|
* notice is included verbatim in any distributions. No written agreement,
|
|
* license, or royalty fee is required for any of the authorized uses.
|
|
* Modifications to this software may be copyrighted by their authors
|
|
* and need not follow the licensing terms described here, provided that
|
|
* the new terms are clearly indicated on the first page of each file where
|
|
* they apply.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include "arc-specific.h"
|
|
|
|
/*
|
|
* List of UART 8250 registers with offsets:
|
|
*
|
|
* 0x00 - Transmit Holding Register, WO, LCR_DLAB == 0
|
|
* 0x00 - Receive Buffer Register, RO, LCR_DLAB == 0
|
|
* 0x00 - Divisor Latch Low, RW, LCR_DLAB == 1
|
|
* 0x04 - Divisor Latch High, RW, LCR_DLAB == 1
|
|
* 0x04 - Interrupt Enable Register, RW, LCR_DLAB == 0
|
|
* 0x08 - FIFO Control Register, WO
|
|
* 0x08 - Interrupt Identification Register, RO
|
|
* 0x0C - Line Control Register, RW
|
|
* 0x10 - Modem Control Register, RW
|
|
* 0x14 - Line Status Register, RO
|
|
* 0x18 - Modem Status Register, RO
|
|
*/
|
|
|
|
#define THR_OFFSET 0x00
|
|
#define RBR_OFFSET 0x00
|
|
#define DLL_OFFSET 0x00
|
|
#define DLH_OFFSET 0x04
|
|
#define IER_OFFSET 0x04
|
|
#define FCR_OFFSET 0x08
|
|
#define IIR_OFFSET 0x08
|
|
#define LCR_OFFSET 0x0C
|
|
#define MCR_OFFSET 0x10
|
|
#define LSR_OFFSET 0x14
|
|
#define MSR_OFFSET 0x18
|
|
|
|
/*
|
|
* LCR (Line Control Register) fields:
|
|
*
|
|
* [7] - Divisor Latch Access Bit
|
|
* [6] - Set Break
|
|
* [5] - Stick Parity
|
|
* [4] - Even Parity Select
|
|
* [3] - Parity Enable
|
|
* [2] - Number of Stop Bits
|
|
* [1:0] - Data Length Select. Values:
|
|
* 0b11 - 8 data bits per character (default)
|
|
* 0b10 - 7 data bits per character
|
|
* 0b01 - 6 data bits per character
|
|
* 0b00 - 5 data bits per character
|
|
*/
|
|
|
|
#define LCR_DLAB_SHIFT 7
|
|
#define LCR_SB_SHIFT 6
|
|
#define LCR_SP_SHIFT 5
|
|
#define LCR_EPS_SHIFT 4
|
|
#define LCR_PEN_SHIFT 3
|
|
#define LCR_STB_SHIFT 2
|
|
#define LCR_DLS_SHIFT 0
|
|
/*
|
|
* MCR (Modem Control Register) fields:
|
|
*
|
|
* [4] - LoopBack Bit
|
|
* [3] - Auxiliary Output 2
|
|
* [2] - Auxiliary Output 1
|
|
* [1] - Request To Send (ON by default)
|
|
* [0] - Data Terminal Ready (ON by default)
|
|
*/
|
|
|
|
#define MCR_LB_SHIFT 4
|
|
#define MCR_OUT2_SHIFT 3
|
|
#define MCR_OUT1_SHIFT 2
|
|
#define MCR_RTS_SHIFT 1
|
|
#define MCR_DTR_SHIFT 0
|
|
|
|
/*
|
|
* LSR (Line Status Register) fields:
|
|
*
|
|
* [7] - Receiver FIFO Error
|
|
* [6] - Transmitter Empty
|
|
* [5] - Transmit Holding Register Empty
|
|
* [4] - Break Interrupt
|
|
* [3] - Framing Error
|
|
* [2] - Parity Error
|
|
* [1] - Overrun Error
|
|
* [0] - Data Ready
|
|
*/
|
|
|
|
#define LSR_RFE_SHIFT 7
|
|
#define LSR_TEMT_SHIFT 6
|
|
#define LSR_THRE_SHIFT 5
|
|
#define LSR_BI_SHIFT 4
|
|
#define LSR_FE_SHIFT 3
|
|
#define LSR_PE_SHIFT 2
|
|
#define LSR_OE_SHIFT 1
|
|
#define LSR_DR_SHIFT 0
|
|
|
|
/*
|
|
* Default initial values for configuration registers.
|
|
*/
|
|
|
|
#define FCR_DEFAULT 0x0
|
|
#define IER_DEFAULT 0x0
|
|
#define LCR_DLS_DEFAULT 0x3
|
|
#define LCR_DEFAULT (LCR_DLS_DEFAULT << LCR_DLS_SHIFT)
|
|
#define MCR_RTS_DEFAULT 0x1
|
|
#define MCR_DTR_DEFAULT 0x1
|
|
#define MCR_DEFAULT ((MCR_RTS_DEFAULT << MCR_RTS_SHIFT) | \
|
|
(MCR_DTR_DEFAULT << MCR_DTR_SHIFT))
|
|
|
|
#define LCR_MODE_SETUP (0x1 << LCR_DLAB_SHIFT)
|
|
#define LSR_MODE_GETC (0x1 << LSR_DR_SHIFT)
|
|
#define LSR_MODE_PUTC ((0x1 << LSR_TEMT_SHIFT) | (0x1 << LSR_THRE_SHIFT))
|
|
|
|
/* Main UART control structure. */
|
|
struct _uart_8250 {
|
|
volatile char *base; /* Start of UART registers. */
|
|
uint32_t clk; /* UART clock. */
|
|
uint32_t baud; /* Baud rate. */
|
|
int aux_mapped; /* If UART registers are mapped to AUX or memory. */
|
|
int ready; /* If UART is ready to use or not. */
|
|
};
|
|
|
|
static struct _uart_8250 _uart_8250;
|
|
|
|
/* Write 32-bit value to the UART register. */
|
|
static inline void
|
|
_uart_8250_write_reg (const struct _uart_8250 *uart, uint32_t reg,
|
|
uint32_t value)
|
|
{
|
|
if (uart->aux_mapped)
|
|
write_aux_reg (value, (uint32_t) uart->base + reg);
|
|
else
|
|
*(volatile uint32_t *)(uart->base + reg) = value;
|
|
}
|
|
|
|
/* Read 32-bit value from the UART register. */
|
|
static inline uint32_t
|
|
_uart_8250_read_reg (const struct _uart_8250 *uart, uint32_t reg)
|
|
{
|
|
if (uart->aux_mapped)
|
|
return read_aux_reg ((uint32_t) uart->base + reg);
|
|
else
|
|
return *(volatile uint32_t *)(uart->base + reg);
|
|
}
|
|
|
|
/* Wait until all flags are set. */
|
|
static inline void
|
|
_uart_8250_wait (const struct _uart_8250 *uart, uint32_t reg, uint32_t flags)
|
|
{
|
|
while (1)
|
|
{
|
|
if ((_uart_8250_read_reg (uart, reg) & flags) == flags)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Get one character from UART. CR is converted to NL. */
|
|
static int
|
|
_uart_8250_getc (const struct _uart_8250 *uart)
|
|
{
|
|
char c;
|
|
|
|
_uart_8250_wait (uart, LSR_OFFSET, LSR_MODE_GETC);
|
|
|
|
c = _uart_8250_read_reg (uart, RBR_OFFSET);
|
|
|
|
if (c == '\r')
|
|
c = '\n';
|
|
|
|
return c;
|
|
}
|
|
|
|
/* Put one character to UART. CR is placed before NL. */
|
|
static void
|
|
_uart_8250_putc (const struct _uart_8250 *uart, char c)
|
|
{
|
|
if (c == '\n')
|
|
_uart_8250_putc (uart, '\r');
|
|
|
|
_uart_8250_wait (uart, LSR_OFFSET, LSR_MODE_PUTC);
|
|
|
|
_uart_8250_write_reg (uart, THR_OFFSET, c);
|
|
}
|
|
|
|
/*
|
|
* Setup UART control structure and following parameters:
|
|
*
|
|
* - baudrate if clock and baudrate are passed to function
|
|
* - 8n1 (8 data bits, no parity bit, one stop bit)
|
|
* - disable interrupts
|
|
* - disable FIFO
|
|
* - set Request To Send and Data Terminal Ready
|
|
*
|
|
* Arguments:
|
|
*
|
|
* - base - start address of UART registers
|
|
* - aux_mapped - set if UART registers are mapped to ARC AUX
|
|
* - clk - UART clock frequency
|
|
* - baud - UART baudrate to setup
|
|
*
|
|
* The function returns 0 on success.
|
|
*/
|
|
int
|
|
_uart_8250_setup (void *base, int aux_mapped, uint32_t clk, uint32_t baud)
|
|
{
|
|
struct _uart_8250 *uart = &_uart_8250;
|
|
|
|
uart->base = base;
|
|
uart->aux_mapped = aux_mapped;
|
|
uart->clk = clk;
|
|
uart->baud = baud;
|
|
|
|
if (clk && baud)
|
|
{
|
|
uint32_t div;
|
|
|
|
div = ((clk + 8 * baud) / baud) / 16;
|
|
_uart_8250_write_reg (uart, LCR_OFFSET, LCR_MODE_SETUP);
|
|
_uart_8250_write_reg (uart, DLL_OFFSET, div & 0xFF);
|
|
_uart_8250_write_reg (uart, DLH_OFFSET, div >> 8);
|
|
}
|
|
|
|
_uart_8250_write_reg (uart, FCR_OFFSET, FCR_DEFAULT);
|
|
_uart_8250_write_reg (uart, IER_OFFSET, IER_DEFAULT);
|
|
_uart_8250_write_reg (uart, LCR_OFFSET, LCR_DEFAULT);
|
|
_uart_8250_write_reg (uart, MCR_OFFSET, MCR_DEFAULT);
|
|
|
|
uart->ready = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* _read() is implemented only for stdin. Each read character is echoed. */
|
|
ssize_t
|
|
_read (int fd, void *buf, size_t count)
|
|
{
|
|
struct _uart_8250 *uart = &_uart_8250;
|
|
size_t bytes_read;
|
|
char *buf_char = buf;
|
|
int c;
|
|
|
|
if (fd != STDIN_FILENO)
|
|
{
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
if (!uart->ready)
|
|
{
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
bytes_read = 0;
|
|
c = EOF;
|
|
/* Break on '\n' to simulate readline behavior. */
|
|
while (bytes_read != count && c != '\n')
|
|
{
|
|
c = _uart_8250_getc (uart);
|
|
if (c == EOF)
|
|
break;
|
|
|
|
/* Echo character to the console. */
|
|
_uart_8250_putc (uart, c);
|
|
|
|
buf_char[bytes_read] = c;
|
|
bytes_read++;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
/* _write() is implemented only for stdout and stderr. */
|
|
ssize_t
|
|
_write (int fd, const char *buf, size_t nbyte)
|
|
{
|
|
struct _uart_8250 *uart = &_uart_8250;
|
|
uint32_t bytes_written;
|
|
|
|
if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
|
|
{
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
if (!uart->ready)
|
|
{
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
bytes_written = 0;
|
|
while (bytes_written != nbyte)
|
|
{
|
|
_uart_8250_putc (uart, buf[bytes_written]);
|
|
bytes_written++;
|
|
}
|
|
|
|
return bytes_written;
|
|
}
|