11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Early serial console for 8250/16550 devices
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
41da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
51da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Bjorn Helgaas <bjorn.helgaas@hp.com>
61da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
71da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * This program is free software; you can redistribute it and/or modify
81da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * it under the terms of the GNU General Public License version 2 as
91da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * published by the Free Software Foundation.
101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Based on the 8250.c serial driver, Copyright (C) 2001 Russell King,
121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * and on early_printk.c by Andi Kleen.
131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * This is for use before the serial driver has initialized, in
151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * particular, before the UARTs have been discovered and named.
161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Instead of specifying the console device as, e.g., "ttyS0",
171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * we locate the device directly by its MMIO or I/O port address.
181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * The user can specify the device directly, e.g.,
2018a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu *	earlycon=uart8250,io,0x3f8,9600n8
2118a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu *	earlycon=uart8250,mmio,0xff5e0000,115200n8
221917ac76e023339c73844bec775375b147f57ac7Samium Gromoff *	earlycon=uart8250,mmio32,0xff5e0000,115200n8
2318a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu * or
2418a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu *	console=uart8250,io,0x3f8,9600n8
2518a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu *	console=uart8250,mmio,0xff5e0000,115200n8
261917ac76e023339c73844bec775375b147f57ac7Samium Gromoff *	console=uart8250,mmio32,0xff5e0000,115200n8
271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/tty.h>
301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/init.h>
311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/console.h>
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/serial_core.h>
331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/serial_reg.h>
341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/serial.h>
3518a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu#include <linux/serial_8250.h>
361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/io.h>
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/serial.h>
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
39d2fd6810a823bcde1ee232668f5689837aafa772Rob Herringstatic struct earlycon_device *early_device;
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
41ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camusunsigned int __weak __init serial8250_early_in(struct uart_port *port, int offset)
421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
431917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	switch (port->iotype) {
441917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_MEM:
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return readb(port->membase + offset);
461917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_MEM32:
471917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		return readl(port->membase + (offset << 2));
481917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_PORT:
491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return inb(port->iobase + offset);
501917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	default:
511917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		return 0;
521917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	}
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
55ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camusvoid __weak __init serial8250_early_out(struct uart_port *port, int offset, int value)
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
571917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	switch (port->iotype) {
581917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_MEM:
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		writeb(value, port->membase + offset);
601917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		break;
611917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_MEM32:
621917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		writel(value, port->membase + (offset << 2));
631917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		break;
641917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	case UPIO_PORT:
651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		outb(value, port->iobase + offset);
661917ac76e023339c73844bec775375b147f57ac7Samium Gromoff		break;
671917ac76e023339c73844bec775375b147f57ac7Samium Gromoff	}
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void __init wait_for_xmitr(struct uart_port *port)
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned int status;
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (;;) {
77ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus		status = serial8250_early_in(port, UART_LSR);
781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if ((status & BOTH_EMPTY) == BOTH_EMPTY)
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return;
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		cpu_relax();
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
84bf2cdef30667d0d3d09c6934c95d1fb87c43345aYinghai Lustatic void __init serial_putc(struct uart_port *port, int c)
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wait_for_xmitr(port);
87ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_TX, c);
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
90ce2e204f0c55567baa0323df8d327989d7113c66Alan Coxstatic void __init early_serial8250_write(struct console *console,
91ce2e204f0c55567baa0323df8d327989d7113c66Alan Cox					const char *s, unsigned int count)
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
93d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	struct uart_port *port = &early_device->port;
941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned int ier;
951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Save the IER and disable interrupts */
97ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	ier = serial8250_early_in(port, UART_IER);
98ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_IER, 0);
991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
100bf2cdef30667d0d3d09c6934c95d1fb87c43345aYinghai Lu	uart_console_write(port, s, count, serial_putc);
1011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Wait for transmitter to become empty and restore the IER */
1031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wait_for_xmitr(port);
104ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_IER, ier);
1051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned int __init probe_baud(struct uart_port *port)
1081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned char lcr, dll, dlm;
1101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned int quot;
1111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
112ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	lcr = serial8250_early_in(port, UART_LCR);
113ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_LCR, lcr | UART_LCR_DLAB);
114ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	dll = serial8250_early_in(port, UART_DLL);
115ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	dlm = serial8250_early_in(port, UART_DLM);
116ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_LCR, lcr);
1171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	quot = (dlm << 8) | dll;
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return (port->uartclk / 16) / quot;
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
122d2fd6810a823bcde1ee232668f5689837aafa772Rob Herringstatic void __init init_port(struct earlycon_device *device)
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct uart_port *port = &device->port;
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned int divisor;
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned char c;
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
128ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_LCR, 0x3);	/* 8n1 */
129ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_IER, 0);	/* no interrupt */
130ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_FCR, 0);	/* no fifo */
131ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_MCR, 0x3);	/* DTR + RTS */
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
133b15d5380e471f9ce27180b14d5080abc2e2f30ecAlexey Brodkin	divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * device->baud);
134ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	c = serial8250_early_in(port, UART_LCR);
135ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_LCR, c | UART_LCR_DLAB);
136ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_DLL, divisor & 0xff);
137ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_DLM, (divisor >> 8) & 0xff);
138ed71871bed7198ca4aa6a79b7a93b73ad6408e98Noam Camus	serial8250_early_out(port, UART_LCR, c & ~UART_LCR_DLAB);
1391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
141d2fd6810a823bcde1ee232668f5689837aafa772Rob Herringstatic int __init early_serial8250_setup(struct earlycon_device *device,
142d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring					 const char *options)
1431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
144d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	if (!(device->port.membase || device->port.iobase))
1451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return 0;
1461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
14760efcf0414be5876d81276e3c1fd12680ba2ce71Rob Herring	if (!device->baud) {
148d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring		device->baud = probe_baud(&device->port);
14960efcf0414be5876d81276e3c1fd12680ba2ce71Rob Herring		snprintf(device->options, sizeof(device->options), "%u",
15060efcf0414be5876d81276e3c1fd12680ba2ce71Rob Herring			 device->baud);
15160efcf0414be5876d81276e3c1fd12680ba2ce71Rob Herring	}
1521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	init_port(device);
15418a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu
155d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	early_device = device;
156d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	device->con->write = early_serial8250_write;
15718a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	return 0;
1581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
159d2fd6810a823bcde1ee232668f5689837aafa772Rob HerringEARLYCON_DECLARE(uart8250, early_serial8250_setup);
160d2fd6810a823bcde1ee232668f5689837aafa772Rob HerringEARLYCON_DECLARE(uart, early_serial8250_setup);
1611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
162fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herringint __init setup_early_serial8250_console(char *cmdline)
163fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring{
164fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring	char match[] = "uart8250";
165fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring
166fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring	if (cmdline && cmdline[4] == ',')
167fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring		match[4] = '\0';
168fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring
169fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring	return setup_earlycon(cmdline, match, early_serial8250_setup);
170fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring}
171fe1cf8af918af3ff0dd58ce92e5a5da117cb1d92Rob Herring
172b6b1d87785712474d0ed80689c17107d616a1171Daniel Ritzint serial8250_find_port_for_earlycon(void)
1731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
174d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	struct earlycon_device *device = early_device;
175d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	struct uart_port *port = device ? &device->port : NULL;
17618a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	int line;
17718a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	int ret;
1781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
179d2fd6810a823bcde1ee232668f5689837aafa772Rob Herring	if (!port || (!port->membase && !port->iobase))
18018a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu		return -ENODEV;
1811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
18218a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	line = serial8250_find_port(port);
1831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (line < 0)
18418a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu		return -ENODEV;
1851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
18618a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	ret = update_console_cmdline("uart", 8250,
18718a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu			     "ttyS", line, device->options);
18818a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	if (ret < 0)
18918a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu		ret = update_console_cmdline("uart", 0,
19018a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu				     "ttyS", line, device->options);
1911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
19218a8bd949d6adb311ea816125ff65050df1f3f6eYinghai Lu	return ret;
1931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
194