dm: serial: Tidy up the pl01x driver

Adjust the driver so that leaf functions take a pointer to the serial port
register base. Put all the global configuration in the init function, and
use the same settings from then on.

This makes it much easier to move to driver model without duplicating the
code, since with driver model we use platform data rather than global
settings.

The driver is compiled with either the CONFIG_PL010_SERIAL or
CONFIG_PL011_SERIAL option and this determines the uart type. With driver
model this needs to come in from platform data, so create a new
CONFIG_PL01X_SERIAL config which brings in the driver, and adjust the
driver to support both peripheral variants.

Signed-off-by: Simon Glass <sjg@chromium.org>
Tested-by: Stephen Warren <swarren@wwwdotorg.org>
This commit is contained in:
Simon Glass 2014-09-22 17:30:57 -06:00
parent 41e98e011d
commit aed2fbef5e
4 changed files with 233 additions and 181 deletions

View file

@ -7,8 +7,11 @@
ifdef CONFIG_DM_SERIAL ifdef CONFIG_DM_SERIAL
obj-y += serial-uclass.o obj-y += serial-uclass.o
obj-$(CONFIG_PL01X_SERIAL) += serial_pl01x.o
else else
obj-y += serial.o obj-y += serial.o
obj-$(CONFIG_PL010_SERIAL) += serial_pl01x.o
obj-$(CONFIG_PL011_SERIAL) += serial_pl01x.o
obj-$(CONFIG_SYS_NS16550_SERIAL) += serial_ns16550.o obj-$(CONFIG_SYS_NS16550_SERIAL) += serial_ns16550.o
endif endif
@ -25,8 +28,6 @@ obj-$(CONFIG_IMX_SERIAL) += serial_imx.o
obj-$(CONFIG_KS8695_SERIAL) += serial_ks8695.o obj-$(CONFIG_KS8695_SERIAL) += serial_ks8695.o
obj-$(CONFIG_MAX3100_SERIAL) += serial_max3100.o obj-$(CONFIG_MAX3100_SERIAL) += serial_max3100.o
obj-$(CONFIG_MXC_UART) += serial_mxc.o obj-$(CONFIG_MXC_UART) += serial_mxc.o
obj-$(CONFIG_PL010_SERIAL) += serial_pl01x.o
obj-$(CONFIG_PL011_SERIAL) += serial_pl01x.o
obj-$(CONFIG_PXA_SERIAL) += serial_pxa.o obj-$(CONFIG_PXA_SERIAL) += serial_pxa.o
obj-$(CONFIG_SA1100_SERIAL) += serial_sa1100.o obj-$(CONFIG_SA1100_SERIAL) += serial_sa1100.o
obj-$(CONFIG_S3C24X0_SERIAL) += serial_s3c24x0.o obj-$(CONFIG_S3C24X0_SERIAL) += serial_s3c24x0.o

View file

@ -12,203 +12,40 @@
/* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */ /* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */
#include <common.h> #include <common.h>
#include <errno.h>
#include <watchdog.h> #include <watchdog.h>
#include <asm/io.h> #include <asm/io.h>
#include <serial.h> #include <serial.h>
#include <serial_pl01x.h>
#include <linux/compiler.h> #include <linux/compiler.h>
#include "serial_pl01x.h" #include "serial_pl01x_internal.h"
/*
* 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.
*/
#define CONSOLE_PORT CONFIG_CONS_INDEX
static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS; static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS;
static enum pl01x_type pl01x_type __attribute__ ((section(".data")));
static struct pl01x_regs *base_regs __attribute__ ((section(".data")));
#define NUM_PORTS (sizeof(port)/sizeof(port[0])) #define NUM_PORTS (sizeof(port)/sizeof(port[0]))
static void pl01x_putc (int portnum, char c);
static int pl01x_getc (int portnum);
static int pl01x_tstc (int portnum);
unsigned int baudrate = CONFIG_BAUDRATE;
DECLARE_GLOBAL_DATA_PTR; DECLARE_GLOBAL_DATA_PTR;
static struct pl01x_regs *pl01x_get_regs(int portnum) static int pl01x_putc(struct pl01x_regs *regs, char c)
{ {
return (struct pl01x_regs *) port[portnum];
}
#ifdef CONFIG_PL010_SERIAL
static int pl01x_serial_init(void)
{
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
unsigned int divisor;
/* First, disable everything */
writel(0, &regs->pl010_cr);
/* Set baud rate */
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, &regs->pl010_lcrm);
writel(divisor & 0xff, &regs->pl010_lcrl);
/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN, &regs->pl010_lcrh);
/* Finally, enable the UART */
writel(UART_PL010_CR_UARTEN, &regs->pl010_cr);
return 0;
}
#endif /* CONFIG_PL010_SERIAL */
#ifdef CONFIG_PL011_SERIAL
static int pl01x_serial_init(void)
{
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
unsigned int temp;
unsigned int divider;
unsigned int remainder;
unsigned int fraction;
unsigned int lcr;
#ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT
/* Empty RX fifo if necessary */
if (readl(&regs->pl011_cr) & UART_PL011_CR_UARTEN) {
while (!(readl(&regs->fr) & UART_PL01x_FR_RXFE))
readl(&regs->dr);
}
#endif
/* First, disable everything */
writel(0, &regs->pl011_cr);
/*
* 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 = CONFIG_PL011_CLOCK / temp;
remainder = CONFIG_PL011_CLOCK % temp;
temp = (8 * remainder) / baudrate;
fraction = (temp >> 1) + (temp & 1);
writel(divider, &regs->pl011_ibrd);
writel(fraction, &regs->pl011_fbrd);
/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
writel(lcr, &regs->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, &regs->fr);
writel(lcr, &regs->pl011_rlcr);
/* lcrh needs to be set again for change to be effective */
writel(lcr, &regs->pl011_lcrh);
}
#endif
/* Finally, enable the UART */
writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE |
UART_PL011_CR_RTS, &regs->pl011_cr);
return 0;
}
#endif /* CONFIG_PL011_SERIAL */
static void pl01x_serial_putc(const char c)
{
if (c == '\n')
pl01x_putc (CONSOLE_PORT, '\r');
pl01x_putc (CONSOLE_PORT, c);
}
static int pl01x_serial_getc(void)
{
return pl01x_getc (CONSOLE_PORT);
}
static int pl01x_serial_tstc(void)
{
return pl01x_tstc (CONSOLE_PORT);
}
static void pl01x_serial_setbrg(void)
{
struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT);
baudrate = gd->baudrate;
/*
* Flush FIFO and wait for non-busy before changing baudrate to avoid
* crap in console
*/
while (!(readl(&regs->fr) & UART_PL01x_FR_TXFE))
WATCHDOG_RESET();
while (readl(&regs->fr) & UART_PL01x_FR_BUSY)
WATCHDOG_RESET();
serial_init();
}
static void pl01x_putc (int portnum, char c)
{
struct pl01x_regs *regs = pl01x_get_regs(portnum);
/* Wait until there is space in the FIFO */ /* Wait until there is space in the FIFO */
while (readl(&regs->fr) & UART_PL01x_FR_TXFF) if (readl(&regs->fr) & UART_PL01x_FR_TXFF)
WATCHDOG_RESET(); return -EAGAIN;
/* Send the character */ /* Send the character */
writel(c, &regs->dr); writel(c, &regs->dr);
return 0;
} }
static int pl01x_getc (int portnum) static int pl01x_getc(struct pl01x_regs *regs)
{ {
struct pl01x_regs *regs = pl01x_get_regs(portnum);
unsigned int data; unsigned int data;
/* Wait until there is data in the FIFO */ /* Wait until there is data in the FIFO */
while (readl(&regs->fr) & UART_PL01x_FR_RXFE) if (readl(&regs->fr) & UART_PL01x_FR_RXFE)
WATCHDOG_RESET(); return -EAGAIN;
data = readl(&regs->dr); data = readl(&regs->dr);
@ -222,14 +59,199 @@ static int pl01x_getc (int portnum)
return (int) data; return (int) data;
} }
static int pl01x_tstc (int portnum) static int pl01x_tstc(struct pl01x_regs *regs)
{ {
struct pl01x_regs *regs = pl01x_get_regs(portnum);
WATCHDOG_RESET(); WATCHDOG_RESET();
return !(readl(&regs->fr) & UART_PL01x_FR_RXFE); return !(readl(&regs->fr) & UART_PL01x_FR_RXFE);
} }
static int pl01x_generic_serial_init(struct pl01x_regs *regs,
enum pl01x_type type)
{
unsigned int lcr;
#ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT
if (type == TYPE_PL011) {
/* Empty RX fifo if necessary */
if (readl(&regs->pl011_cr) & UART_PL011_CR_UARTEN) {
while (!(readl(&regs->fr) & UART_PL01x_FR_RXFE))
readl(&regs->dr);
}
}
#endif
/* First, disable everything */
writel(0, &regs->pl010_cr);
/* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */
lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
writel(lcr, &regs->pl011_lcrh);
switch (type) {
case TYPE_PL010:
break;
case TYPE_PL011: {
#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, &regs->fr);
writel(lcr, &regs->pl011_rlcr);
/* lcrh needs to be set again for change to be effective */
writel(lcr, &regs->pl011_lcrh);
#endif
break;
}
default:
return -EINVAL;
}
return 0;
}
static int pl01x_generic_setbrg(struct pl01x_regs *regs, enum pl01x_type type,
int clock, int baudrate)
{
switch (type) {
case TYPE_PL010: {
unsigned int divisor;
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, &regs->pl010_lcrm);
writel(divisor & 0xff, &regs->pl010_lcrl);
/* Finally, enable the UART */
writel(UART_PL010_CR_UARTEN, &regs->pl010_cr);
break;
}
case TYPE_PL011: {
unsigned int temp;
unsigned int divider;
unsigned int remainder;
unsigned int fraction;
/*
* 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, &regs->pl011_ibrd);
writel(fraction, &regs->pl011_fbrd);
/* Finally, enable the UART */
writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE |
UART_PL011_CR_RXE | UART_PL011_CR_RTS, &regs->pl011_cr);
break;
}
default:
return -EINVAL;
}
return 0;
}
#ifndef CONFIG_DM_SERIAL
static void pl01x_serial_init_baud(int baudrate)
{
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);
pl01x_generic_setbrg(base_regs, TYPE_PL010, clock, baudrate);
}
/*
* 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)
{
pl01x_serial_init_baud(CONFIG_BAUDRATE);
return 0;
}
static void pl01x_serial_putc(const char c)
{
if (c == '\n')
while (pl01x_putc(base_regs, '\r') == -EAGAIN);
while (pl01x_putc(base_regs, c) == -EAGAIN);
}
static int pl01x_serial_getc(void)
{
while (1) {
int ch = pl01x_getc(base_regs);
if (ch == -EAGAIN) {
WATCHDOG_RESET();
continue;
}
return ch;
}
}
static int pl01x_serial_tstc(void)
{
return pl01x_tstc(base_regs);
}
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);
}
static struct serial_device pl01x_serial_drv = { static struct serial_device pl01x_serial_drv = {
.name = "pl01x_serial", .name = "pl01x_serial",
.start = pl01x_serial_init, .start = pl01x_serial_init,
@ -250,3 +272,5 @@ __weak struct serial_device *default_serial_console(void)
{ {
return &pl01x_serial_drv; return &pl01x_serial_drv;
} }
#endif /* nCONFIG_DM_SERIAL */

27
include/serial_pl01x.h Normal file
View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2014 Google, Inc
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef __serial_pl01x_h
#define __serial_pl01x_h
enum pl01x_type {
TYPE_PL010,
TYPE_PL011,
};
/*
*Information about a serial port
*
* @base: Register base address
* @type: Port type
* @clock: Input clock rate, used for calculating the baud rate divisor
*/
struct pl01x_serial_platdata {
unsigned long base;
enum pl01x_type type;
unsigned int clock;
};
#endif