162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Serial port routines for use during early boot reporting. This code is
462306a36Sopenharmony_ci * included from both the compressed kernel and the regular kernel.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include "boot.h"
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define DEFAULT_SERIAL_PORT 0x3f8 /* ttyS0 */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define DLAB		0x80
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define TXR             0       /*  Transmit register (WRITE) */
1362306a36Sopenharmony_ci#define RXR             0       /*  Receive register  (READ)  */
1462306a36Sopenharmony_ci#define IER             1       /*  Interrupt Enable          */
1562306a36Sopenharmony_ci#define IIR             2       /*  Interrupt ID              */
1662306a36Sopenharmony_ci#define FCR             2       /*  FIFO control              */
1762306a36Sopenharmony_ci#define LCR             3       /*  Line control              */
1862306a36Sopenharmony_ci#define MCR             4       /*  Modem control             */
1962306a36Sopenharmony_ci#define LSR             5       /*  Line Status               */
2062306a36Sopenharmony_ci#define MSR             6       /*  Modem Status              */
2162306a36Sopenharmony_ci#define DLL             0       /*  Divisor Latch Low         */
2262306a36Sopenharmony_ci#define DLH             1       /*  Divisor latch High        */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define DEFAULT_BAUD 9600
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void early_serial_init(int port, int baud)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	unsigned char c;
2962306a36Sopenharmony_ci	unsigned divisor;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	outb(0x3, port + LCR);	/* 8n1 */
3262306a36Sopenharmony_ci	outb(0, port + IER);	/* no interrupt */
3362306a36Sopenharmony_ci	outb(0, port + FCR);	/* no fifo */
3462306a36Sopenharmony_ci	outb(0x3, port + MCR);	/* DTR + RTS */
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	divisor	= 115200 / baud;
3762306a36Sopenharmony_ci	c = inb(port + LCR);
3862306a36Sopenharmony_ci	outb(c | DLAB, port + LCR);
3962306a36Sopenharmony_ci	outb(divisor & 0xff, port + DLL);
4062306a36Sopenharmony_ci	outb((divisor >> 8) & 0xff, port + DLH);
4162306a36Sopenharmony_ci	outb(c & ~DLAB, port + LCR);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	early_serial_base = port;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void parse_earlyprintk(void)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	int baud = DEFAULT_BAUD;
4962306a36Sopenharmony_ci	char arg[32];
5062306a36Sopenharmony_ci	int pos = 0;
5162306a36Sopenharmony_ci	int port = 0;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (cmdline_find_option("earlyprintk", arg, sizeof(arg)) > 0) {
5462306a36Sopenharmony_ci		char *e;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci		if (!strncmp(arg, "serial", 6)) {
5762306a36Sopenharmony_ci			port = DEFAULT_SERIAL_PORT;
5862306a36Sopenharmony_ci			pos += 6;
5962306a36Sopenharmony_ci		}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		if (arg[pos] == ',')
6262306a36Sopenharmony_ci			pos++;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		/*
6562306a36Sopenharmony_ci		 * make sure we have
6662306a36Sopenharmony_ci		 *	"serial,0x3f8,115200"
6762306a36Sopenharmony_ci		 *	"serial,ttyS0,115200"
6862306a36Sopenharmony_ci		 *	"ttyS0,115200"
6962306a36Sopenharmony_ci		 */
7062306a36Sopenharmony_ci		if (pos == 7 && !strncmp(arg + pos, "0x", 2)) {
7162306a36Sopenharmony_ci			port = simple_strtoull(arg + pos, &e, 16);
7262306a36Sopenharmony_ci			if (port == 0 || arg + pos == e)
7362306a36Sopenharmony_ci				port = DEFAULT_SERIAL_PORT;
7462306a36Sopenharmony_ci			else
7562306a36Sopenharmony_ci				pos = e - arg;
7662306a36Sopenharmony_ci		} else if (!strncmp(arg + pos, "ttyS", 4)) {
7762306a36Sopenharmony_ci			static const int bases[] = { 0x3f8, 0x2f8 };
7862306a36Sopenharmony_ci			int idx = 0;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci			/* += strlen("ttyS"); */
8162306a36Sopenharmony_ci			pos += 4;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci			if (arg[pos++] == '1')
8462306a36Sopenharmony_ci				idx = 1;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci			port = bases[idx];
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		if (arg[pos] == ',')
9062306a36Sopenharmony_ci			pos++;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		baud = simple_strtoull(arg + pos, &e, 0);
9362306a36Sopenharmony_ci		if (baud == 0 || arg + pos == e)
9462306a36Sopenharmony_ci			baud = DEFAULT_BAUD;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (port)
9862306a36Sopenharmony_ci		early_serial_init(port, baud);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci#define BASE_BAUD (1843200/16)
10262306a36Sopenharmony_cistatic unsigned int probe_baud(int port)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	unsigned char lcr, dll, dlh;
10562306a36Sopenharmony_ci	unsigned int quot;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	lcr = inb(port + LCR);
10862306a36Sopenharmony_ci	outb(lcr | DLAB, port + LCR);
10962306a36Sopenharmony_ci	dll = inb(port + DLL);
11062306a36Sopenharmony_ci	dlh = inb(port + DLH);
11162306a36Sopenharmony_ci	outb(lcr, port + LCR);
11262306a36Sopenharmony_ci	quot = (dlh << 8) | dll;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return BASE_BAUD / quot;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic void parse_console_uart8250(void)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	char optstr[64], *options;
12062306a36Sopenharmony_ci	int baud = DEFAULT_BAUD;
12162306a36Sopenharmony_ci	int port = 0;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/*
12462306a36Sopenharmony_ci	 * console=uart8250,io,0x3f8,115200n8
12562306a36Sopenharmony_ci	 * need to make sure it is last one console !
12662306a36Sopenharmony_ci	 */
12762306a36Sopenharmony_ci	if (cmdline_find_option("console", optstr, sizeof(optstr)) <= 0)
12862306a36Sopenharmony_ci		return;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	options = optstr;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (!strncmp(options, "uart8250,io,", 12))
13362306a36Sopenharmony_ci		port = simple_strtoull(options + 12, &options, 0);
13462306a36Sopenharmony_ci	else if (!strncmp(options, "uart,io,", 8))
13562306a36Sopenharmony_ci		port = simple_strtoull(options + 8, &options, 0);
13662306a36Sopenharmony_ci	else
13762306a36Sopenharmony_ci		return;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if (options && (options[0] == ','))
14062306a36Sopenharmony_ci		baud = simple_strtoull(options + 1, &options, 0);
14162306a36Sopenharmony_ci	else
14262306a36Sopenharmony_ci		baud = probe_baud(port);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (port)
14562306a36Sopenharmony_ci		early_serial_init(port, baud);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_civoid console_init(void)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	parse_earlyprintk();
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!early_serial_base)
15362306a36Sopenharmony_ci		parse_console_uart8250();
15462306a36Sopenharmony_ci}
155