162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Early serial console for 8250/16550 devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) Copyright 2004 Hewlett-Packard Development Company, L.P. 662306a36Sopenharmony_ci * Bjorn Helgaas <bjorn.helgaas@hp.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Based on the 8250.c serial driver, Copyright (C) 2001 Russell King, 962306a36Sopenharmony_ci * and on early_printk.c by Andi Kleen. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * This is for use before the serial driver has initialized, in 1262306a36Sopenharmony_ci * particular, before the UARTs have been discovered and named. 1362306a36Sopenharmony_ci * Instead of specifying the console device as, e.g., "ttyS0", 1462306a36Sopenharmony_ci * we locate the device directly by its MMIO or I/O port address. 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * The user can specify the device directly, e.g., 1762306a36Sopenharmony_ci * earlycon=uart8250,io,0x3f8,9600n8 1862306a36Sopenharmony_ci * earlycon=uart8250,mmio,0xff5e0000,115200n8 1962306a36Sopenharmony_ci * earlycon=uart8250,mmio32,0xff5e0000,115200n8 2062306a36Sopenharmony_ci * or 2162306a36Sopenharmony_ci * console=uart8250,io,0x3f8,9600n8 2262306a36Sopenharmony_ci * console=uart8250,mmio,0xff5e0000,115200n8 2362306a36Sopenharmony_ci * console=uart8250,mmio32,0xff5e0000,115200n8 2462306a36Sopenharmony_ci */ 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <linux/tty.h> 2762306a36Sopenharmony_ci#include <linux/init.h> 2862306a36Sopenharmony_ci#include <linux/console.h> 2962306a36Sopenharmony_ci#include <linux/of.h> 3062306a36Sopenharmony_ci#include <linux/serial_reg.h> 3162306a36Sopenharmony_ci#include <linux/serial.h> 3262306a36Sopenharmony_ci#include <linux/serial_8250.h> 3362306a36Sopenharmony_ci#include <asm/io.h> 3462306a36Sopenharmony_ci#include <asm/serial.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic unsigned int serial8250_early_in(struct uart_port *port, int offset) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci offset <<= port->regshift; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci switch (port->iotype) { 4162306a36Sopenharmony_ci case UPIO_MEM: 4262306a36Sopenharmony_ci return readb(port->membase + offset); 4362306a36Sopenharmony_ci case UPIO_MEM16: 4462306a36Sopenharmony_ci return readw(port->membase + offset); 4562306a36Sopenharmony_ci case UPIO_MEM32: 4662306a36Sopenharmony_ci return readl(port->membase + offset); 4762306a36Sopenharmony_ci case UPIO_MEM32BE: 4862306a36Sopenharmony_ci return ioread32be(port->membase + offset); 4962306a36Sopenharmony_ci case UPIO_PORT: 5062306a36Sopenharmony_ci return inb(port->iobase + offset); 5162306a36Sopenharmony_ci default: 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void serial8250_early_out(struct uart_port *port, int offset, int value) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci offset <<= port->regshift; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci switch (port->iotype) { 6162306a36Sopenharmony_ci case UPIO_MEM: 6262306a36Sopenharmony_ci writeb(value, port->membase + offset); 6362306a36Sopenharmony_ci break; 6462306a36Sopenharmony_ci case UPIO_MEM16: 6562306a36Sopenharmony_ci writew(value, port->membase + offset); 6662306a36Sopenharmony_ci break; 6762306a36Sopenharmony_ci case UPIO_MEM32: 6862306a36Sopenharmony_ci writel(value, port->membase + offset); 6962306a36Sopenharmony_ci break; 7062306a36Sopenharmony_ci case UPIO_MEM32BE: 7162306a36Sopenharmony_ci iowrite32be(value, port->membase + offset); 7262306a36Sopenharmony_ci break; 7362306a36Sopenharmony_ci case UPIO_PORT: 7462306a36Sopenharmony_ci outb(value, port->iobase + offset); 7562306a36Sopenharmony_ci break; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void serial_putc(struct uart_port *port, unsigned char c) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci unsigned int status; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci serial8250_early_out(port, UART_TX, c); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci for (;;) { 8662306a36Sopenharmony_ci status = serial8250_early_in(port, UART_LSR); 8762306a36Sopenharmony_ci if (uart_lsr_tx_empty(status)) 8862306a36Sopenharmony_ci break; 8962306a36Sopenharmony_ci cpu_relax(); 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic void early_serial8250_write(struct console *console, 9462306a36Sopenharmony_ci const char *s, unsigned int count) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct earlycon_device *device = console->data; 9762306a36Sopenharmony_ci struct uart_port *port = &device->port; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci uart_console_write(port, s, count, serial_putc); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci#ifdef CONFIG_CONSOLE_POLL 10362306a36Sopenharmony_cistatic int early_serial8250_read(struct console *console, 10462306a36Sopenharmony_ci char *s, unsigned int count) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct earlycon_device *device = console->data; 10762306a36Sopenharmony_ci struct uart_port *port = &device->port; 10862306a36Sopenharmony_ci unsigned int status; 10962306a36Sopenharmony_ci int num_read = 0; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci while (num_read < count) { 11262306a36Sopenharmony_ci status = serial8250_early_in(port, UART_LSR); 11362306a36Sopenharmony_ci if (!(status & UART_LSR_DR)) 11462306a36Sopenharmony_ci break; 11562306a36Sopenharmony_ci s[num_read++] = serial8250_early_in(port, UART_RX); 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return num_read; 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci#else 12162306a36Sopenharmony_ci#define early_serial8250_read NULL 12262306a36Sopenharmony_ci#endif 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic void __init init_port(struct earlycon_device *device) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct uart_port *port = &device->port; 12762306a36Sopenharmony_ci unsigned int divisor; 12862306a36Sopenharmony_ci unsigned char c; 12962306a36Sopenharmony_ci unsigned int ier; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci serial8250_early_out(port, UART_LCR, UART_LCR_WLEN8); /* 8n1 */ 13262306a36Sopenharmony_ci ier = serial8250_early_in(port, UART_IER); 13362306a36Sopenharmony_ci serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); /* no interrupt */ 13462306a36Sopenharmony_ci serial8250_early_out(port, UART_FCR, 0); /* no fifo */ 13562306a36Sopenharmony_ci serial8250_early_out(port, UART_MCR, UART_MCR_DTR | UART_MCR_RTS); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (port->uartclk) { 13862306a36Sopenharmony_ci divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * device->baud); 13962306a36Sopenharmony_ci c = serial8250_early_in(port, UART_LCR); 14062306a36Sopenharmony_ci serial8250_early_out(port, UART_LCR, c | UART_LCR_DLAB); 14162306a36Sopenharmony_ci serial8250_early_out(port, UART_DLL, divisor & 0xff); 14262306a36Sopenharmony_ci serial8250_early_out(port, UART_DLM, (divisor >> 8) & 0xff); 14362306a36Sopenharmony_ci serial8250_early_out(port, UART_LCR, c & ~UART_LCR_DLAB); 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ciint __init early_serial8250_setup(struct earlycon_device *device, 14862306a36Sopenharmony_ci const char *options) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci if (!(device->port.membase || device->port.iobase)) 15162306a36Sopenharmony_ci return -ENODEV; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (!device->baud) { 15462306a36Sopenharmony_ci struct uart_port *port = &device->port; 15562306a36Sopenharmony_ci unsigned int ier; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* assume the device was initialized, only mask interrupts */ 15862306a36Sopenharmony_ci ier = serial8250_early_in(port, UART_IER); 15962306a36Sopenharmony_ci serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); 16062306a36Sopenharmony_ci } else 16162306a36Sopenharmony_ci init_port(device); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci device->con->write = early_serial8250_write; 16462306a36Sopenharmony_ci device->con->read = early_serial8250_read; 16562306a36Sopenharmony_ci return 0; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ciEARLYCON_DECLARE(uart8250, early_serial8250_setup); 16862306a36Sopenharmony_ciEARLYCON_DECLARE(uart, early_serial8250_setup); 16962306a36Sopenharmony_ciOF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup); 17062306a36Sopenharmony_ciOF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); 17162306a36Sopenharmony_ciOF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); 17262306a36Sopenharmony_ciOF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_OMAP 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int __init early_omap8250_setup(struct earlycon_device *device, 17762306a36Sopenharmony_ci const char *options) 17862306a36Sopenharmony_ci{ 17962306a36Sopenharmony_ci struct uart_port *port = &device->port; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (!(device->port.membase || device->port.iobase)) 18262306a36Sopenharmony_ci return -ENODEV; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci port->regshift = 2; 18562306a36Sopenharmony_ci device->con->write = early_serial8250_write; 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap2-uart", early_omap8250_setup); 19062306a36Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap3-uart", early_omap8250_setup); 19162306a36Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,omap4-uart", early_omap8250_setup); 19262306a36Sopenharmony_ciOF_EARLYCON_DECLARE(omap8250, "ti,am654-uart", early_omap8250_setup); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci#endif 195