xref: /rk3399_rockchip-uboot/drivers/serial/serial_pl01x.c (revision ec3fd68952662b1badb02caab9705eb93bdc4f1b)
120c9226cSAndreas Engel /*
220c9226cSAndreas Engel  * (C) Copyright 2000
320c9226cSAndreas Engel  * Rob Taylor, Flying Pig Systems. robt@flyingpig.com.
420c9226cSAndreas Engel  *
520c9226cSAndreas Engel  * (C) Copyright 2004
620c9226cSAndreas Engel  * ARM Ltd.
720c9226cSAndreas Engel  * Philippe Robin, <philippe.robin@arm.com>
820c9226cSAndreas Engel  *
920c9226cSAndreas Engel  * See file CREDITS for list of people who contributed to this
1020c9226cSAndreas Engel  * project.
1120c9226cSAndreas Engel  *
1220c9226cSAndreas Engel  * This program is free software; you can redistribute it and/or
1320c9226cSAndreas Engel  * modify it under the terms of the GNU General Public License as
1420c9226cSAndreas Engel  * published by the Free Software Foundation; either version 2 of
1520c9226cSAndreas Engel  * the License, or (at your option) any later version.
1620c9226cSAndreas Engel  *
1720c9226cSAndreas Engel  * This program is distributed in the hope that it will be useful,
1820c9226cSAndreas Engel  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1920c9226cSAndreas Engel  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2020c9226cSAndreas Engel  * GNU General Public License for more details.
2120c9226cSAndreas Engel  *
2220c9226cSAndreas Engel  * You should have received a copy of the GNU General Public License
2320c9226cSAndreas Engel  * along with this program; if not, write to the Free Software
2420c9226cSAndreas Engel  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
2520c9226cSAndreas Engel  * MA 02111-1307 USA
2620c9226cSAndreas Engel  */
2720c9226cSAndreas Engel 
2848d0192fSAndreas Engel /* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */
2920c9226cSAndreas Engel 
3020c9226cSAndreas Engel #include <common.h>
3120c9226cSAndreas Engel #include <watchdog.h>
32249d5219SMatt Waddel #include <asm/io.h>
3339f61477SMarek Vasut #include <serial.h>
3439f61477SMarek Vasut #include <linux/compiler.h>
3520c9226cSAndreas Engel #include "serial_pl01x.h"
3620c9226cSAndreas Engel 
3720c9226cSAndreas Engel /*
3820c9226cSAndreas Engel  * Integrator AP has two UARTs, we use the first one, at 38400-8-N-1
3920c9226cSAndreas Engel  * Integrator CP has two UARTs, use the first one, at 38400-8-N-1
4020c9226cSAndreas Engel  * Versatile PB has four UARTs.
4120c9226cSAndreas Engel  */
4220c9226cSAndreas Engel #define CONSOLE_PORT CONFIG_CONS_INDEX
4320c9226cSAndreas Engel static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS;
4420c9226cSAndreas Engel #define NUM_PORTS (sizeof(port)/sizeof(port[0]))
4520c9226cSAndreas Engel 
4620c9226cSAndreas Engel static void pl01x_putc (int portnum, char c);
4720c9226cSAndreas Engel static int pl01x_getc (int portnum);
4820c9226cSAndreas Engel static int pl01x_tstc (int portnum);
49249d5219SMatt Waddel unsigned int baudrate = CONFIG_BAUDRATE;
50249d5219SMatt Waddel DECLARE_GLOBAL_DATA_PTR;
5120c9226cSAndreas Engel 
5272d5e44cSRabin Vincent static struct pl01x_regs *pl01x_get_regs(int portnum)
5372d5e44cSRabin Vincent {
5472d5e44cSRabin Vincent 	return (struct pl01x_regs *) port[portnum];
5572d5e44cSRabin Vincent }
5672d5e44cSRabin Vincent 
5748d0192fSAndreas Engel #ifdef CONFIG_PL010_SERIAL
5820c9226cSAndreas Engel 
5939f61477SMarek Vasut static int pl01x_serial_init(void)
6020c9226cSAndreas Engel {
6172d5e44cSRabin Vincent 	struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
6220c9226cSAndreas Engel 	unsigned int divisor;
6320c9226cSAndreas Engel 
64249d5219SMatt Waddel 	/* First, disable everything */
6572d5e44cSRabin Vincent 	writel(0, &regs->pl010_cr);
6620c9226cSAndreas Engel 
67249d5219SMatt Waddel 	/* Set baud rate */
68249d5219SMatt Waddel 	switch (baudrate) {
6920c9226cSAndreas Engel 	case 9600:
7020c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_9600;
7120c9226cSAndreas Engel 		break;
7220c9226cSAndreas Engel 
7320c9226cSAndreas Engel 	case 19200:
7420c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_9600;
7520c9226cSAndreas Engel 		break;
7620c9226cSAndreas Engel 
7720c9226cSAndreas Engel 	case 38400:
7820c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_38400;
7920c9226cSAndreas Engel 		break;
8020c9226cSAndreas Engel 
8120c9226cSAndreas Engel 	case 57600:
8220c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_57600;
8320c9226cSAndreas Engel 		break;
8420c9226cSAndreas Engel 
8520c9226cSAndreas Engel 	case 115200:
8620c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_115200;
8720c9226cSAndreas Engel 		break;
8820c9226cSAndreas Engel 
8920c9226cSAndreas Engel 	default:
9020c9226cSAndreas Engel 		divisor = UART_PL010_BAUD_38400;
9120c9226cSAndreas Engel 	}
9220c9226cSAndreas Engel 
9372d5e44cSRabin Vincent 	writel((divisor & 0xf00) >> 8, &regs->pl010_lcrm);
9472d5e44cSRabin Vincent 	writel(divisor & 0xff, &regs->pl010_lcrl);
9520c9226cSAndreas Engel 
96249d5219SMatt Waddel 	/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
9772d5e44cSRabin Vincent 	writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN, &regs->pl010_lcrh);
9820c9226cSAndreas Engel 
99249d5219SMatt Waddel 	/* Finally, enable the UART */
10072d5e44cSRabin Vincent 	writel(UART_PL010_CR_UARTEN, &regs->pl010_cr);
10120c9226cSAndreas Engel 
10220c9226cSAndreas Engel 	return 0;
10320c9226cSAndreas Engel }
10420c9226cSAndreas Engel 
10548d0192fSAndreas Engel #endif /* CONFIG_PL010_SERIAL */
10620c9226cSAndreas Engel 
10748d0192fSAndreas Engel #ifdef CONFIG_PL011_SERIAL
10820c9226cSAndreas Engel 
10939f61477SMarek Vasut static int pl01x_serial_init(void)
11020c9226cSAndreas Engel {
11172d5e44cSRabin Vincent 	struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
11220c9226cSAndreas Engel 	unsigned int temp;
11320c9226cSAndreas Engel 	unsigned int divider;
11420c9226cSAndreas Engel 	unsigned int remainder;
11520c9226cSAndreas Engel 	unsigned int fraction;
116910f1ae3SJohn Rigby 	unsigned int lcr;
117910f1ae3SJohn Rigby 
118910f1ae3SJohn Rigby #ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT
119910f1ae3SJohn Rigby 	/* Empty RX fifo if necessary */
120910f1ae3SJohn Rigby 	if (readl(&regs->pl011_cr) & UART_PL011_CR_UARTEN) {
121910f1ae3SJohn Rigby 		while (!(readl(&regs->fr) & UART_PL01x_FR_RXFE))
122910f1ae3SJohn Rigby 			readl(&regs->dr);
123910f1ae3SJohn Rigby 	}
124910f1ae3SJohn Rigby #endif
12520c9226cSAndreas Engel 
126249d5219SMatt Waddel 	/* First, disable everything */
12772d5e44cSRabin Vincent 	writel(0, &regs->pl011_cr);
12820c9226cSAndreas Engel 
12920c9226cSAndreas Engel 	/*
130249d5219SMatt Waddel 	 * Set baud rate
131249d5219SMatt Waddel 	 *
132249d5219SMatt Waddel 	 * IBRD = UART_CLK / (16 * BAUD_RATE)
133249d5219SMatt Waddel 	 * FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE))) / (16 * BAUD_RATE))
13420c9226cSAndreas Engel 	 */
135249d5219SMatt Waddel 	temp = 16 * baudrate;
13620c9226cSAndreas Engel 	divider = CONFIG_PL011_CLOCK / temp;
13720c9226cSAndreas Engel 	remainder = CONFIG_PL011_CLOCK % temp;
138249d5219SMatt Waddel 	temp = (8 * remainder) / baudrate;
13920c9226cSAndreas Engel 	fraction = (temp >> 1) + (temp & 1);
14020c9226cSAndreas Engel 
14172d5e44cSRabin Vincent 	writel(divider, &regs->pl011_ibrd);
14272d5e44cSRabin Vincent 	writel(fraction, &regs->pl011_fbrd);
14320c9226cSAndreas Engel 
144249d5219SMatt Waddel 	/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
145910f1ae3SJohn Rigby 	lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
146910f1ae3SJohn Rigby 	writel(lcr, &regs->pl011_lcrh);
14720c9226cSAndreas Engel 
148910f1ae3SJohn Rigby #ifdef CONFIG_PL011_SERIAL_RLCR
149910f1ae3SJohn Rigby 	{
150910f1ae3SJohn Rigby 		int i;
151910f1ae3SJohn Rigby 
152910f1ae3SJohn Rigby 		/*
153910f1ae3SJohn Rigby 		 * Program receive line control register after waiting
154910f1ae3SJohn Rigby 		 * 10 bus cycles.  Delay be writing to readonly register
155910f1ae3SJohn Rigby 		 * 10 times
156910f1ae3SJohn Rigby 		 */
157910f1ae3SJohn Rigby 		for (i = 0; i < 10; i++)
158910f1ae3SJohn Rigby 			writel(lcr, &regs->fr);
159910f1ae3SJohn Rigby 
160910f1ae3SJohn Rigby 		writel(lcr, &regs->pl011_rlcr);
16184dee301SMathieu J. Poirier 		/* lcrh needs to be set again for change to be effective */
16284dee301SMathieu J. Poirier 		writel(lcr, &regs->pl011_lcrh);
163910f1ae3SJohn Rigby 	}
164910f1ae3SJohn Rigby #endif
165249d5219SMatt Waddel 	/* Finally, enable the UART */
16672d5e44cSRabin Vincent 	writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE,
16772d5e44cSRabin Vincent 	       &regs->pl011_cr);
16820c9226cSAndreas Engel 
16920c9226cSAndreas Engel 	return 0;
17020c9226cSAndreas Engel }
17120c9226cSAndreas Engel 
17248d0192fSAndreas Engel #endif /* CONFIG_PL011_SERIAL */
17320c9226cSAndreas Engel 
17439f61477SMarek Vasut static void pl01x_serial_putc(const char c)
17520c9226cSAndreas Engel {
17620c9226cSAndreas Engel 	if (c == '\n')
17720c9226cSAndreas Engel 		pl01x_putc (CONSOLE_PORT, '\r');
17820c9226cSAndreas Engel 
17920c9226cSAndreas Engel 	pl01x_putc (CONSOLE_PORT, c);
18020c9226cSAndreas Engel }
18120c9226cSAndreas Engel 
18239f61477SMarek Vasut static int pl01x_serial_getc(void)
18320c9226cSAndreas Engel {
18420c9226cSAndreas Engel 	return pl01x_getc (CONSOLE_PORT);
18520c9226cSAndreas Engel }
18620c9226cSAndreas Engel 
18739f61477SMarek Vasut static int pl01x_serial_tstc(void)
18820c9226cSAndreas Engel {
18920c9226cSAndreas Engel 	return pl01x_tstc (CONSOLE_PORT);
19020c9226cSAndreas Engel }
19120c9226cSAndreas Engel 
19239f61477SMarek Vasut static void pl01x_serial_setbrg(void)
19320c9226cSAndreas Engel {
19496baa4c3SLinus Walleij 	struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
19596baa4c3SLinus Walleij 
196249d5219SMatt Waddel 	baudrate = gd->baudrate;
19796baa4c3SLinus Walleij 	/*
19896baa4c3SLinus Walleij 	 * Flush FIFO and wait for non-busy before changing baudrate to avoid
19996baa4c3SLinus Walleij 	 * crap in console
20096baa4c3SLinus Walleij 	 */
20196baa4c3SLinus Walleij 	while (!(readl(&regs->fr) & UART_PL01x_FR_TXFE))
20296baa4c3SLinus Walleij 		WATCHDOG_RESET();
20396baa4c3SLinus Walleij 	while (readl(&regs->fr) & UART_PL01x_FR_BUSY)
20496baa4c3SLinus Walleij 		WATCHDOG_RESET();
205249d5219SMatt Waddel 	serial_init();
20620c9226cSAndreas Engel }
20720c9226cSAndreas Engel 
20820c9226cSAndreas Engel static void pl01x_putc (int portnum, char c)
20920c9226cSAndreas Engel {
21072d5e44cSRabin Vincent 	struct pl01x_regs *regs = pl01x_get_regs(portnum);
21172d5e44cSRabin Vincent 
21220c9226cSAndreas Engel 	/* Wait until there is space in the FIFO */
21372d5e44cSRabin Vincent 	while (readl(&regs->fr) & UART_PL01x_FR_TXFF)
21420c9226cSAndreas Engel 		WATCHDOG_RESET();
21520c9226cSAndreas Engel 
21620c9226cSAndreas Engel 	/* Send the character */
21772d5e44cSRabin Vincent 	writel(c, &regs->dr);
21820c9226cSAndreas Engel }
21920c9226cSAndreas Engel 
22020c9226cSAndreas Engel static int pl01x_getc (int portnum)
22120c9226cSAndreas Engel {
22272d5e44cSRabin Vincent 	struct pl01x_regs *regs = pl01x_get_regs(portnum);
22320c9226cSAndreas Engel 	unsigned int data;
22420c9226cSAndreas Engel 
22520c9226cSAndreas Engel 	/* Wait until there is data in the FIFO */
22672d5e44cSRabin Vincent 	while (readl(&regs->fr) & UART_PL01x_FR_RXFE)
22720c9226cSAndreas Engel 		WATCHDOG_RESET();
22820c9226cSAndreas Engel 
22972d5e44cSRabin Vincent 	data = readl(&regs->dr);
23020c9226cSAndreas Engel 
23120c9226cSAndreas Engel 	/* Check for an error flag */
23220c9226cSAndreas Engel 	if (data & 0xFFFFFF00) {
23320c9226cSAndreas Engel 		/* Clear the error */
23472d5e44cSRabin Vincent 		writel(0xFFFFFFFF, &regs->ecr);
23520c9226cSAndreas Engel 		return -1;
23620c9226cSAndreas Engel 	}
23720c9226cSAndreas Engel 
23820c9226cSAndreas Engel 	return (int) data;
23920c9226cSAndreas Engel }
24020c9226cSAndreas Engel 
24120c9226cSAndreas Engel static int pl01x_tstc (int portnum)
24220c9226cSAndreas Engel {
24372d5e44cSRabin Vincent 	struct pl01x_regs *regs = pl01x_get_regs(portnum);
24472d5e44cSRabin Vincent 
24520c9226cSAndreas Engel 	WATCHDOG_RESET();
24672d5e44cSRabin Vincent 	return !(readl(&regs->fr) & UART_PL01x_FR_RXFE);
24720c9226cSAndreas Engel }
24839f61477SMarek Vasut 
24939f61477SMarek Vasut static struct serial_device pl01x_serial_drv = {
25039f61477SMarek Vasut 	.name	= "pl01x_serial",
25139f61477SMarek Vasut 	.start	= pl01x_serial_init,
25239f61477SMarek Vasut 	.stop	= NULL,
25339f61477SMarek Vasut 	.setbrg	= pl01x_serial_setbrg,
25439f61477SMarek Vasut 	.putc	= pl01x_serial_putc,
255*ec3fd689SMarek Vasut 	.puts	= default_serial_puts,
25639f61477SMarek Vasut 	.getc	= pl01x_serial_getc,
25739f61477SMarek Vasut 	.tstc	= pl01x_serial_tstc,
25839f61477SMarek Vasut };
25939f61477SMarek Vasut 
26039f61477SMarek Vasut void pl01x_serial_initialize(void)
26139f61477SMarek Vasut {
26239f61477SMarek Vasut 	serial_register(&pl01x_serial_drv);
26339f61477SMarek Vasut }
26439f61477SMarek Vasut 
26539f61477SMarek Vasut __weak struct serial_device *default_serial_console(void)
26639f61477SMarek Vasut {
26739f61477SMarek Vasut 	return &pl01x_serial_drv;
26839f61477SMarek Vasut }
269