/*
 * udbg for NS16550 compatible 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>
#include <asm/reg_a2.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 void 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 */

#ifdef CONFIG_PPC_EARLY_DEBUG_WSP
static void udbg_wsp_flush(void)
{
	if (udbg_comport) {
		while ((readb(&udbg_comport->lsr) & LSR_THRE) == 0)
			/* wait for idle */;
	}
}

static void udbg_wsp_putc(char c)
{
	if (udbg_comport) {
		if (c == '\n')
			udbg_wsp_putc('\r');
		udbg_wsp_flush();
		writeb(c, &udbg_comport->thr); eieio();
	}
}

static int udbg_wsp_getc(void)
{
	if (udbg_comport) {
		while ((readb(&udbg_comport->lsr) & LSR_DR) == 0)
			; /* wait for char */
		return readb(&udbg_comport->rbr);
	}
	return -1;
}

static int udbg_wsp_getc_poll(void)
{
	if (udbg_comport)
		if (readb(&udbg_comport->lsr) & LSR_DR)
			return readb(&udbg_comport->rbr);
	return -1;
}

void __init udbg_init_wsp(void)
{
	udbg_comport = (struct NS16550 __iomem *)WSP_UART_VIRT;

	udbg_init_uart(udbg_comport, 57600, 50000000);

	udbg_putc = udbg_wsp_putc;
	udbg_flush = udbg_wsp_flush;
	udbg_getc = udbg_wsp_getc;
	udbg_getc_poll = udbg_wsp_getc_poll;
}
#endif /* CONFIG_PPC_EARLY_DEBUG_WSP */
