2004-03-14 15:06:13 +00: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 07:37:19 +00:00
|
|
|
* SPDX-License-Identifier: GPL-2.0+
|
2004-03-14 15:06:13 +00:00
|
|
|
*/
|
|
|
|
|
2008-09-08 12:30:53 +00:00
|
|
|
/* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */
|
2004-03-14 15:06:13 +00:00
|
|
|
|
|
|
|
#include <common.h>
|
2014-09-22 23:30:58 +00:00
|
|
|
#include <dm.h>
|
2014-09-22 23:30:57 +00:00
|
|
|
#include <errno.h>
|
2008-06-02 20:42:19 +00:00
|
|
|
#include <watchdog.h>
|
2010-10-07 21:48:46 +00:00
|
|
|
#include <asm/io.h>
|
2012-09-14 20:38:46 +00:00
|
|
|
#include <serial.h>
|
2014-10-24 03:41:19 +00:00
|
|
|
#include <dm/platform_data/serial_pl01x.h>
|
2012-09-14 20:38:46 +00:00
|
|
|
#include <linux/compiler.h>
|
2014-09-22 23:30:57 +00:00
|
|
|
#include "serial_pl01x_internal.h"
|
2015-05-06 18:46:29 +00:00
|
|
|
#include <fdtdec.h>
|
|
|
|
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
2004-03-14 15:06:13 +00:00
|
|
|
|
2014-09-22 23:30:58 +00:00
|
|
|
#ifndef CONFIG_DM_SERIAL
|
|
|
|
|
2004-08-02 23:22:59 +00:00
|
|
|
static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS;
|
2014-09-22 23:30:57 +00:00
|
|
|
static enum pl01x_type pl01x_type __attribute__ ((section(".data")));
|
|
|
|
static struct pl01x_regs *base_regs __attribute__ ((section(".data")));
|
2004-08-02 23:22:59 +00:00
|
|
|
#define NUM_PORTS (sizeof(port)/sizeof(port[0]))
|
2004-03-14 15:06:13 +00:00
|
|
|
|
2014-09-22 23:30:58 +00:00
|
|
|
#endif
|
2004-03-14 15:06:13 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_putc(struct pl01x_regs *regs, char c)
|
2010-05-05 03:53:07 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Wait until there is space in the FIFO */
|
|
|
|
if (readl(®s->fr) & UART_PL01x_FR_TXFF)
|
|
|
|
return -EAGAIN;
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Send the character */
|
|
|
|
writel(c, ®s->dr);
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_getc(struct pl01x_regs *regs)
|
|
|
|
{
|
|
|
|
unsigned int data;
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Wait until there is data in the FIFO */
|
|
|
|
if (readl(®s->fr) & UART_PL01x_FR_RXFE)
|
|
|
|
return -EAGAIN;
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
data = readl(®s->dr);
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Check for an error flag */
|
|
|
|
if (data & 0xFFFFFF00) {
|
|
|
|
/* Clear the error */
|
|
|
|
writel(0xFFFFFFFF, ®s->ecr);
|
|
|
|
return -1;
|
2004-03-14 22:25:36 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
return (int) data;
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_tstc(struct pl01x_regs *regs)
|
|
|
|
{
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
return !(readl(®s->fr) & UART_PL01x_FR_RXFE);
|
|
|
|
}
|
2008-09-08 08:17:31 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_generic_serial_init(struct pl01x_regs *regs,
|
|
|
|
enum pl01x_type type)
|
2008-09-08 08:17:31 +00:00
|
|
|
{
|
2014-11-21 18:34:23 +00:00
|
|
|
switch (type) {
|
|
|
|
case TYPE_PL010:
|
|
|
|
/* disable everything */
|
|
|
|
writel(0, ®s->pl010_cr);
|
|
|
|
break;
|
|
|
|
case TYPE_PL011:
|
2011-04-19 10:42:39 +00:00
|
|
|
#ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT
|
2014-09-22 23:30:57 +00:00
|
|
|
/* 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);
|
|
|
|
}
|
2011-04-19 10:42:39 +00:00
|
|
|
#endif
|
2014-11-21 18:34:22 +00:00
|
|
|
/* disable everything */
|
|
|
|
writel(0, ®s->pl011_cr);
|
2014-11-21 18:34:21 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-21 13:10:06 +00:00
|
|
|
static int pl011_set_line_control(struct pl01x_regs *regs)
|
2014-11-21 18:34:21 +00:00
|
|
|
{
|
|
|
|
unsigned int lcr;
|
|
|
|
/*
|
|
|
|
* Internal update of baud rate register require line
|
|
|
|
* control register write
|
|
|
|
*/
|
|
|
|
lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
|
2011-04-19 10:42:39 +00:00
|
|
|
#ifdef CONFIG_PL011_SERIAL_RLCR
|
2014-11-21 18:34:21 +00:00
|
|
|
{
|
2011-04-19 10:42:39 +00:00
|
|
|
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);
|
2014-09-22 23:30:57 +00:00
|
|
|
}
|
2014-11-21 18:34:21 +00:00
|
|
|
#endif
|
|
|
|
writel(lcr, ®s->pl011_lcrh);
|
2008-09-08 08:17:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_generic_setbrg(struct pl01x_regs *regs, enum pl01x_type type,
|
|
|
|
int clock, int baudrate)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
switch (type) {
|
|
|
|
case TYPE_PL010: {
|
|
|
|
unsigned int divisor;
|
|
|
|
|
2015-04-21 13:10:06 +00:00
|
|
|
/* disable everything */
|
|
|
|
writel(0, ®s->pl010_cr);
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
switch (baudrate) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
writel((divisor & 0xf00) >> 8, ®s->pl010_lcrm);
|
|
|
|
writel(divisor & 0xff, ®s->pl010_lcrl);
|
|
|
|
|
2015-04-21 13:10:06 +00:00
|
|
|
/*
|
|
|
|
* Set line control for the PL010 to be 8 bits, 1 stop bit,
|
|
|
|
* no parity, fifo enabled
|
|
|
|
*/
|
|
|
|
writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN,
|
|
|
|
®s->pl010_lcrh);
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Finally, enable the UART */
|
|
|
|
writel(UART_PL010_CR_UARTEN, ®s->pl010_cr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TYPE_PL011: {
|
|
|
|
unsigned int temp;
|
|
|
|
unsigned int divider;
|
|
|
|
unsigned int remainder;
|
|
|
|
unsigned int fraction;
|
2004-03-14 15:06:13 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
/*
|
|
|
|
* Set baud rate
|
|
|
|
*
|
|
|
|
* IBRD = UART_CLK / (16 * BAUD_RATE)
|
|
|
|
* FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE)))
|
|
|
|
* / (16 * BAUD_RATE))
|
|
|
|
*/
|
|
|
|
temp = 16 * baudrate;
|
|
|
|
divider = clock / temp;
|
|
|
|
remainder = clock % temp;
|
|
|
|
temp = (8 * remainder) / baudrate;
|
|
|
|
fraction = (temp >> 1) + (temp & 1);
|
|
|
|
|
|
|
|
writel(divider, ®s->pl011_ibrd);
|
|
|
|
writel(fraction, ®s->pl011_fbrd);
|
|
|
|
|
2015-04-21 13:10:06 +00:00
|
|
|
pl011_set_line_control(regs);
|
2014-09-22 23:30:57 +00:00
|
|
|
/* Finally, enable the UART */
|
|
|
|
writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE |
|
|
|
|
UART_PL011_CR_RXE | UART_PL011_CR_RTS, ®s->pl011_cr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2004-03-14 15:06:13 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
return 0;
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
#ifndef CONFIG_DM_SERIAL
|
|
|
|
static void pl01x_serial_init_baud(int baudrate)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
int clock = 0;
|
|
|
|
|
|
|
|
#if defined(CONFIG_PL010_SERIAL)
|
|
|
|
pl01x_type = TYPE_PL010;
|
|
|
|
#elif defined(CONFIG_PL011_SERIAL)
|
|
|
|
pl01x_type = TYPE_PL011;
|
|
|
|
clock = CONFIG_PL011_CLOCK;
|
|
|
|
#endif
|
|
|
|
base_regs = (struct pl01x_regs *)port[CONFIG_CONS_INDEX];
|
|
|
|
|
|
|
|
pl01x_generic_serial_init(base_regs, pl01x_type);
|
2014-11-21 18:34:19 +00:00
|
|
|
pl01x_generic_setbrg(base_regs, pl01x_type, clock, baudrate);
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00: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.
|
|
|
|
*/
|
|
|
|
int pl01x_serial_init(void)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
pl01x_serial_init_baud(CONFIG_BAUDRATE);
|
2011-10-02 11:52:52 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
return 0;
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static void pl01x_serial_putc(const char c)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
if (c == '\n')
|
|
|
|
while (pl01x_putc(base_regs, '\r') == -EAGAIN);
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
while (pl01x_putc(base_regs, c) == -EAGAIN);
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_serial_getc(void)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
while (1) {
|
|
|
|
int ch = pl01x_getc(base_regs);
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
if (ch == -EAGAIN) {
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
continue;
|
|
|
|
}
|
2004-03-14 22:25:36 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
return ch;
|
2004-03-14 22:25:36 +00:00
|
|
|
}
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static int pl01x_serial_tstc(void)
|
2004-03-14 15:06:13 +00:00
|
|
|
{
|
2014-09-22 23:30:57 +00:00
|
|
|
return pl01x_tstc(base_regs);
|
|
|
|
}
|
2010-05-05 03:53:07 +00:00
|
|
|
|
2014-09-22 23:30:57 +00:00
|
|
|
static void pl01x_serial_setbrg(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Flush FIFO and wait for non-busy before changing baudrate to avoid
|
|
|
|
* crap in console
|
|
|
|
*/
|
|
|
|
while (!(readl(&base_regs->fr) & UART_PL01x_FR_TXFE))
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
while (readl(&base_regs->fr) & UART_PL01x_FR_BUSY)
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
pl01x_serial_init_baud(gd->baudrate);
|
2004-03-14 15:06:13 +00:00
|
|
|
}
|
2012-09-14 20:38:46 +00: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 14:07:02 +00:00
|
|
|
.puts = default_serial_puts,
|
2012-09-14 20:38:46 +00: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;
|
|
|
|
}
|
2014-09-22 23:30:57 +00:00
|
|
|
|
|
|
|
#endif /* nCONFIG_DM_SERIAL */
|
2014-09-22 23:30:58 +00:00
|
|
|
|
|
|
|
#ifdef CONFIG_DM_SERIAL
|
|
|
|
|
|
|
|
struct pl01x_priv {
|
|
|
|
struct pl01x_regs *regs;
|
|
|
|
enum pl01x_type type;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pl01x_serial_setbrg(struct udevice *dev, int baudrate)
|
|
|
|
{
|
|
|
|
struct pl01x_serial_platdata *plat = dev_get_platdata(dev);
|
|
|
|
struct pl01x_priv *priv = dev_get_priv(dev);
|
|
|
|
|
|
|
|
pl01x_generic_setbrg(priv->regs, priv->type, plat->clock, baudrate);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pl01x_serial_probe(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct pl01x_serial_platdata *plat = dev_get_platdata(dev);
|
|
|
|
struct pl01x_priv *priv = dev_get_priv(dev);
|
|
|
|
|
|
|
|
priv->regs = (struct pl01x_regs *)plat->base;
|
|
|
|
priv->type = plat->type;
|
|
|
|
return pl01x_generic_serial_init(priv->regs, priv->type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pl01x_serial_getc(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct pl01x_priv *priv = dev_get_priv(dev);
|
|
|
|
|
|
|
|
return pl01x_getc(priv->regs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pl01x_serial_putc(struct udevice *dev, const char ch)
|
|
|
|
{
|
|
|
|
struct pl01x_priv *priv = dev_get_priv(dev);
|
|
|
|
|
|
|
|
return pl01x_putc(priv->regs, ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pl01x_serial_pending(struct udevice *dev, bool input)
|
|
|
|
{
|
|
|
|
struct pl01x_priv *priv = dev_get_priv(dev);
|
|
|
|
unsigned int fr = readl(&priv->regs->fr);
|
|
|
|
|
|
|
|
if (input)
|
|
|
|
return pl01x_tstc(priv->regs);
|
|
|
|
else
|
|
|
|
return fr & UART_PL01x_FR_TXFF ? 0 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dm_serial_ops pl01x_serial_ops = {
|
|
|
|
.putc = pl01x_serial_putc,
|
|
|
|
.pending = pl01x_serial_pending,
|
|
|
|
.getc = pl01x_serial_getc,
|
|
|
|
.setbrg = pl01x_serial_setbrg,
|
|
|
|
};
|
|
|
|
|
2015-05-06 18:46:29 +00:00
|
|
|
#ifdef CONFIG_OF_CONTROL
|
|
|
|
static const struct udevice_id pl01x_serial_id[] ={
|
|
|
|
{.compatible = "arm,pl011", .data = TYPE_PL011},
|
|
|
|
{.compatible = "arm,pl010", .data = TYPE_PL010},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pl01x_serial_ofdata_to_platdata(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct pl01x_serial_platdata *plat = dev_get_platdata(dev);
|
|
|
|
fdt_addr_t addr;
|
|
|
|
|
|
|
|
addr = fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg");
|
|
|
|
if (addr == FDT_ADDR_T_NONE)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
plat->base = addr;
|
|
|
|
plat->clock = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "clock", 1);
|
|
|
|
plat->type = dev_get_driver_data(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-09-22 23:30:58 +00:00
|
|
|
U_BOOT_DRIVER(serial_pl01x) = {
|
|
|
|
.name = "serial_pl01x",
|
|
|
|
.id = UCLASS_SERIAL,
|
2015-05-06 18:46:29 +00:00
|
|
|
.of_match = of_match_ptr(pl01x_serial_id),
|
|
|
|
.ofdata_to_platdata = of_match_ptr(pl01x_serial_ofdata_to_platdata),
|
|
|
|
.platdata_auto_alloc_size = sizeof(struct pl01x_serial_platdata),
|
2014-09-22 23:30:58 +00:00
|
|
|
.probe = pl01x_serial_probe,
|
|
|
|
.ops = &pl01x_serial_ops,
|
|
|
|
.flags = DM_FLAG_PRE_RELOC,
|
2014-11-25 04:36:35 +00:00
|
|
|
.priv_auto_alloc_size = sizeof(struct pl01x_priv),
|
2014-09-22 23:30:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|