/*
* linux/drivers/char/serial_s3c44b0.c
*
* Driver for S3C44B0 serial ports
*
* Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.
*
* 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: s3c44b0.c,v 1.9.2.1 2001/11/27 17:35:39 rmk Exp $
*
* This is a generic driver for ARM S3C44B0-type serial ports. They
* have a lot of 16550-like features, but are not register compatable.
* Note that although they do have CTS, DCD and DSR inputs, they do
* not have an RI input, nor do they have DTR or RTS outputs. If
* required, these have to be supplied via some other means (eg, GPIO)
* and hooked into this driver.
*/
#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_S3C44B0_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/serial_core.h>
#include <asm/hardware.h>
#define UART_NR 2
#define SERIAL_S3C44B0_MAJOR 4
#define SERIAL_S3C44B0_MINOR 64
#define SERIAL_S3C44B0_NR UART_NR
#define CALLOUT_S3C44B0_NAME "cua"
#define CALLOUT_S3C44B0_MAJOR 5
#define CALLOUT_S3C44B0_MINOR 64
#define CALLOUT_S3C44B0_NR UART_NR
static struct tty_driver normal, callout;
static struct tty_struct *s3c44b0_table[UART_NR];
static struct termios *s3c44b0_termios[UART_NR], *s3c44b0_termios_locked[UART_NR];
#ifdef SUPPORT_SYSRQ
static struct console s3c44b0_console;
#endif
/*
* Access macros for the S3C44B0 UARTs
*/
#define UFSTAT ((port)->iobase + (void *)&UFSTAT0 - (void *)&ULCON0)
#define UERSTAT ((port)->iobase + (void *)&UERSTAT0 - (void *)&ULCON0)
#define UMSTAT ((port)->iobase + (void *)&UMSTAT0 - (void *)&ULCON0)
#define URXH ((port)->iobase + (void *)&URXH0 - (void *)&ULCON0)
#define UTXH ((port)->iobase + (void *)&UTXH0 - (void *)&ULCON0)
#define UMCON ((port)->iobase + (void *)&UMCON0 - (void *)&ULCON0)
#define UCON ((port)->iobase + (void *)&UCON0 - (void *)&ULCON0)
#define ULCON ((port)->iobase + (void *)&ULCON0 - (void *)&ULCON0)
#define UFCON ((port)->iobase + (void *)&UFCON0 - (void *)&ULCON0)
#define UBRDIV ((port)->iobase + (void *)&UBRDIV0 - (void *)&ULCON0)
#define RFCNT_MASK 0x0f
#define TFCNT_MASK 0xf0
#define TFULL 0x200
#define UART_OE 1
#define UART_PE 2
#define UART_FE 3
#define UART_BE 4
#define UART_ANY_ERR (UART_OE|UART_PE|UART_FE)
#define UART_RTS 1
#define UART_CTS 1
#define UCON_BRK 0x10
#define ULCON_CS5 0
#define ULCON_CS6 1
#define ULCON_CS7 2
#define ULCON_CS8 3
#define ULCON_STOPB 4
#define ULCON_PARITY 0x20
#define ULCON_PEVEN 0x08
static void s3c44b0uart_stop_tx(struct uart_port *port, u_int from_tty)
{
outl(0x85, UCON);
INT_DISABLE(port->irq);
}
static void s3c44b0uart_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty)
{
if(nonempty){
outl(0x285, UCON);
INT_ENABLE(port->irq);
}
}
static void s3c44b0uart_stop_rx(struct uart_port *port)
{
INT_DISABLE(port->irq+4);
}
static void s3c44b0uart_enable_ms(struct uart_port *port)
{
}
static void
s3c44b0uart_rx_int(int irq, void *dev_id, struct pt_regs *regs)
{
struct uart_info *info = dev_id;
struct tty_struct *tty = info->tty;
unsigned int status, uer, ch, flg, ignored = 0;
struct uart_port *port = info->port;
status = inl(UFSTAT);
while(status&RFCNT_MASK){
uer = inl(UERSTAT);
ch = inb(URXH);
if(tty->flip.count >= TTY_FLIPBUF_SIZE)
goto ignore_char;
port->icount.rx++;
flg = TTY_NORMAL;
if(uer&UART_ANY_ERR)
goto handle_error;
if(uart_handle_sysrq_char(info, ch, regs))
goto ignore_char;
error_return:
*tty->flip.flag_buf_ptr++ = flg;
*tty->flip.char_buf_ptr++ = ch;
tty->flip.count++;
ignore_char:
status = inl(UFSTAT);
}
out:
tty_flip_buffer_push(tty);
return;
handle_error:
if(uer&UART_PE)
port->icount.parity++;
else if(uer&UART_FE)
port->icount.frame++;
if(uer&UART_OE)
port->icount.overrun++;
if(uer&port->ignore_status_mask){
if( ++ignored > 100)
goto out;
goto ignore_char;
}
uer &= port->read_status_mask;
if(uer&UART_PE)
flg = TTY_PARITY;
else if(uer&UART_FE)
flg = TTY_FRAME;
if (uer&UART_OE) {
/*
* CHECK: does overrun affect the current character?
* ASSUMPTION: it does not.
*/
*tty->flip.flag_buf_ptr++ = flg;
*tty->flip.char_buf_ptr++ = ch;
tty->flip.count++;
if (tty->flip.count >= TTY_FLIPBUF_SIZE)
goto ignore_char;
ch = 0;
flg = TTY_OVERRUN;
}
#ifdef SUPPORT_SYSRQ
info->sysrq = 0;
#endif
goto error_return;
}
static void s3c44b0uart_tx_int(int irq, void *dev_id, struct pt_regs *regs)
{
struct uart_info *info = dev_id;
struct uart_port *port = info->port;
if (port->x_char) {
outb(port->x_char, UTXH);
port->icount.tx++;
port->x_char = 0;
return;
}
if (info->xmit.head == info->xmit.tail
|| info->tty->stopped
|| info->tty->hw_stopped) {
s3c44b0uart_stop_tx(port, 0);
return;
}
while(!(inl(UFSTAT)&TFULL)){
outb(info->xmit.buf[info->xmit.tail], UTXH);
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)
s3c44b0uart_stop_tx(info->port, 0);
}
static u_int s3c44b0uart_tx_empty(struct uart_port *port)
{
return (inl(UFSTAT)&TFCNT_MASK) ? 0 : TIOCSER_TEMT;
}
static u_int s3c44b0uart_get_mctrl(struct uart_port *port)
{
return (inl(UMSTAT)&UART_CTS) ? TIOCM_CTS : 0;
}
static void s3c44b0uart_set_mctrl(struct uart_port *port, u_int mctrl)
{
unsigned int status;
status = inl(UMCON);
if(mctrl & TIOCM_RTS)
status |= UART_RTS;
else
status &= ~UART_RTS;
outl(status, UMCON);
}
static void s3c44b0uart_break_ctl(struct uart_port *port, int break_state)
{
int status;
status = inl(UCON);
if(break_state)
status |= UCON_BRK;
else
status &= ~UCON_BRK;
outl(status, UCON);
}
static int s3c44b0uart_startup(struct uart_port *port, struct uart_info *info)
{
int retv;
retv = request_irq(port->irq, s3c44b0uart_tx_int, 0,
"s3c44b0_uart_tx", info);
if(retv)
return retv;
retv = request_irq(port->irq+4, s3c44b0uart_rx_int, 0,
"s3c44b0_uart_rx", info);
if(retv){
free_irq(port->irq, info);
return retv;
}
outl(0x17, UFCON);
outl(0x85, UCON);
INT_ENABLE(port->irq+4);
return 0;
}
static void s3c44b0uart_shutdown(struct uart_port *port, struct uart_info *info)
{
free_irq(port->irq, info);
free_irq(port->irq+4, info);
outl(0x00, UCON);
}
static void s3c44b0uart_change_speed(struct uart_port *port, u_int cfla