19bef3d41SPaul Gortmaker /*
29bef3d41SPaul Gortmaker  * Early serial console for 8250/16550 devices
39bef3d41SPaul Gortmaker  *
49bef3d41SPaul Gortmaker  * (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
59bef3d41SPaul Gortmaker  *	Bjorn Helgaas <bjorn.helgaas@hp.com>
69bef3d41SPaul Gortmaker  *
79bef3d41SPaul Gortmaker  * This program is free software; you can redistribute it and/or modify
89bef3d41SPaul Gortmaker  * it under the terms of the GNU General Public License version 2 as
99bef3d41SPaul Gortmaker  * published by the Free Software Foundation.
109bef3d41SPaul Gortmaker  *
119bef3d41SPaul Gortmaker  * Based on the 8250.c serial driver, Copyright (C) 2001 Russell King,
129bef3d41SPaul Gortmaker  * and on early_printk.c by Andi Kleen.
139bef3d41SPaul Gortmaker  *
149bef3d41SPaul Gortmaker  * This is for use before the serial driver has initialized, in
159bef3d41SPaul Gortmaker  * particular, before the UARTs have been discovered and named.
169bef3d41SPaul Gortmaker  * Instead of specifying the console device as, e.g., "ttyS0",
179bef3d41SPaul Gortmaker  * we locate the device directly by its MMIO or I/O port address.
189bef3d41SPaul Gortmaker  *
199bef3d41SPaul Gortmaker  * The user can specify the device directly, e.g.,
209bef3d41SPaul Gortmaker  *	earlycon=uart8250,io,0x3f8,9600n8
219bef3d41SPaul Gortmaker  *	earlycon=uart8250,mmio,0xff5e0000,115200n8
229bef3d41SPaul Gortmaker  *	earlycon=uart8250,mmio32,0xff5e0000,115200n8
239bef3d41SPaul Gortmaker  * or
249bef3d41SPaul Gortmaker  *	console=uart8250,io,0x3f8,9600n8
259bef3d41SPaul Gortmaker  *	console=uart8250,mmio,0xff5e0000,115200n8
269bef3d41SPaul Gortmaker  *	console=uart8250,mmio32,0xff5e0000,115200n8
279bef3d41SPaul Gortmaker  */
289bef3d41SPaul Gortmaker 
299bef3d41SPaul Gortmaker #include <linux/tty.h>
309bef3d41SPaul Gortmaker #include <linux/init.h>
319bef3d41SPaul Gortmaker #include <linux/console.h>
32d05f1570SScott Wood #include <linux/of.h>
33d05f1570SScott Wood #include <linux/of_device.h>
349bef3d41SPaul Gortmaker #include <linux/serial_reg.h>
359bef3d41SPaul Gortmaker #include <linux/serial.h>
369bef3d41SPaul Gortmaker #include <linux/serial_8250.h>
379bef3d41SPaul Gortmaker #include <asm/io.h>
389bef3d41SPaul Gortmaker #include <asm/serial.h>
399bef3d41SPaul Gortmaker 
40f3fb7ef3SVineet Gupta static unsigned int __init serial8250_early_in(struct uart_port *port, int offset)
419bef3d41SPaul Gortmaker {
429bef3d41SPaul Gortmaker 	switch (port->iotype) {
439bef3d41SPaul Gortmaker 	case UPIO_MEM:
449bef3d41SPaul Gortmaker 		return readb(port->membase + offset);
45bd94c407SMasahiro Yamada 	case UPIO_MEM16:
46bd94c407SMasahiro Yamada 		return readw(port->membase + (offset << 1));
479bef3d41SPaul Gortmaker 	case UPIO_MEM32:
489bef3d41SPaul Gortmaker 		return readl(port->membase + (offset << 2));
49c627f2ceSKevin Cernekee 	case UPIO_MEM32BE:
50c627f2ceSKevin Cernekee 		return ioread32be(port->membase + (offset << 2));
519bef3d41SPaul Gortmaker 	case UPIO_PORT:
529bef3d41SPaul Gortmaker 		return inb(port->iobase + offset);
539bef3d41SPaul Gortmaker 	default:
549bef3d41SPaul Gortmaker 		return 0;
559bef3d41SPaul Gortmaker 	}
569bef3d41SPaul Gortmaker }
579bef3d41SPaul Gortmaker 
58f3fb7ef3SVineet Gupta static void __init serial8250_early_out(struct uart_port *port, int offset, int value)
599bef3d41SPaul Gortmaker {
609bef3d41SPaul Gortmaker 	switch (port->iotype) {
619bef3d41SPaul Gortmaker 	case UPIO_MEM:
629bef3d41SPaul Gortmaker 		writeb(value, port->membase + offset);
639bef3d41SPaul Gortmaker 		break;
64bd94c407SMasahiro Yamada 	case UPIO_MEM16:
65bd94c407SMasahiro Yamada 		writew(value, port->membase + (offset << 1));
66bd94c407SMasahiro Yamada 		break;
679bef3d41SPaul Gortmaker 	case UPIO_MEM32:
689bef3d41SPaul Gortmaker 		writel(value, port->membase + (offset << 2));
699bef3d41SPaul Gortmaker 		break;
70c627f2ceSKevin Cernekee 	case UPIO_MEM32BE:
71c627f2ceSKevin Cernekee 		iowrite32be(value, port->membase + (offset << 2));
72c627f2ceSKevin Cernekee 		break;
739bef3d41SPaul Gortmaker 	case UPIO_PORT:
749bef3d41SPaul Gortmaker 		outb(value, port->iobase + offset);
759bef3d41SPaul Gortmaker 		break;
769bef3d41SPaul Gortmaker 	}
779bef3d41SPaul Gortmaker }
789bef3d41SPaul Gortmaker 
799bef3d41SPaul Gortmaker #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
809bef3d41SPaul Gortmaker 
81004e2ed5SMasahiro Yamada static void __init serial_putc(struct uart_port *port, int c)
829bef3d41SPaul Gortmaker {
839bef3d41SPaul Gortmaker 	unsigned int status;
849bef3d41SPaul Gortmaker 
85004e2ed5SMasahiro Yamada 	serial8250_early_out(port, UART_TX, c);
86004e2ed5SMasahiro Yamada 
879bef3d41SPaul Gortmaker 	for (;;) {
88ed71871bSNoam Camus 		status = serial8250_early_in(port, UART_LSR);
899bef3d41SPaul Gortmaker 		if ((status & BOTH_EMPTY) == BOTH_EMPTY)
90004e2ed5SMasahiro Yamada 			break;
919bef3d41SPaul Gortmaker 		cpu_relax();
929bef3d41SPaul Gortmaker 	}
939bef3d41SPaul Gortmaker }
949bef3d41SPaul Gortmaker 
959bef3d41SPaul Gortmaker static void __init early_serial8250_write(struct console *console,
969bef3d41SPaul Gortmaker 					const char *s, unsigned int count)
979bef3d41SPaul Gortmaker {
98d0d654ceSPeter Hurley 	struct earlycon_device *device = console->data;
99d0d654ceSPeter Hurley 	struct uart_port *port = &device->port;
1009bef3d41SPaul Gortmaker 
1019bef3d41SPaul Gortmaker 	uart_console_write(port, s, count, serial_putc);
1029bef3d41SPaul Gortmaker }
1039bef3d41SPaul Gortmaker 
104d2fd6810SRob Herring static void __init init_port(struct earlycon_device *device)
1059bef3d41SPaul Gortmaker {
1069bef3d41SPaul Gortmaker 	struct uart_port *port = &device->port;
1079bef3d41SPaul Gortmaker 	unsigned int divisor;
1089bef3d41SPaul Gortmaker 	unsigned char c;
109a4c639b0SRob Herring 	unsigned int ier;
1109bef3d41SPaul Gortmaker 
111ed71871bSNoam Camus 	serial8250_early_out(port, UART_LCR, 0x3);	/* 8n1 */
112a4c639b0SRob Herring 	ier = serial8250_early_in(port, UART_IER);
113a4c639b0SRob Herring 	serial8250_early_out(port, UART_IER, ier & UART_IER_UUE); /* no interrupt */
114ed71871bSNoam Camus 	serial8250_early_out(port, UART_FCR, 0);	/* no fifo */
115ed71871bSNoam Camus 	serial8250_early_out(port, UART_MCR, 0x3);	/* DTR + RTS */
1169bef3d41SPaul Gortmaker 
117b15d5380SAlexey Brodkin 	divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * device->baud);
118ed71871bSNoam Camus 	c = serial8250_early_in(port, UART_LCR);
119ed71871bSNoam Camus 	serial8250_early_out(port, UART_LCR, c | UART_LCR_DLAB);
120ed71871bSNoam Camus 	serial8250_early_out(port, UART_DLL, divisor & 0xff);
121ed71871bSNoam Camus 	serial8250_early_out(port, UART_DLM, (divisor >> 8) & 0xff);
122ed71871bSNoam Camus 	serial8250_early_out(port, UART_LCR, c & ~UART_LCR_DLAB);
1239bef3d41SPaul Gortmaker }
1249bef3d41SPaul Gortmaker 
1251c5841e8SEddie Huang int __init early_serial8250_setup(struct earlycon_device *device,
126d2fd6810SRob Herring 					 const char *options)
1279bef3d41SPaul Gortmaker {
128d2fd6810SRob Herring 	if (!(device->port.membase || device->port.iobase))
129cd385e9aSPeter Hurley 		return -ENODEV;
1309bef3d41SPaul Gortmaker 
13160efcf04SRob Herring 	if (!device->baud) {
1320e3e143eSPeter Hurley 		struct uart_port *port = &device->port;
1330e3e143eSPeter Hurley 		unsigned int ier;
1340e3e143eSPeter Hurley 
1350e3e143eSPeter Hurley 		/* assume the device was initialized, only mask interrupts */
1360e3e143eSPeter Hurley 		ier = serial8250_early_in(port, UART_IER);
1370e3e143eSPeter Hurley 		serial8250_early_out(port, UART_IER, ier & UART_IER_UUE);
1380e3e143eSPeter Hurley 	} else
1399bef3d41SPaul Gortmaker 		init_port(device);
140d2fd6810SRob Herring 
141d2fd6810SRob Herring 	device->con->write = early_serial8250_write;
1429bef3d41SPaul Gortmaker 	return 0;
1439bef3d41SPaul Gortmaker }
144d2fd6810SRob Herring EARLYCON_DECLARE(uart8250, early_serial8250_setup);
145d2fd6810SRob Herring EARLYCON_DECLARE(uart, early_serial8250_setup);
146d05f1570SScott Wood OF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup);
147d05f1570SScott Wood OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup);
148