2004-03-14 23:06:13 +08:00
|
|
|
/*
|
|
|
|
* (C) Copyright 2000
|
|
|
|
* Rob Taylor, Flying Pig Systems. robt@flyingpig.com.
|
|
|
|
*
|
|
|
|
* (C) Copyright 2004
|
|
|
|
* ARM Ltd.
|
|
|
|
* Philippe Robin, <philippe.robin@arm.com>
|
|
|
|
*
|
2013-07-08 15:37:19 +08:00
|
|
|
* SPDX-License-Identifier: GPL-2.0+
|
2004-03-14 23:06:13 +08:00
|
|
|
*/
|
|
|
|
|
2008-09-08 20:30:53 +08:00
|
|
|
/* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */
|
2004-03-14 23:06:13 +08:00
|
|
|
|
|
|
|
#include <common.h>
|
2008-06-03 04:42:19 +08:00
|
|
|
#include <watchdog.h>
|
2010-10-08 05:48:46 +08:00
|
|
|
#include <asm/io.h>
|
2012-09-15 04:38:46 +08:00
|
|
|
#include <serial.h>
|
|
|
|
#include <linux/compiler.h>
|
2008-09-08 16:17:31 +08:00
|
|
|
#include "serial_pl01x.h"
|
2004-03-14 23:06:13 +08:00
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
/*
|
|
|
|
* Integrator AP has two UARTs, we use the first one, at 38400-8-N-1
|
|
|
|
* Integrator CP has two UARTs, use the first one, at 38400-8-N-1
|
|
|
|
* Versatile PB has four UARTs.
|
|
|
|
*/
|
2004-03-14 23:06:13 +08:00
|
|
|
#define CONSOLE_PORT CONFIG_CONS_INDEX
|
2004-08-03 07:22:59 +08:00
|
|
|
static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS;
|
|
|
|
#define NUM_PORTS (sizeof(port)/sizeof(port[0]))
|
2004-03-14 23:06:13 +08:00
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
static void pl01x_putc (int portnum, char c);
|
|
|
|
static int pl01x_getc (int portnum);
|
|
|
|
static int pl01x_tstc (int portnum);
|
2010-10-08 05:48:46 +08:00
|
|
|
unsigned int baudrate = CONFIG_BAUDRATE;
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
2004-03-14 23:06:13 +08:00
|
|
|
|
2010-05-05 11:53:07 +08:00
|
|
|
static struct pl01x_regs *pl01x_get_regs(int portnum)
|
|
|
|
{
|
|
|
|
return (struct pl01x_regs *) port[portnum];
|
|
|
|
}
|
|
|
|
|
2008-09-08 20:30:53 +08:00
|
|
|
#ifdef CONFIG_PL010_SERIAL
|
2004-03-14 23:06:13 +08:00
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static int pl01x_serial_init(void)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2010-05-05 11:53:07 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
|
2004-03-15 06:25:36 +08:00
|
|
|
unsigned int divisor;
|
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* First, disable everything */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(0, ®s->pl010_cr);
|
2004-03-15 06:25:36 +08:00
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* Set baud rate */
|
|
|
|
switch (baudrate) {
|
2004-03-15 06:25:36 +08:00
|
|
|
case 9600:
|
|
|
|
divisor = UART_PL010_BAUD_9600;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 19200:
|
|
|
|
divisor = UART_PL010_BAUD_9600;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 38400:
|
|
|
|
divisor = UART_PL010_BAUD_38400;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 57600:
|
|
|
|
divisor = UART_PL010_BAUD_57600;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 115200:
|
|
|
|
divisor = UART_PL010_BAUD_115200;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
divisor = UART_PL010_BAUD_38400;
|
|
|
|
}
|
|
|
|
|
2010-05-05 11:53:07 +08:00
|
|
|
writel((divisor & 0xf00) >> 8, ®s->pl010_lcrm);
|
|
|
|
writel(divisor & 0xff, ®s->pl010_lcrl);
|
2004-03-15 06:25:36 +08:00
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN, ®s->pl010_lcrh);
|
2004-03-15 06:25:36 +08:00
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* Finally, enable the UART */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(UART_PL010_CR_UARTEN, ®s->pl010_cr);
|
2004-03-15 06:25:36 +08:00
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
return 0;
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2008-09-08 20:30:53 +08:00
|
|
|
#endif /* CONFIG_PL010_SERIAL */
|
2008-09-08 16:17:31 +08:00
|
|
|
|
2008-09-08 20:30:53 +08:00
|
|
|
#ifdef CONFIG_PL011_SERIAL
|
2008-09-08 16:17:31 +08:00
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static int pl01x_serial_init(void)
|
2008-09-08 16:17:31 +08:00
|
|
|
{
|
2010-05-05 11:53:07 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
|
2008-09-08 16:17:31 +08:00
|
|
|
unsigned int temp;
|
|
|
|
unsigned int divider;
|
|
|
|
unsigned int remainder;
|
|
|
|
unsigned int fraction;
|
2011-04-19 18:42:39 +08:00
|
|
|
unsigned int lcr;
|
|
|
|
|
|
|
|
#ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT
|
|
|
|
/* Empty RX fifo if necessary */
|
|
|
|
if (readl(®s->pl011_cr) & UART_PL011_CR_UARTEN) {
|
|
|
|
while (!(readl(®s->fr) & UART_PL01x_FR_RXFE))
|
|
|
|
readl(®s->dr);
|
|
|
|
}
|
|
|
|
#endif
|
2008-09-08 16:17:31 +08:00
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* First, disable everything */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(0, ®s->pl011_cr);
|
2008-09-08 16:17:31 +08:00
|
|
|
|
|
|
|
/*
|
2010-10-08 05:48:46 +08:00
|
|
|
* Set baud rate
|
|
|
|
*
|
|
|
|
* IBRD = UART_CLK / (16 * BAUD_RATE)
|
|
|
|
* FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE))) / (16 * BAUD_RATE))
|
2008-09-08 16:17:31 +08:00
|
|
|
*/
|
2010-10-08 05:48:46 +08:00
|
|
|
temp = 16 * baudrate;
|
2008-09-08 16:17:31 +08:00
|
|
|
divider = CONFIG_PL011_CLOCK / temp;
|
|
|
|
remainder = CONFIG_PL011_CLOCK % temp;
|
2010-10-08 05:48:46 +08:00
|
|
|
temp = (8 * remainder) / baudrate;
|
2008-09-08 16:17:31 +08:00
|
|
|
fraction = (temp >> 1) + (temp & 1);
|
|
|
|
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(divider, ®s->pl011_ibrd);
|
|
|
|
writel(fraction, ®s->pl011_fbrd);
|
2008-09-08 16:17:31 +08:00
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
|
2011-04-19 18:42:39 +08:00
|
|
|
lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
|
|
|
|
writel(lcr, ®s->pl011_lcrh);
|
|
|
|
|
|
|
|
#ifdef CONFIG_PL011_SERIAL_RLCR
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Program receive line control register after waiting
|
|
|
|
* 10 bus cycles. Delay be writing to readonly register
|
|
|
|
* 10 times
|
|
|
|
*/
|
|
|
|
for (i = 0; i < 10; i++)
|
|
|
|
writel(lcr, ®s->fr);
|
|
|
|
|
|
|
|
writel(lcr, ®s->pl011_rlcr);
|
2012-08-03 19:05:12 +08:00
|
|
|
/* lcrh needs to be set again for change to be effective */
|
|
|
|
writel(lcr, ®s->pl011_lcrh);
|
2011-04-19 18:42:39 +08:00
|
|
|
}
|
|
|
|
#endif
|
2010-10-08 05:48:46 +08:00
|
|
|
/* Finally, enable the UART */
|
2012-12-03 01:09:26 +08:00
|
|
|
writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE |
|
|
|
|
UART_PL011_CR_RTS, ®s->pl011_cr);
|
2008-09-08 16:17:31 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-08 20:30:53 +08:00
|
|
|
#endif /* CONFIG_PL011_SERIAL */
|
2008-09-08 16:17:31 +08:00
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static void pl01x_serial_putc(const char c)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
|
|
|
if (c == '\n')
|
2008-09-08 16:17:31 +08:00
|
|
|
pl01x_putc (CONSOLE_PORT, '\r');
|
2004-03-14 23:06:13 +08:00
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
pl01x_putc (CONSOLE_PORT, c);
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static int pl01x_serial_getc(void)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2008-09-08 16:17:31 +08:00
|
|
|
return pl01x_getc (CONSOLE_PORT);
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static int pl01x_serial_tstc(void)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2008-09-08 16:17:31 +08:00
|
|
|
return pl01x_tstc (CONSOLE_PORT);
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2012-09-15 04:38:46 +08:00
|
|
|
static void pl01x_serial_setbrg(void)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2011-10-02 19:52:52 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
|
|
|
|
|
2010-10-08 05:48:46 +08:00
|
|
|
baudrate = gd->baudrate;
|
2011-10-02 19:52:52 +08:00
|
|
|
/*
|
|
|
|
* Flush FIFO and wait for non-busy before changing baudrate to avoid
|
|
|
|
* crap in console
|
|
|
|
*/
|
|
|
|
while (!(readl(®s->fr) & UART_PL01x_FR_TXFE))
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
while (readl(®s->fr) & UART_PL01x_FR_BUSY)
|
|
|
|
WATCHDOG_RESET();
|
2010-10-08 05:48:46 +08:00
|
|
|
serial_init();
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
static void pl01x_putc (int portnum, char c)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2010-05-05 11:53:07 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(portnum);
|
|
|
|
|
2004-03-15 06:25:36 +08:00
|
|
|
/* Wait until there is space in the FIFO */
|
2010-05-05 11:53:07 +08:00
|
|
|
while (readl(®s->fr) & UART_PL01x_FR_TXFF)
|
2008-06-03 04:42:19 +08:00
|
|
|
WATCHDOG_RESET();
|
2004-03-15 06:25:36 +08:00
|
|
|
|
|
|
|
/* Send the character */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(c, ®s->dr);
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
static int pl01x_getc (int portnum)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2010-05-05 11:53:07 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(portnum);
|
2004-03-15 06:25:36 +08:00
|
|
|
unsigned int data;
|
|
|
|
|
|
|
|
/* Wait until there is data in the FIFO */
|
2010-05-05 11:53:07 +08:00
|
|
|
while (readl(®s->fr) & UART_PL01x_FR_RXFE)
|
2008-06-03 04:42:19 +08:00
|
|
|
WATCHDOG_RESET();
|
2004-03-15 06:25:36 +08:00
|
|
|
|
2010-05-05 11:53:07 +08:00
|
|
|
data = readl(®s->dr);
|
2004-03-15 06:25:36 +08:00
|
|
|
|
|
|
|
/* Check for an error flag */
|
|
|
|
if (data & 0xFFFFFF00) {
|
|
|
|
/* Clear the error */
|
2010-05-05 11:53:07 +08:00
|
|
|
writel(0xFFFFFFFF, ®s->ecr);
|
2004-03-15 06:25:36 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (int) data;
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
|
|
|
|
2008-09-08 16:17:31 +08:00
|
|
|
static int pl01x_tstc (int portnum)
|
2004-03-14 23:06:13 +08:00
|
|
|
{
|
2010-05-05 11:53:07 +08:00
|
|
|
struct pl01x_regs *regs = pl01x_get_regs(portnum);
|
|
|
|
|
2008-06-03 04:42:19 +08:00
|
|
|
WATCHDOG_RESET();
|
2010-05-05 11:53:07 +08:00
|
|
|
return !(readl(®s->fr) & UART_PL01x_FR_RXFE);
|
2004-03-14 23:06:13 +08:00
|
|
|
}
|
2012-09-15 04:38:46 +08:00
|
|
|
|
|
|
|
static struct serial_device pl01x_serial_drv = {
|
|
|
|
.name = "pl01x_serial",
|
|
|
|
.start = pl01x_serial_init,
|
|
|
|
.stop = NULL,
|
|
|
|
.setbrg = pl01x_serial_setbrg,
|
|
|
|
.putc = pl01x_serial_putc,
|
2012-10-06 22:07:02 +08:00
|
|
|
.puts = default_serial_puts,
|
2012-09-15 04:38:46 +08:00
|
|
|
.getc = pl01x_serial_getc,
|
|
|
|
.tstc = pl01x_serial_tstc,
|
|
|
|
};
|
|
|
|
|
|
|
|
void pl01x_serial_initialize(void)
|
|
|
|
{
|
|
|
|
serial_register(&pl01x_serial_drv);
|
|
|
|
}
|
|
|
|
|
|
|
|
__weak struct serial_device *default_serial_console(void)
|
|
|
|
{
|
|
|
|
return &pl01x_serial_drv;
|
|
|
|
}
|