Files
opensbi/lib/utils/serial/uart8250.c
Bo Gan fbff3a7e07 lib: utils/serial: Support multiple UART8250 devices
Previously we assume only 1 UART8250 instance can be used. Now we support
multiple instances by introducing counterpart functions to putc/getc/init
which take an extra *dev parameter, and name them as uart8250_device_xyz()
The original functions without the *dev parameter will operate on the
default instance exactly the same as before, so no changes on the caller
is required.

Note: uart8250_device_init only does device initialization without the
console registration logic.

Signed-off-by: Bo Gan <ganboing@gmail.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://lore.kernel.org/r/20251218104243.562667-7-ganboing@gmail.com
Signed-off-by: Anup Patel <anup@brainfault.org>
2025-12-21 21:02:44 +05:30

162 lines
4.7 KiB
C

/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
*
* Authors:
* Anup Patel <anup.patel@wdc.com>
*/
#include <sbi/riscv_asm.h>
#include <sbi/riscv_io.h>
#include <sbi/sbi_console.h>
#include <sbi/sbi_domain.h>
#include <sbi_utils/serial/uart8250.h>
/* clang-format off */
#define UART_RBR_OFFSET 0 /* In: Receive Buffer Register */
#define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */
#define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */
#define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */
#define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */
#define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */
#define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */
#define UART_LCR_OFFSET 3 /* Out: Line Control Register */
#define UART_MCR_OFFSET 4 /* Out: Modem Control Register */
#define UART_LSR_OFFSET 5 /* In: Line Status Register */
#define UART_MSR_OFFSET 6 /* In: Modem Status Register */
#define UART_SCR_OFFSET 7 /* I/O: Scratch Register */
#define UART_MDR1_OFFSET 8 /* I/O: Mode Register */
#define UART_LSR_FIFOE 0x80 /* Fifo error */
#define UART_LSR_TEMT 0x40 /* Transmitter empty */
#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */
#define UART_LSR_BI 0x10 /* Break interrupt indicator */
#define UART_LSR_FE 0x08 /* Frame error indicator */
#define UART_LSR_PE 0x04 /* Parity error indicator */
#define UART_LSR_OE 0x02 /* Overrun error indicator */
#define UART_LSR_DR 0x01 /* Receiver data ready */
#define UART_LSR_BRK_ERROR_BITS 0x1E /* BI, FE, PE, OE bits */
/* The XScale PXA UARTs define these bits */
#define UART_IER_DMAE 0x80 /* DMA Requests Enable */
#define UART_IER_UUE 0x40 /* UART Unit Enable */
#define UART_IER_NRZE 0x20 /* NRZ coding Enable */
#define UART_IER_RTOIE 0x10 /* Receiver Time Out Interrupt Enable */
/* clang-format on */
static struct uart8250_device uart8250_dev;
static u32 get_reg(struct uart8250_device *dev, u32 num)
{
u32 offset = num << dev->reg_shift;
if (dev->reg_width == 1)
return readb(dev->base + offset);
else if (dev->reg_width == 2)
return readw(dev->base + offset);
else
return readl(dev->base + offset);
}
static void set_reg(struct uart8250_device *dev, u32 num, u32 val)
{
u32 offset = num << dev->reg_shift;
if (dev->reg_width == 1)
writeb(val, dev->base + offset);
else if (dev->reg_width == 2)
writew(val, dev->base + offset);
else
writel(val, dev->base + offset);
}
void uart8250_device_putc(struct uart8250_device *dev, char ch)
{
while ((get_reg(dev, UART_LSR_OFFSET) & UART_LSR_THRE) == 0)
;
set_reg(dev, UART_THR_OFFSET, ch);
}
static void uart8250_putc(char ch)
{
return uart8250_device_putc(&uart8250_dev, ch);
}
int uart8250_device_getc(struct uart8250_device *dev)
{
if (get_reg(dev, UART_LSR_OFFSET) & UART_LSR_DR)
return get_reg(dev, UART_RBR_OFFSET);
return -1;
}
static int uart8250_getc(void)
{
return uart8250_device_getc(&uart8250_dev);
}
static struct sbi_console_device uart8250_console = {
.name = "uart8250",
.console_putc = uart8250_putc,
.console_getc = uart8250_getc
};
void uart8250_device_init(struct uart8250_device *dev, unsigned long base,
u32 in_freq, u32 baudrate, u32 reg_shift,
u32 reg_width, u32 reg_offset, u32 caps)
{
u16 bdiv = 0;
dev->base = (volatile char *)base + reg_offset;
dev->reg_shift = reg_shift;
dev->reg_width = reg_width;
dev->in_freq = in_freq;
dev->baudrate = baudrate;
if (dev->baudrate) {
bdiv = (dev->in_freq + 8 * dev->baudrate) /
(16 * dev->baudrate);
}
/* Disable all interrupts */
set_reg(dev, UART_IER_OFFSET, (caps & UART_CAP_UUE) ?
UART_IER_UUE : 0x00);
/* Enable DLAB */
set_reg(dev, UART_LCR_OFFSET, 0x80);
if (bdiv) {
/* Set divisor low byte */
set_reg(dev, UART_DLL_OFFSET, bdiv & 0xff);
/* Set divisor high byte */
set_reg(dev, UART_DLM_OFFSET, (bdiv >> 8) & 0xff);
}
/* 8 bits, no parity, one stop bit */
set_reg(dev, UART_LCR_OFFSET, 0x03);
/* Enable FIFO */
set_reg(dev, UART_FCR_OFFSET, 0x01);
/* No modem control DTR RTS */
set_reg(dev, UART_MCR_OFFSET, 0x00);
/* Clear line status and read receive buffer */
if (get_reg(dev, UART_LSR_OFFSET) & UART_LSR_DR)
get_reg(dev, UART_RBR_OFFSET);
/* Set scratchpad */
set_reg(dev, UART_SCR_OFFSET, 0x00);
}
int uart8250_init(unsigned long base, u32 in_freq, u32 baudrate, u32 reg_shift,
u32 reg_width, u32 reg_offset, u32 caps)
{
uart8250_device_init(&uart8250_dev, base, in_freq, baudrate,
reg_shift, reg_width, reg_offset, caps);
sbi_console_set_device(&uart8250_console);
return sbi_domain_root_add_memrange(base, PAGE_SIZE, PAGE_SIZE,
(SBI_DOMAIN_MEMREGION_MMIO |
SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW));
}