/*
 *  linux/drivers/char/serial_s3c2400.c
 *
 *  Driver for S3C2400 serial ports
 *
 *  Copyright (C) 2002 Gary Jennejohn (gj@denx.de)
 *
 *  Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.
 *  Based on drivers/serial/serial_amba.c
 *  Copyright 1999 ARM Limited
 *  Copyright (C) 2000 Deep Blue Solutions Ltd.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  $Id:$
 *
 * This is a generic driver for ARM S3C2400-type serial ports.
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/circ_buf.h>
#include <linux/serial.h>
#include <linux/console.h>
#include <linux/sysrq.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>

#if defined(CONFIG_SERIAL_S3C2400_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif

#include <linux/serial_core.h>

#ifdef CONFIG_TRAB
#define UART_NR		1
#else
#define UART_NR		2
#endif

#define SERIAL_S3C2400_MAJOR	TTY_MAJOR
#define SERIAL_S3C2400_MINOR	64
#define SERIAL_S3C2400_NR	UART_NR

#define CALLOUT_S3C2400_NAME	"cua"
#define CALLOUT_S3C2400_MAJOR	TTYAUX_MAJOR
#define CALLOUT_S3C2400_MINOR	64
#define CALLOUT_S3C2400_NR	UART_NR

static struct tty_driver normal, callout;
static struct tty_struct *s3c2400_table[UART_NR];
static struct termios *s3c2400_termios[UART_NR], *s3c2400_termios_locked[UART_NR];
static struct uart_port s3c2400_ports[UART_NR];
#ifdef CONFIG_SERIAL_S3C2400_CONSOLE /*SUPPORT_SYSRQ*/
static struct console s3c2400_console;
#endif
static unsigned int fclk = 0;
static unsigned int uartclk = 0;
static char s3c2400_do_modem = 0;

/* forward declaration to shut the compiler up */
static void s3c2400_rx_int(int, void *, struct pt_regs *);

/*
 * Access macros for the S3C2400 UARTs
 */
#define GET_ULCON(port)	*((unsigned int *)((port)->membase + ULCON))
#define GET_UCON(port)	*((unsigned int *)((port)->membase + UCON))
#define GET_UFCON(port)	*((unsigned int *)((port)->membase + UFCON))
#define GET_UMCON(port)	*((unsigned int *)((port)->membase + UMCON))
#define GET_UTRSTAT(port)	*((unsigned int *)((port)->membase + UTRSTAT))
#define GET_UERSTAT(port)	*((unsigned int *)((port)->membase + UERSTAT))
#define GET_UFSTAT(port)	*((unsigned int *)((port)->membase + UFSTAT))
#define GET_UMSTAT(port)	*((unsigned int *)((port)->membase + UMSTAT))
#define GET_URXH(port)	*((unsigned int *)((port)->membase + URXH))
#define GET_UBRDIV(port)	*((unsigned int *)((port)->membase + UBRDIV))
#define SET_ULCON(port, val)	*((unsigned int *)((port)->membase + ULCON)) = (val)
#define SET_UCON(port, val) *((unsigned int *)((port)->membase + UCON)) = (val)
#define SET_UFCON(port, val) *((unsigned int *)((port)->membase + UFCON)) = (val)
#define SET_UMCON(port, val) *((unsigned int *)((port)->membase + UMCON)) = (val)
#define SET_UTRSTAT(port, val) *((unsigned int *)((port)->membase + UTRSTAT)) = (val)
#define SET_UERSTAT(port, val) *((unsigned int *)((port)->membase + UERSTAT)) = (val)
#define SET_UFSTAT(port, val) *((unsigned int *)((port)->membase + UFSTAT)) = (val)
#define SET_UMSTAT(port, val) *((unsigned int *)((port)->membase + UMSTAT)) = (val)
#define SET_UTXH(port, val) *((unsigned int *)((port)->membase + UTXH)) = (val)
#define SET_UBRDIV(port, val) *((unsigned int *)((port)->membase + UBRDIV)) = (val)

/* fake flag to indicate CREAD was not set -> throw away all bytes */
#define UART_DUMMY_UER_RX 256

/* interrupt mask manipulations */
#ifdef INTMSK_OK
#define ENABLE_IRQ(n) rSRCPND = (1 << (n)); rINTPND = (1 << (n)); rINTMSK |= (1 << (n))
/*#define DISABLE_IRQ(n) rSRCPND = (1 << (n)); rINTPND = (1 << (n)); rINTMSK &= ~(1 << (n))*/
#define DISABLE_IRQ(n) rINTMSK &= ~(1 << (n))
#else
#define ENABLE_IRQ(n) enable_irq((n))
#define DISABLE_IRQ(n) disable_irq((n))
#endif

/* for HACK in time.h - GJ */
struct uart_info *myui[UART_NR];

/*
 * Our private driver data mappings.
 */
#define drv_old_status	driver_priv

#ifdef HANG_DEBUG
/* for debugging */
char serdebug[128];
char *sbptr;
#define SERDEBUG(f) \
sbptr += sprintf(sbptr, f); \
if ((sbptr - serdebug) > 115) \
sbptr = serdebug;
#else
#define SERDEBUG(f)
#endif

#define MPLLCON (io_p2v(0x14800004))
#define CLKDIVN (io_p2v(0x14800014))
static void s3c2400_get_fclk(void)
{
	unsigned int mpllcon = *(unsigned int *)MPLLCON;
	int clkdivn = (*(unsigned int *)CLKDIVN) & 3;
	int clk_factor[4] = {1, 2, 2, 4};

	/* the S3C2400 uses a 12 MHz xtal */
	/* formula from page 6-22 */
	fclk = ((12000000 * (8 + ((mpllcon & 0xff000) >> 12)))/((2 + ((mpllcon & 0x1f0) >> 4)) * (1 << (mpllcon & 3))));
	uartclk = fclk/clk_factor[clkdivn];
	printk ("CPU clock: %d MHz\n", fclk/1000000);
}

static void s3c2400_stop_tx(struct uart_port *port, u_int from_tty)
{
SERDEBUG("-1")
	DISABLE_IRQ(port->unused[1]);
}

static void s3c2400_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty)
{
SERDEBUG("+1")
	ENABLE_IRQ(port->unused[1]);
}

static void s3c2400_stop_rx(struct uart_port *port)
{
SERDEBUG("-2")
	DISABLE_IRQ(port->irq);
}

static void s3c2400_enable_ms(struct uart_port *port)
{
	/* this requires using GPIO and is board specific */
	/* The SMDK2400 only supports RTS/CTS w/o interrupts */
	/* GJ - enable EINT7 on TRAB for RI here ? */
	return;
}

/*
 * Special case: info == NULL iff the routine is called from
 * time.h. In this case we try to check both UARTs, else just
 * do the one passed in from the caller.
 */
void
serial_s3c2400_check_rx_overrun(struct uart_info *info)
{
	struct tty_struct *tty;
	unsigned int ch, rsr;
	struct uart_port *port;
	struct uart_info *linfo;
	int i;

	for (i = 0; i < UART_NR; i++)
  	{
		if (info == NULL)
		{
			linfo = myui[i];
			if (linfo == NULL)
				continue;
		}
		else
			linfo = info;
		tty = linfo->tty;
		port = linfo->port;

		rsr = GET_UERSTAT(port);
		if ((rsr & UERSTAT_OVERRUN) && (tty != NULL))
		{
			ch = GET_URXH(port);
			tty_insert_flip_char(tty, ch, rsr);
			tty_flip_buffer_push(tty);
		}
		if (info == linfo)
			break;
	}
}

static void
#ifdef SUPPORT_SYSRQ
s3c2400_rx_chars(struct uart_info *info, struct pt_regs *regs)
#else
s3c2400_rx_chars(struct uart_info *info)
#endif
{
	struct tty_struct *tty = info->tty;
	unsigned int ch = 0, rsr = 0;
	struct uart_port *port = info->port;

SERDEBUG("+3")
	ENABLE_IRQ(port->irq);
	while ((GET_UFSTAT(port) & (UFSTAT_RXCNTMASK | UFSTAT_RXFIFOFULL)) > 0) {
		ch = GET_URXH(port);
		rsr = GET_UERSTAT(port) | UART_DUMMY_UER_RX;

		if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
			tty->flip.tqueue.routine((void *)tty);
			if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
				printk(KERN_WARNING "TTY_DONT_FLIP set\n");
				return;
			}
		}

		*tty->flip.char_buf_ptr = ch;
		*tty->flip.flag_buf_ptr = TTY_NORMAL;
		port->icount.rx++;

		/*
		 * Note that the error handling code is
		 * out of the main execution path
		 */
		if (rsr & UERSTAT_ANY) {
			if (rsr & UERSTAT_BREAK) {
SERDEBUG("B ")
				rsr &= ~(UERSTAT_FRAERR | UERSTAT_PARERR);
				port->icount.brk++;
				if (uart_handle_break(info, &s3c2400_console))
					continue;
			} else if (rsr & UERSTAT_PARERR) {
SERDEBUG("P ")
				port->icount.parity++;
			} else if (rsr & UERSTAT_FRAERR) {
SERDEBUG("F ")
				port->icount.frame++;
			}
			if (rsr & UERSTAT_OVERRUN) {
				port->icount.overrun++;
			}

			rsr &= port->read_status_mask;

			if (rsr & UERSTAT_BREAK)
				*tty->flip.flag_buf_ptr = TTY_BREAK;
			else if (rsr & UERSTAT_PARERR)
				*tty->flip.flag_buf_ptr = TTY_PARITY;
			else if (rsr & UERSTAT_FRAERR)
				*tty->flip.flag_buf_ptr = TTY_FRAME;
			rSRCPND = BIT_UERR01;
			rINTPND = BIT_UERR01;
		}

#ifdef SUPPORT_SYSRQ
		if (uart_handle_sysrq_char(info, ch, regs))
			continue;
#endif

		if ((rsr & port->ignore_status_mask) == 0) {
			tty->flip.flag_buf_ptr++;
			tty->flip.char_buf_ptr++;
			tty->flip.count++;
		}
		if ((rsr & UERSTAT_OVERRUN) &&
		    tty->flip.count < TTY_FLIPBUF_SIZE) {
			/*
			 * Overrun is special, since it's reported
			 * immediately, and doesn't affect the current
			 * character
			 */
			*tty->flip.char_buf_ptr++ = 0;
			*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
			tty->flip.count++;
		}
	}
	if (ch != 0)
		tty_flip_buffer_push(tty);
	return;
}

static void s3c2400_tx_chars(struct uart_info *info)
{
	struct uart_port *port = info->port;

	if (info->xmit.head == info->xmit.tail
	    || info->tty->stopped
	    || info->tty->hw_stopped) {
SERDEBUG("XX")
		s3c2400_stop_tx(port, 0);
		return;
	}

	while ((GET_UFSTAT(port) & UFSTAT_TXFIFOFULL) != UFSTAT_TXFIFOFULL) {
		if (port->x_char) {
			SET_UTXH(port, port->x_char);
			port->icount.tx++;
			port->x_char = 0;
			return;
		}
		SET_UTXH(port, info->xmit.buf[info->xmit.tail]);
		info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
		if (info->xmit.head == info->xmit.tail)
			break;
	}

	if (CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) <
			WAKEUP_CHARS)
		uart_event(info, EVT_WRITE_WAKEUP);

	if (info->xmit.head == info->xmit.tail)
	{
SERDEBUG("ZZ")
		s3c2400_stop_tx(info->port, 0);
	}
}

static void s3c2400_modem_status(struct uart_info *info)
{
	struct uart_port *port = info->port;
	unsigned int status;

	/* this requires using GPIO and is board specific */
	/* The SMDK2400 only supports RTS/CTS */

	status = GET_UMSTAT(port);
	if (status & UMSTAT_CTSDELTA)
		uart_handle_cts_change(info, status & UMSTAT_CTSACTIVE);

	wake_up_interruptible(&info->delta_msr_wait);
}

static void s3c2400_tx_int(int irq, void *dev_id, struct pt_regs *regs)
{
	struct uart_info *info = dev_id;

SERDEBUG("TX")
	s3c2400_tx_chars(info);

	/* since we don't have a modem change interrupt - XXXX */
	if (s3c2400_do_modem == 1)
		s3c2400_modem_status(info);

	/* catch RX as quickly as possible - TX is not so important */
	/* since the bytes are alreeady queued up */
	if (rSRCPND & BIT_URXD0)
	{
		if (myui[0] != NULL)
		{
			s3c2400_rx_int(IRQ_URXD0, myui[0], regs);
			rSRCPND = BIT_URXD0;
			rINTPND = BIT_URXD0;
		}
	}
#ifndef CONFIG_TRAB
	if (rSRCPND & BIT_URXD1)
	{
		if (myui[1] != NULL)
		{
			s3c2400_rx_int(IRQ_URXD1, myui[1], regs);
			rSRCPND = BIT_URXD1;
			rINTPND = BIT_URXD1;
		}
	}
#endif
}

static void s3c2400_rx_int(int irq, void *dev_id, struct pt_regs *regs)
{
	struct uart_info *info = dev_id;

SERDEBUG("RX")
#ifdef SUPPORT_SYSRQ
	s3c2400_rx_chars(info, regs);
#else
	s3c2400_rx_chars(info);
#endif

	/* since we don't have a modem change interrupt - XXXX */
	if (s3c2400_do_modem == 1)
		s3c2400_modem_status(info);
}

static u_int s3c2400_tx_empty(struct uart_port *port)
{
	int status;

	status = GET_UFSTAT(port) & (UFSTAT_TXCNTMASK | UFSTAT_TXFIFOFULL);
	return status ? 0 : TIOCSER_TEMT;
}

static u_int s3c2400_get_mctrl(struct uart_port *port)
{
	unsigned int result = 0;
	unsigned int status;

	/* this requires using GPIO and is board specific */
	/* The SMDK2400 only supports RTS/CTS */

	status = GET_UMSTAT(port); /* only for CTS */
	/* say they're set per Documentation/serial/driver */
	result |= (TIOCM_CAR | TIOCM_DSR);
	if (status & UMSTAT_CTSACTIVE)
		result |= TIOCM_CTS;

#ifdef CONFIG_TRAB
	/* Check RI, which is on PORT E bit 7 */
	/* PE7 is an input by default, so no setup is required */
	/* This assumes that RI is active high, which per the */
	/* RS232 standard it is. */
	if (rPEDAT & 0x80)
		result |= TIOCM_RI;
#endif
	return result;
}

static void s3c2400_set_mctrl(struct uart_port *port, u_int mctrl)
{
	u_int status;

	/* this requires using GPIO and is board specific */
	/* The SMDK2400 only supports RTS/CTS */

	status = GET_UMCON(port);

	/* if CTS/RTS are controlled by the HW then just leave */
	if (status & UMCON_AFCON)
		return;

	if (mctrl & TIOCM_RTS)
		status |= UMCON_RTSON;
	else
		status &= ~UMCON_RTSON;
	SET_UMCON(port, status);
}

static void s3c2400_break_ctl(struct uart_port *port, int break_state)
{
	unsigned int lcr_h;

	lcr_h = GET_UCON(port);
	/* timing is actually controlled by HW on the S3C2400 */
	if (break_state != 0)
		lcr_h |= UCON_TXBRK;
	else
		lcr_h &= ~UCON_TXBRK;
	SET_UCON(port, lcr_h);
}

static int s3c2400_startup(struct uart_port *port, struct uart_info *info)
{
	int retval;
	u_int ufcon, ucon;
	char *rxstr[2] = {"rx0", "rx1" };
	char *txstr[2] = {"tx0", "tx1" };

	if (fclk == 0 || uartclk == 0)
		s3c2400_get_fclk();

	s3c2400_ports[0].uartclk = uartclk;
#ifndef CONFIG_TRAB
	s3c2400_ports[1].uartclk = uartclk;
#endif

	/* for hack in time.h - GJ */
	if (port == &s3c2400_ports[0])
		myui[0] = info;
#ifndef CONFIG_TRAB
	else
		myui[1] = info;
#endif

	/* block the IRQ */
SERDEBUG("-4")
    	DISABLE_IRQ(port->irq);
SERDEBUG("-5")
    	DISABLE_IRQ(port->unused[1]);

	/* We always use the FIFOs */
	ufcon = (UFCON_FIFO|UFCON_RXFIFOTL12|UFCON_TXFIFOTL4);
	ufcon |= (UFCON_RXFIFORESET|UFCON_TXFIFORESET);

	/*
	 * Allocate the IRQs
	 */
	retval = request_irq(port->irq, s3c2400_rx_int, 0, rxstr[port->unused[0]], info);
	if (retval)
		return retval;
	retval = request_irq(port->unused[1], s3c2400_tx_int, 0, txstr[port->unused[0]], info);
	if (retval)
	{
		free_irq(port->irq, info);
		return retval;
	}
#if 0 /* no interrupts on errors */
	if (port->unused[0] == 0)
	{
		retval = request_irq(port->unused[2], s3c2400_error_int, 0, "error", info);
		if (retval)
		{
			free_irq(port->irq, info);
			free_irq(port->unused[1], info);
			return retval;
		}
	}
#endif

	/*
	 * initialise the old status of the modem signals
	 */
	/* also need GPIO stuff here - XXX */
	info->drv_old_status = GET_UMSTAT(port) & UMSTAT_CTSACTIVE;

	/*
	 * Finally, enable interrupts
	 */
	/* no interrupts on errors */
	rINTMSK |= BIT_UERR01;
	ucon = (UCON_RXINT|UCON_TXINT|UCON_LEVELTXINT|UCON_RXTO);
	SET_UCON(port, ucon);
	SET_UFCON(port, ufcon);

	/* have to make certain that the UART's functions are enabled */
	if (port == &s3c2400_ports[0]) {
		/* RXD0|TXD0|RTS0|CTS0 */
		rPFCON |= (2|(2<<4)|(2<<8)|(2<<10));
		rPFUP |= 0x35;
	}
#ifndef CONFIG_TRAB
	else {
		/* RXD1|TXD1 */
		rPFCON |= ((2<<2)|(2<<6));
		rPFUP |= 0x0a;
		/* RTS1|CTS1 */
		/* NOTE: these block EINT3 and EINT4 !!!! */
		rPECON |= ((3<<6)|(3<<8));
		rPEUP |= 0x18;
	}
#endif
	/* wait for FIFOs to reset */
	while ((GET_UFCON(port) & (UFCON_RXFIFORESET|UFCON_TXFIFORESET))) ;

	/* unblock the IRQ */
SERDEBUG("+4")
	ENABLE_IRQ(port->irq);
SERDEBUG("+5")
	ENABLE_IRQ(port->unused[1]);

	return 0;
}

static void s3c2400_shutdown(struct uart_port *port, struct uart_info *info)
{
	/*
	 * Free the interrupt
	 */
	free_irq(port->irq, info);
	free_irq(port->unused[1], info);
#if 0 /* no interrupts on errors */
	if (port->unused[0] == 0)
		free_irq(port->unused[2], info);
#endif

	/*
	 * disable all interrupts, disable break, disable the port
	 */
	SET_UCON(port, 0);

	/* disable the fifos */
	SET_UFCON(port, 0);
}

static void s3c2400_change_speed(struct uart_port *port, u_int cflag, u_int iflag, u_int quot)
{
	u_int ulcon;

#if DEBUG
	printk("s3c2400_set_cflag(0x%x) called\n", cflag);
#endif

	/* byte size and parity */
	switch (cflag & CSIZE) {
	case CS5: ulcon = ULCON_5BITS; break;
	case CS6: ulcon = ULCON_6BITS; break;
	case CS7: ulcon = ULCON_7BITS; break;
	default:  ulcon = ULCON_8BITS; break; // CS8
	}
	if (cflag & CSTOPB)
		ulcon |= ULCON_2STOP;
	if (cflag & PARENB) {
		if (!(cflag & PARODD))
			ulcon |= ULCON_EVENPAR;
		else
			ulcon |= ULCON_ODDPAR;
	}

	port->read_status_mask = UERSTAT_OVERRUN;
	if (iflag & INPCK)
		port->read_status_mask |= UERSTAT_FRAERR | UERSTAT_PARERR;
	if (iflag & (BRKINT | PARMRK))
		port->read_status_mask |= UERSTAT_BREAK;

	/*
	 * Characters to ignore
	 */
	port->ignore_status_mask = 0;
	if (iflag & IGNPAR)
		port->ignore_status_mask |= UERSTAT_FRAERR | UERSTAT_PARERR;
	if (iflag & IGNBRK) {
		port->ignore_status_mask |= UERSTAT_BREAK;
		/*
		 * If we're ignoring parity and break indicators,
		 * ignore overruns too (for real raw support).
		 */
		if (iflag & IGNPAR)
			port->ignore_status_mask |= UERSTAT_OVERRUN;
	}

	/*
	 * Ignore all characters if CREAD is not set.
	 */
	if ((cflag & CREAD) == 0)
		port->ignore_status_mask |= UART_DUMMY_UER_RX;

	/* block the IRQs */
SERDEBUG("-6")
    	DISABLE_IRQ(port->irq);
SERDEBUG("-7")
	DISABLE_IRQ(port->unused[1]);

	if ((cflag & CRTSCTS) || !(cflag & CLOCAL))
		s3c2400_do_modem = 1;
	else
		s3c2400_do_modem = 0;

	/* Set baud rate */
	quot -= 1;
	SET_UBRDIV(port, quot);

	SET_ULCON(port, ulcon);

	/* unblock the IRQs */
SERDEBUG("+6")
	ENABLE_IRQ(port->irq);
SERDEBUG("+7")
	ENABLE_IRQ(port->unused[1]);
}

static const char *s3c2400_type(struct uart_port *port)
{
	return port->type == PORT_S3C2400 ? "S3C2400" : NULL;
}

/*
 * Release the memory region(s) being used by 'port'
 */
static void s3c2400_release_port(struct uart_port *port)
{
	return;
}

/*
 * Request the memory region(s) being used by 'port'
 */
static int s3c2400_request_port(struct uart_port *port)
{
	return 0;
}

/*
 * Configure/autoconfigure the port.
 */
static void s3c2400_config_port(struct uart_port *port, int flags)
{
	if (flags & UART_CONFIG_TYPE) {
		port->type = PORT_S3C2400;
		s3c2400_request_port(port);
	}
}

/*
 * verify the new serial_struct (for TIOCSSERIAL).
 */
static int s3c2400_verify_port(struct uart_port *port, struct serial_struct *ser)
{
	int ret = 0;
	if (ser->type != PORT_UNKNOWN && ser->type != PORT_S3C2400)
		ret = -EINVAL;
	if (ser->irq < 0 || ser->irq >= NR_IRQS)
		ret = -EINVAL;
	if (ser->baud_base < 9600)
		ret = -EINVAL;
	return ret;
}

static struct uart_ops s3c2400_pops = {
	tx_empty:	s3c2400_tx_empty,
	set_mctrl:	s3c2400_set_mctrl,
	get_mctrl:	s3c2400_get_mctrl,
	stop_tx:	s3c2400_stop_tx,
	start_tx:	s3c2400_start_tx,
	stop_rx:	s3c2400_stop_rx,
	enable_ms:	s3c2400_enable_ms,
	break_ctl:	s3c2400_break_ctl,
	startup:	s3c2400_startup,
	shutdown:	s3c2400_shutdown,
	change_speed:	s3c2400_change_speed,
	type:		s3c2400_type,
	release_port:	s3c2400_release_port,
	request_port:	s3c2400_request_port,
	config_port:	s3c2400_config_port,
	verify_port:	s3c2400_verify_port,
};

static struct uart_port s3c2400_ports[UART_NR] = {
	{
		membase:	(void *)SERBASE,
		mapbase:	io_v2p(SERBASE),
		iotype:		SERIAL_IO_MEM,
		irq:		IRQ_URXD0,
		uartclk:	0, /* filled in dynamically */
		fifosize:	16,
		unused:		{ 0, IRQ_UTXD0, IRQ_UERR01 }, /* xmit/error IRQ */
		type:		PORT_S3C2400,
		ops:		&s3c2400_pops,
		flags:		ASYNC_BOOT_AUTOCONF,
	},
#ifndef CONFIG_TRAB
	{
		membase:	(void *)(SERBASE + 0x4000),
		mapbase:	io_v2p(SERBASE + 0x4000),
		iotype:		SERIAL_IO_MEM,
		irq:		IRQ_URXD1,
		uartclk:	0, /* filled in dynamically */
		fifosize:	16,
		unused:		{ 1, IRQ_UTXD1, 0 }, /* xmit/error IRQ */
		type:		PORT_S3C2400,
		ops:		&s3c2400_pops,
		flags:		ASYNC_BOOT_AUTOCONF,
	}
#endif
};

#ifdef CONFIG_SERIAL_S3C2400_CONSOLE
#ifdef used_and_not_const_char_pointer
static int s3c2400_console_read(struct uart_port *port, char *s, u_int count)
{
	unsigned int status;
	int fifocnt;
	int c;
#if DEBUG
	printk("s3c2400_console_read() called\n");
#endif

	c = 0;
	/* block the IRQ */
SERDEBUG("-8")
    	DISABLE_IRQ(port->irq);
	/* FIFO not used */
	if ((GET_UFCON(port) & UFCON_FIFO) == 0)
	{
		while (c < count) {
			status = GET_UTRSTAT(port);
			if (status & UTRSTAT_RXDATA) {
				*s++ = GET_URXH(port);
				c++;
			} else
				break;
		}
		/* unblock the IRQ */
SERDEBUG("+8")
		ENABLE_IRQ(port->irq);
		return c;
	}

	fifocnt = GET_UFSTAT(port) & UFSTAT_RXCNTMASK;
	while (c < count && fifocnt) {
		*s++ = GET_URXH(port);
		c++;
		fifocnt--;
	}
	rSRCPND = BIT_UERR01;
	rINTPND = BIT_UERR01;
#ifndef INTMSK_OK
	rSRCPND = ((unsigned long)1 << port->irq);
	rINTPND = ((unsigned long)1 << port->irq);
#endif
	/* unblock the IRQ */
SERDEBUG("+8")
	ENABLE_IRQ(port->irq);
	// return the count
	return c;
}
#endif

static void s3c2400_console_write(struct console *co, const char *s, u_int count)
{
	struct uart_port *port = s3c2400_ports + co->index;
	unsigned int status, ucon;
	int i, nofifo;

	ucon = GET_UCON(port);
	if (!(ucon & UCON_TXINT))
		return;

	/* block the IRQ */
SERDEBUG("-9")
    	DISABLE_IRQ(port->unused[1]);

	nofifo = ((GET_UFCON(port) & UFCON_FIFO) == 0);
	/*
	 *	Now, do each character
	 */
	for (i = 0; i < count; i++) {
		if (nofifo) {
			do {
				status = GET_UTRSTAT(port);
			} while (!(status & UTRSTAT_TXBMPTY));
		} else {
			do {
				status = GET_UFSTAT(port);
			} while ((status & UFSTAT_TXCNTMASK));
		}
		SET_UTXH(port, s[i]);
		if (s[i] == '\n') {
			if (nofifo) {
				do {
					status = GET_UTRSTAT(port);
				} while (!(status & UTRSTAT_TXBMPTY));
			} else {
				do {
					status = GET_UFSTAT(port);
				} while ((status & UFSTAT_TXCNTMASK));
			}
			SET_UTXH(port, '\r');
		}
	}

	/*
	 *	Finally, wait for transmitter to become empty
	 *	and restore the IRQ
	 */
	if (nofifo) {
		do {
			status = GET_UTRSTAT(port);
		} while (!(status & UTRSTAT_TXBSMPTY));
	} else {
		do {
			status = GET_UFSTAT(port);
		} while ((status & UFSTAT_TXCNTMASK));
	}
	rSRCPND = ((unsigned long)1 << port->unused[1]);
	rINTPND = ((unsigned long)1 << port->unused[1]);
SERDEBUG("+9")
	ENABLE_IRQ(port->unused[1]);
}

static kdev_t s3c2400_console_device(struct console *co)
{
	return MKDEV(SERIAL_S3C2400_MAJOR, SERIAL_S3C2400_MINOR + co->index);
}

static int s3c2400_console_wait_key(struct console *co)
{
	struct uart_port *port = s3c2400_ports + co->index;
	unsigned int status;

	/* block the IRQ */
SERDEBUG("-10")
    	DISABLE_IRQ(port->irq);
	/* FIFO not used */
	if ((GET_UFCON(port) & UFCON_FIFO) == 0)
	{
		do {
			status = GET_UTRSTAT(port);
		} while (!(status & UTRSTAT_RXDATA));
		/* unblock the IRQ */
SERDEBUG("+10")
		ENABLE_IRQ(port->irq);
		return GET_URXH(port);
	}

	do {
		status = GET_UFSTAT(port) & UFSTAT_RXCNTMASK;
	} while (!status);
	rSRCPND = BIT_UERR01;
	rINTPND = BIT_UERR01;
#ifndef INTMSK_OK
	rSRCPND = ((unsigned long)1 << port->irq);
	rINTPND = ((unsigned long)1 << port->irq);
#endif
	/* unblock the IRQ */
SERDEBUG("+10")
	ENABLE_IRQ(port->irq);
	return GET_URXH(port);
}

static void __init
s3c2400_console_get_options(struct uart_port *port, int *baud, int *parity, int *bits)
{
	if (GET_UCON(port) & (UCON_RXINT|UCON_TXINT)) {
		u_int lcr_h, quot;
		lcr_h = GET_ULCON(port);

		*parity = 'n';
		if (lcr_h & (ULCON_ODDPAR|ULCON_EVENPAR)) {
			if (lcr_h & ULCON_EVENPAR)
				*parity = 'e';
			else
				*parity = 'o';
		}

		if ((lcr_h & 0x03) == ULCON_7BITS)
			*bits = 7;
		else
			*bits = 8;

		quot = GET_UBRDIV(port);
		/* this doesn't work exactly because we use integer */
		/* math to calculate quot which results in rounding */
		/* errors when we try to go from quot -> baud !! */
		*baud = port->uartclk / (16 * (quot + 1));
		if (*baud > 1200 && *baud < 2400)
			*baud = 1200;
		if (*baud > 2400 && *baud < 4800)
			*baud = 2400;
		if (*baud > 4800 && *baud < 9600)
			*baud = 4800;
		if (*baud > 9600 && *baud < 19200)
			*baud = 9600;
		if (*baud > 19200 && *baud < 38400)
			*baud = 19200;
		if (*baud > 38400 && *baud < 57600)
			*baud = 38400;
		if (*baud > 57600 && *baud < 115200)
			*baud = 57600;
		if (*baud > 115200 && *baud < 230400)
			*baud = 115200;
	}
}

static int __init s3c2400_console_setup(struct console *co, char *options)
{
	struct uart_port *port;
	int baud = 115200;
	int bits = 8;
	int parity = 'n';
	int flow = 'n';

#ifdef HANG_DEBUG
sbptr = serdebug;
#endif
	if (fclk == 0 || uartclk == 0)
		s3c2400_get_fclk();

	s3c2400_ports[0].uartclk = uartclk;
	s3c2400_ports[0].type = PORT_S3C2400;
#ifndef CONFIG_TRAB
	s3c2400_ports[1].uartclk = uartclk;
	s3c2400_ports[1].type = PORT_S3C2400;
#endif

	/*
	 * Check whether an invalid uart number has been specified, and
	 * if so, search for the first available port that does have
	 * console support.
	 */
	port = uart_get_console(s3c2400_ports, UART_NR, co);

	if (options)
		uart_parse_options(options, &baud, &parity, &bits, &flow);
	else
		s3c2400_console_get_options(port, &baud, &parity, &bits);

	return uart_set_options(port, co, baud, parity, bits, flow);
}

static struct console s3c2400_console = {
	name:		"ttyS",
	write:		s3c2400_console_write,
#ifdef used_and_not_const_char_pointer
	read:		s3c2400_console_read,
#endif
	device:		s3c2400_console_device,
	wait_key:	s3c2400_console_wait_key,
	setup:		s3c2400_console_setup,
	flags:		CON_PRINTBUFFER,
	index:		-1,
};

void __init s3c2400_console_init(void)
{
	register_console(&s3c2400_console);
}

#define S3C2400_CONSOLE	&s3c2400_console
#else
#define S3C2400_CONSOLE	NULL
#endif

static struct uart_driver s3c2400_reg = {
	owner:			THIS_MODULE,
	normal_major:		SERIAL_S3C2400_MAJOR,
#ifdef CONFIG_DEVFS_FS
	normal_name:		"ttyS%d",
	callout_name:		"cua%d",
#else
	normal_name:		"ttyS",
	callout_name:		"cua",
#endif
	normal_driver:		&normal,
	callout_major:		CALLOUT_S3C2400_MAJOR,
	callout_driver:		&callout,
	table:			s3c2400_table,
	termios:		s3c2400_termios,
	termios_locked:		s3c2400_termios_locked,
	minor:			SERIAL_S3C2400_MINOR,
	nr:			UART_NR,
	port:			s3c2400_ports,
	cons:			S3C2400_CONSOLE,
};

static int __init s3c2400_init(void)
{
	return uart_register_driver(&s3c2400_reg);
}

static void __exit s3c2400_exit(void)
{
	uart_unregister_driver(&s3c2400_reg);
}

module_init(s3c2400_init);
module_exit(s3c2400_exit);

EXPORT_NO_SYMBOLS;

MODULE_AUTHOR("Gary Jennejohn");
MODULE_DESCRIPTION("ARM S3C2400 serial port driver");
MODULE_LICENSE("GPL");
