| /* |
| * 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)); |
| } |