18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Early serial console for 8250/16550 devices 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) Copyright 2004 Hewlett-Packard Development Company, L.P. 68c2ecf20Sopenharmony_ci * Bjorn Helgaas <bjorn.helgaas@hp.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Based on the 8250.c serial driver, Copyright (C) 2001 Russell King, 98c2ecf20Sopenharmony_ci * and on early_printk.c by Andi Kleen. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * This is for use before the serial driver has initialized, in 128c2ecf20Sopenharmony_ci * particular, before the UARTs have been discovered and named. 138c2ecf20Sopenharmony_ci * Instead of specifying the console device as, e.g., "ttyS0", 148c2ecf20Sopenharmony_ci * we locate the device directly by its MMIO or I/O port address. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * The user can specify the device directly, e.g., 178c2ecf20Sopenharmony_ci * earlycon=uart8250,io,0x3f8,9600n8 188c2ecf20Sopenharmony_ci * earlycon=uart8250,mmio,0xff5e0000,115200n8 198c2ecf20Sopenharmony_ci * earlycon=uart8250,mmio32,0xff5e0000,115200n8 208c2ecf20Sopenharmony_ci * or 218c2ecf20Sopenharmony_ci * console=uart8250,io,0x3f8,9600n8 228c2ecf20Sopenharmony_ci * console=uart8250,mmio,0xff5e0000,115200n8 238c2ecf20Sopenharmony_ci * console=uart8250,mmio32,0xff5e0000,115200n8 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <linux/tty.h> 278c2ecf20Sopenharmony_ci#include <linux/init.h> 288c2ecf20Sopenharmony_ci#include <linux/console.h> 298c2ecf20Sopenharmony_ci#include <linux/of.h> 308c2ecf20Sopenharmony_ci#include <linux/of_device.h> 318c2ecf20Sopenharmony_ci#include <linux/serial_reg.h> 328c2ecf20Sopenharmony_ci#include <linux/serial.h> 338c2ecf20Sopenharmony_ci#include <linux/serial_8250.h> 348c2ecf20Sopenharmony_ci#include <asm/io.h> 358c2ecf20Sopenharmony_ci#include <asm/serial.h> 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic unsigned int serial8250_early_in(struct uart_port *port, int offset) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci int reg_offset = offset; 408c2ecf20Sopenharmony_ci offset <<= port->regshift; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci switch (port->iotype) { 438c2ecf20Sopenharmony_ci case UPIO_MEM: 448c2ecf20Sopenharmony_ci return readb(port->membase + offset); 458c2ecf20Sopenharmony_ci case UPIO_MEM16: 468c2ecf20Sopenharmony_ci return readw(port->membase + offset); 478c2ecf20Sopenharmony_ci case UPIO_MEM32: 488c2ecf20Sopenharmony_ci return readl(port->membase + offset); 498c2ecf20Sopenharmony_ci case UPIO_MEM32BE: 508c2ecf20Sopenharmony_ci return ioread32be(port->membase + offset); 518c2ecf20Sopenharmony_ci case UPIO_PORT: 528c2ecf20Sopenharmony_ci return inb(port->iobase + offset); 538c2ecf20Sopenharmony_ci case UPIO_AU: 548c2ecf20Sopenharmony_ci return port->serial_in(port, reg_offset); 558c2ecf20Sopenharmony_ci default: 568c2ecf20Sopenharmony_ci return 0; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void serial8250_early_out(struct uart_port *port, int offset, int value) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci int reg_offset = offset; 638c2ecf20Sopenharmony_ci offset <<= port->regshift; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci switch (port->iotype) { 668c2ecf20Sopenharmony_ci case UPIO_MEM: 678c2ecf20Sopenharmony_ci writeb(value, port->membase + offset); 688c2ecf20Sopenharmony_ci break; 698c2ecf20Sopenharmony_ci case UPIO_MEM16: 708c2ecf20Sopenharmony_ci writew(value, port->membase + offset); 718c2ecf20Sopenharmony_ci break; 728c2ecf20Sopenharmony_ci case UPIO_MEM32: 738c2ecf20Sopenharmony_ci writel(value, port->membase + offset); 748c2ecf20Sopenharmony_ci break; 758c2ecf20Sopenharmony_ci case UPIO_MEM32BE: 768c2ecf20Sopenharmony_ci iowrite32be(value, port->membase + offset); 778c2ecf20Sopenharmony_ci break; 788c2ecf20Sopenharmony_ci case UPIO_PORT: 798c2ecf20Sopenharmony_ci outb(value, port->iobase + offset); 808c2ecf20Sopenharmony_ci break; 818c2ecf20Sopenharmony_ci case UPIO_AU: 828c2ecf20Sopenharmony_ci port->serial_out(port, reg_offset, value); 838c2ecf20Sopenharmony_ci break; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void serial_putc(struct uart_port *port, int c) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci unsigned int status; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_TX, c); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci for (;;) { 968c2ecf20Sopenharmony_ci status = serial8250_early_in(port, UART_LSR); 978c2ecf20Sopenharmony_ci if ((status & BOTH_EMPTY) == BOTH_EMPTY) 988c2ecf20Sopenharmony_ci break; 998c2ecf20Sopenharmony_ci cpu_relax(); 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void early_serial8250_write(struct console *console, 1048c2ecf20Sopenharmony_ci const char *s, unsigned int count) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct earlycon_device *device = console->data; 1078c2ecf20Sopenharmony_ci struct uart_port *port = &device->port; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci uart_console_write(port, s, count, serial_putc); 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci#ifdef CONFIG_CONSOLE_POLL 1138c2ecf20Sopenharmony_cistatic int early_serial8250_read(struct console *console, 1148c2ecf20Sopenharmony_ci char *s, unsigned int count) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct earlycon_device *device = console->data; 1178c2ecf20Sopenharmony_ci struct uart_port *port = &device->port; 1188c2ecf20Sopenharmony_ci unsigned int status; 1198c2ecf20Sopenharmony_ci int num_read = 0; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci while (num_read < count) { 1228c2ecf20Sopenharmony_ci status = serial8250_early_in(port, UART_LSR); 1238c2ecf20Sopenharmony_ci if (!(status & UART_LSR_DR)) 1248c2ecf20Sopenharmony_ci break; 1258c2ecf20Sopenharmony_ci s[num_read++] = serial8250_early_in(port, UART_RX); 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci return num_read; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci#else 1318c2ecf20Sopenharmony_ci#define early_serial8250_read NULL 1328c2ecf20Sopenharmony_ci#endif 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic void __init init_port(struct earlycon_device *device) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct uart_port *port = &device->port; 1378c2ecf20Sopenharmony_ci unsigned int divisor; 1388c2ecf20Sopenharmony_ci unsigned char c; 1398c2ecf20Sopenharmony_ci unsigned int ier; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_LCR, 0x3); /* 8n1 */ 1428c2ecf20Sopenharmony_ci ier = serial8250_early_in(port, UART_IER); 1438c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); /* no interrupt */ 1448c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_FCR, 0); /* no fifo */ 1458c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_MCR, 0x3); /* DTR + RTS */ 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (port->uartclk) { 1488c2ecf20Sopenharmony_ci divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * device->baud); 1498c2ecf20Sopenharmony_ci c = serial8250_early_in(port, UART_LCR); 1508c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_LCR, c | UART_LCR_DLAB); 1518c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_DLL, divisor & 0xff); 1528c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_DLM, (divisor >> 8) & 0xff); 1538c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_LCR, c & ~UART_LCR_DLAB); 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ciint __init early_serial8250_setup(struct earlycon_device *device, 1588c2ecf20Sopenharmony_ci const char *options) 1598c2ecf20Sopenharmony_ci{ 1608c2ecf20Sopenharmony_ci if (!(device->port.membase || device->port.iobase)) 1618c2ecf20Sopenharmony_ci return -ENODEV; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci if (!device->baud) { 1648c2ecf20Sopenharmony_ci struct uart_port *port = &device->port; 1658c2ecf20Sopenharmony_ci unsigned int ier; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci /* assume the device was initialized, only mask interrupts */ 1688c2ecf20Sopenharmony_ci ier = serial8250_early_in(port, UART_IER); 1698c2ecf20Sopenharmony_ci serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); 1708c2ecf20Sopenharmony_ci } else 1718c2ecf20Sopenharmony_ci init_port(device); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci device->con->write = early_serial8250_write; 1748c2ecf20Sopenharmony_ci device->con->read = early_serial8250_read; 1758c2ecf20Sopenharmony_ci return 0; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ciEARLYCON_DECLARE(uart8250, early_serial8250_setup); 1788c2ecf20Sopenharmony_ciEARLYCON_DECLARE(uart, early_serial8250_setup); 1798c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup); 1808c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); 1818c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); 1828c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_OMAP 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int __init early_omap8250_setup(struct earlycon_device *device, 1878c2ecf20Sopenharmony_ci const char *options) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct uart_port *port = &device->port; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (!(device->port.membase || device->port.iobase)) 1928c2ecf20Sopenharmony_ci return -ENODEV; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci port->regshift = 2; 1958c2ecf20Sopenharmony_ci device->con->write = early_serial8250_write; 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap2-uart", early_omap8250_setup); 2008c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap3-uart", early_omap8250_setup); 2018c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap4-uart", early_omap8250_setup); 2028c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,am654-uart", early_omap8250_setup); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci#endif 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_RT288X 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ciunsigned int au_serial_in(struct uart_port *p, int offset); 2098c2ecf20Sopenharmony_civoid au_serial_out(struct uart_port *p, int offset, int value); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic int __init early_au_setup(struct earlycon_device *dev, const char *opt) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci dev->port.serial_in = au_serial_in; 2148c2ecf20Sopenharmony_ci dev->port.serial_out = au_serial_out; 2158c2ecf20Sopenharmony_ci dev->port.iotype = UPIO_AU; 2168c2ecf20Sopenharmony_ci dev->con->write = early_serial8250_write; 2178c2ecf20Sopenharmony_ci return 0; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ciOF_EARLYCON_DECLARE(palmchip, "ralink,rt2880-uart", early_au_setup); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci#endif 222