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