af9c724907
When the console is on a serial port to be driven by serial8250, a character can be lost from the end of the first line in the two-line sequence serial8250.0: ttyS0 at MMIO 0xe0004500 (irq = 42) is a 16550A console handover: boot [udbg0] -> real [ttyS0] This happens because udbg_puts or udbg_write stuff the last byte of the line into the Tx FIFO and return, whereupon the serial8250 initialization code immediately empties that FIFO. The fix: udbg_puts and udbg_write now wait for the Tx FIFO to clear before returning. This delays the system by one additional serial frame time for each line written by udbg, but the effect is not noticeable, a cumulative 17 milliseconds for 200 lines of early printk output at 115200 baud. Also, the routines in udbg_16550.c now emit CRLF instead of LFCR. Linux makes a point of emitting CRLF because, when serial output is captured to a file, LFCR sequences can confuse text editors. See http://lkml.org/lkml/2006/2/4/50 for some history. Signed-off-by: Andrew Klossner <andrew@cesa.opbu.xerox.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
301 lines
6.4 KiB
C
301 lines
6.4 KiB
C
/*
|
|
* udbg for for NS16550 compatable serial ports
|
|
*
|
|
* Copyright (C) 2001-2005 PPC 64 Team, IBM Corp
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
#include <linux/types.h>
|
|
#include <asm/udbg.h>
|
|
#include <asm/io.h>
|
|
|
|
extern u8 real_readb(volatile u8 __iomem *addr);
|
|
extern void real_writeb(u8 data, volatile u8 __iomem *addr);
|
|
extern u8 real_205_readb(volatile u8 __iomem *addr);
|
|
extern void real_205_writeb(u8 data, volatile u8 __iomem *addr);
|
|
|
|
struct NS16550 {
|
|
/* this struct must be packed */
|
|
unsigned char rbr; /* 0 */
|
|
unsigned char ier; /* 1 */
|
|
unsigned char fcr; /* 2 */
|
|
unsigned char lcr; /* 3 */
|
|
unsigned char mcr; /* 4 */
|
|
unsigned char lsr; /* 5 */
|
|
unsigned char msr; /* 6 */
|
|
unsigned char scr; /* 7 */
|
|
};
|
|
|
|
#define thr rbr
|
|
#define iir fcr
|
|
#define dll rbr
|
|
#define dlm ier
|
|
#define dlab lcr
|
|
|
|
#define LSR_DR 0x01 /* Data ready */
|
|
#define LSR_OE 0x02 /* Overrun */
|
|
#define LSR_PE 0x04 /* Parity error */
|
|
#define LSR_FE 0x08 /* Framing error */
|
|
#define LSR_BI 0x10 /* Break */
|
|
#define LSR_THRE 0x20 /* Xmit holding register empty */
|
|
#define LSR_TEMT 0x40 /* Xmitter empty */
|
|
#define LSR_ERR 0x80 /* Error */
|
|
|
|
#define LCR_DLAB 0x80
|
|
|
|
static struct NS16550 __iomem *udbg_comport;
|
|
|
|
static void udbg_550_flush(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((in_8(&udbg_comport->lsr) & LSR_THRE) == 0)
|
|
/* wait for idle */;
|
|
}
|
|
}
|
|
|
|
static void udbg_550_putc(char c)
|
|
{
|
|
if (udbg_comport) {
|
|
if (c == '\n')
|
|
udbg_550_putc('\r');
|
|
udbg_550_flush();
|
|
out_8(&udbg_comport->thr, c);
|
|
}
|
|
}
|
|
|
|
static int udbg_550_getc_poll(void)
|
|
{
|
|
if (udbg_comport) {
|
|
if ((in_8(&udbg_comport->lsr) & LSR_DR) != 0)
|
|
return in_8(&udbg_comport->rbr);
|
|
else
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int udbg_550_getc(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((in_8(&udbg_comport->lsr) & LSR_DR) == 0)
|
|
/* wait for char */;
|
|
return in_8(&udbg_comport->rbr);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void udbg_init_uart(void __iomem *comport, unsigned int speed,
|
|
unsigned int clock)
|
|
{
|
|
unsigned int dll, base_bauds;
|
|
|
|
if (clock == 0)
|
|
clock = 1843200;
|
|
if (speed == 0)
|
|
speed = 9600;
|
|
|
|
base_bauds = clock / 16;
|
|
dll = base_bauds / speed;
|
|
|
|
if (comport) {
|
|
udbg_comport = (struct NS16550 __iomem *)comport;
|
|
out_8(&udbg_comport->lcr, 0x00);
|
|
out_8(&udbg_comport->ier, 0xff);
|
|
out_8(&udbg_comport->ier, 0x00);
|
|
out_8(&udbg_comport->lcr, LCR_DLAB);
|
|
out_8(&udbg_comport->dll, dll & 0xff);
|
|
out_8(&udbg_comport->dlm, dll >> 8);
|
|
/* 8 data, 1 stop, no parity */
|
|
out_8(&udbg_comport->lcr, 0x03);
|
|
/* RTS/DTR */
|
|
out_8(&udbg_comport->mcr, 0x03);
|
|
/* Clear & enable FIFOs */
|
|
out_8(&udbg_comport->fcr ,0x07);
|
|
udbg_putc = udbg_550_putc;
|
|
udbg_flush = udbg_550_flush;
|
|
udbg_getc = udbg_550_getc;
|
|
udbg_getc_poll = udbg_550_getc_poll;
|
|
}
|
|
}
|
|
|
|
unsigned int udbg_probe_uart_speed(void __iomem *comport, unsigned int clock)
|
|
{
|
|
unsigned int dll, dlm, divisor, prescaler, speed;
|
|
u8 old_lcr;
|
|
struct NS16550 __iomem *port = comport;
|
|
|
|
old_lcr = in_8(&port->lcr);
|
|
|
|
/* select divisor latch registers. */
|
|
out_8(&port->lcr, LCR_DLAB);
|
|
|
|
/* now, read the divisor */
|
|
dll = in_8(&port->dll);
|
|
dlm = in_8(&port->dlm);
|
|
divisor = dlm << 8 | dll;
|
|
|
|
/* check prescaling */
|
|
if (in_8(&port->mcr) & 0x80)
|
|
prescaler = 4;
|
|
else
|
|
prescaler = 1;
|
|
|
|
/* restore the LCR */
|
|
out_8(&port->lcr, old_lcr);
|
|
|
|
/* calculate speed */
|
|
speed = (clock / prescaler) / (divisor * 16);
|
|
|
|
/* sanity check */
|
|
if (speed > (clock / 16))
|
|
speed = 9600;
|
|
|
|
return speed;
|
|
}
|
|
|
|
#ifdef CONFIG_PPC_MAPLE
|
|
void udbg_maple_real_flush(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((real_readb(&udbg_comport->lsr) & LSR_THRE) == 0)
|
|
/* wait for idle */;
|
|
}
|
|
}
|
|
|
|
void udbg_maple_real_putc(char c)
|
|
{
|
|
if (udbg_comport) {
|
|
if (c == '\n')
|
|
udbg_maple_real_putc('\r');
|
|
udbg_maple_real_flush();
|
|
real_writeb(c, &udbg_comport->thr); eieio();
|
|
}
|
|
}
|
|
|
|
void __init udbg_init_maple_realmode(void)
|
|
{
|
|
udbg_comport = (struct NS16550 __iomem *)0xf40003f8;
|
|
|
|
udbg_putc = udbg_maple_real_putc;
|
|
udbg_flush = udbg_maple_real_flush;
|
|
udbg_getc = NULL;
|
|
udbg_getc_poll = NULL;
|
|
}
|
|
#endif /* CONFIG_PPC_MAPLE */
|
|
|
|
#ifdef CONFIG_PPC_PASEMI
|
|
void udbg_pas_real_flush(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((real_205_readb(&udbg_comport->lsr) & LSR_THRE) == 0)
|
|
/* wait for idle */;
|
|
}
|
|
}
|
|
|
|
void udbg_pas_real_putc(char c)
|
|
{
|
|
if (udbg_comport) {
|
|
if (c == '\n')
|
|
udbg_pas_real_putc('\r');
|
|
udbg_pas_real_flush();
|
|
real_205_writeb(c, &udbg_comport->thr); eieio();
|
|
}
|
|
}
|
|
|
|
void udbg_init_pas_realmode(void)
|
|
{
|
|
udbg_comport = (struct NS16550 __iomem *)0xfcff03f8UL;
|
|
|
|
udbg_putc = udbg_pas_real_putc;
|
|
udbg_flush = udbg_pas_real_flush;
|
|
udbg_getc = NULL;
|
|
udbg_getc_poll = NULL;
|
|
}
|
|
#endif /* CONFIG_PPC_MAPLE */
|
|
|
|
#ifdef CONFIG_PPC_EARLY_DEBUG_44x
|
|
#include <platforms/44x/44x.h>
|
|
|
|
static int udbg_44x_as1_flush(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((as1_readb(&udbg_comport->lsr) & LSR_THRE) == 0)
|
|
/* wait for idle */;
|
|
}
|
|
}
|
|
|
|
static void udbg_44x_as1_putc(char c)
|
|
{
|
|
if (udbg_comport) {
|
|
if (c == '\n')
|
|
udbg_44x_as1_putc('\r');
|
|
udbg_44x_as1_flush();
|
|
as1_writeb(c, &udbg_comport->thr); eieio();
|
|
}
|
|
}
|
|
|
|
static int udbg_44x_as1_getc(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((as1_readb(&udbg_comport->lsr) & LSR_DR) == 0)
|
|
; /* wait for char */
|
|
return as1_readb(&udbg_comport->rbr);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void __init udbg_init_44x_as1(void)
|
|
{
|
|
udbg_comport =
|
|
(struct NS16550 __iomem *)PPC44x_EARLY_DEBUG_VIRTADDR;
|
|
|
|
udbg_putc = udbg_44x_as1_putc;
|
|
udbg_flush = udbg_44x_as1_flush;
|
|
udbg_getc = udbg_44x_as1_getc;
|
|
}
|
|
#endif /* CONFIG_PPC_EARLY_DEBUG_44x */
|
|
|
|
#ifdef CONFIG_PPC_EARLY_DEBUG_40x
|
|
static void udbg_40x_real_flush(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((real_readb(&udbg_comport->lsr) & LSR_THRE) == 0)
|
|
/* wait for idle */;
|
|
}
|
|
}
|
|
|
|
static void udbg_40x_real_putc(char c)
|
|
{
|
|
if (udbg_comport) {
|
|
if (c == '\n')
|
|
udbg_40x_real_putc('\r');
|
|
udbg_40x_real_flush();
|
|
real_writeb(c, &udbg_comport->thr); eieio();
|
|
}
|
|
}
|
|
|
|
static int udbg_40x_real_getc(void)
|
|
{
|
|
if (udbg_comport) {
|
|
while ((real_readb(&udbg_comport->lsr) & LSR_DR) == 0)
|
|
; /* wait for char */
|
|
return real_readb(&udbg_comport->rbr);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void __init udbg_init_40x_realmode(void)
|
|
{
|
|
udbg_comport = (struct NS16550 __iomem *)
|
|
CONFIG_PPC_EARLY_DEBUG_40x_PHYSADDR;
|
|
|
|
udbg_putc = udbg_40x_real_putc;
|
|
udbg_flush = udbg_40x_real_flush;
|
|
udbg_getc = udbg_40x_real_getc;
|
|
udbg_getc_poll = NULL;
|
|
}
|
|
#endif /* CONFIG_PPC_EARLY_DEBUG_40x */
|