33f0f88f1c
The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
821 lines
19 KiB
C
821 lines
19 KiB
C
/*
|
|
* dz.c: Serial port driver for DECStations equiped
|
|
* with the DZ chipset.
|
|
*
|
|
* Copyright (C) 1998 Olivier A. D. Lebaillif
|
|
*
|
|
* Email: olivier.lebaillif@ifrsys.com
|
|
*
|
|
* [31-AUG-98] triemer
|
|
* Changed IRQ to use Harald's dec internals interrupts.h
|
|
* removed base_addr code - moving address assignment to setup.c
|
|
* Changed name of dz_init to rs_init to be consistent with tc code
|
|
* [13-NOV-98] triemer fixed code to receive characters
|
|
* after patches by harald to irq code.
|
|
* [09-JAN-99] triemer minor fix for schedule - due to removal of timeout
|
|
* field from "current" - somewhere between 2.1.121 and 2.1.131
|
|
Qua Jun 27 15:02:26 BRT 2001
|
|
* [27-JUN-2001] Arnaldo Carvalho de Melo <acme@conectiva.com.br> - cleanups
|
|
*
|
|
* Parts (C) 1999 David Airlie, airlied@linux.ie
|
|
* [07-SEP-99] Bugfixes
|
|
*
|
|
* [06-Jan-2002] Russell King <rmk@arm.linux.org.uk>
|
|
* Converted to new serial core
|
|
*/
|
|
|
|
#undef DEBUG_DZ
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/init.h>
|
|
#include <linux/console.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/serial_core.h>
|
|
#include <linux/serial.h>
|
|
|
|
#include <asm/bootinfo.h>
|
|
#include <asm/dec/interrupts.h>
|
|
#include <asm/dec/kn01.h>
|
|
#include <asm/dec/kn02.h>
|
|
#include <asm/dec/machtype.h>
|
|
#include <asm/dec/prom.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/system.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#define CONSOLE_LINE (3) /* for definition of struct console */
|
|
|
|
#include "dz.h"
|
|
|
|
#define DZ_INTR_DEBUG 1
|
|
|
|
static char *dz_name = "DECstation DZ serial driver version ";
|
|
static char *dz_version = "1.02";
|
|
|
|
struct dz_port {
|
|
struct uart_port port;
|
|
unsigned int cflag;
|
|
};
|
|
|
|
static struct dz_port dz_ports[DZ_NB_PORT];
|
|
|
|
#ifdef DEBUG_DZ
|
|
/*
|
|
* debugging code to send out chars via prom
|
|
*/
|
|
static void debug_console(const char *s, int count)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (*s == 10)
|
|
prom_printf("%c", 13);
|
|
prom_printf("%c", *s++);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* dz_in () and dz_out ()
|
|
*
|
|
* These routines are used to access the registers of the DZ
|
|
* chip, hiding relocation differences between implementation.
|
|
* ------------------------------------------------------------
|
|
*/
|
|
|
|
static inline unsigned short dz_in(struct dz_port *dport, unsigned offset)
|
|
{
|
|
volatile unsigned short *addr =
|
|
(volatile unsigned short *) (dport->port.membase + offset);
|
|
return *addr;
|
|
}
|
|
|
|
static inline void dz_out(struct dz_port *dport, unsigned offset,
|
|
unsigned short value)
|
|
{
|
|
volatile unsigned short *addr =
|
|
(volatile unsigned short *) (dport->port.membase + offset);
|
|
*addr = value;
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* rs_stop () and rs_start ()
|
|
*
|
|
* These routines are called before setting or resetting
|
|
* tty->stopped. They enable or disable transmitter interrupts,
|
|
* as necessary.
|
|
* ------------------------------------------------------------
|
|
*/
|
|
|
|
static void dz_stop_tx(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned short tmp, mask = 1 << dport->port.line;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
|
|
tmp &= ~mask; /* clear the TX flag */
|
|
dz_out(dport, DZ_TCR, tmp);
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
}
|
|
|
|
static void dz_start_tx(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned short tmp, mask = 1 << dport->port.line;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
|
|
tmp |= mask; /* set the TX flag */
|
|
dz_out(dport, DZ_TCR, tmp);
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
}
|
|
|
|
static void dz_stop_rx(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
dport->cflag &= ~DZ_CREAD;
|
|
dz_out(dport, DZ_LPR, dport->cflag);
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
}
|
|
|
|
static void dz_enable_ms(struct uart_port *port)
|
|
{
|
|
/* nothing to do */
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* Here starts the interrupt handling routines. All of the
|
|
* following subroutines are declared as inline and are folded
|
|
* into dz_interrupt. They were separated out for readability's
|
|
* sake.
|
|
*
|
|
* Note: rs_interrupt() is a "fast" interrupt, which means that it
|
|
* runs with interrupts turned off. People who may want to modify
|
|
* rs_interrupt() should try to keep the interrupt handler as fast as
|
|
* possible. After you are done making modifications, it is not a bad
|
|
* idea to do:
|
|
*
|
|
* make drivers/serial/dz.s
|
|
*
|
|
* and look at the resulting assemble code in dz.s.
|
|
*
|
|
* ------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* receive_char ()
|
|
*
|
|
* This routine deals with inputs from any lines.
|
|
* ------------------------------------------------------------
|
|
*/
|
|
static inline void dz_receive_chars(struct dz_port *dport)
|
|
{
|
|
struct tty_struct *tty = NULL;
|
|
struct uart_icount *icount;
|
|
int ignore = 0;
|
|
unsigned short status, tmp;
|
|
unsigned char ch, flag;
|
|
|
|
/* this code is going to be a problem...
|
|
the call to tty_flip_buffer is going to need
|
|
to be rethought...
|
|
*/
|
|
do {
|
|
status = dz_in(dport, DZ_RBUF);
|
|
|
|
/* punt so we don't get duplicate characters */
|
|
if (!(status & DZ_DVAL))
|
|
goto ignore_char;
|
|
|
|
|
|
ch = UCHAR(status); /* grab the char */
|
|
flag = TTY_NORMAL;
|
|
|
|
#if 0
|
|
if (info->is_console) {
|
|
if (ch == 0)
|
|
return; /* it's a break ... */
|
|
}
|
|
#endif
|
|
|
|
tty = dport->port.info->tty;/* now tty points to the proper dev */
|
|
icount = &dport->port.icount;
|
|
|
|
if (!tty)
|
|
break;
|
|
|
|
icount->rx++;
|
|
|
|
/* keep track of the statistics */
|
|
if (status & (DZ_OERR | DZ_FERR | DZ_PERR)) {
|
|
if (status & DZ_PERR) /* parity error */
|
|
icount->parity++;
|
|
else if (status & DZ_FERR) /* frame error */
|
|
icount->frame++;
|
|
if (status & DZ_OERR) /* overrun error */
|
|
icount->overrun++;
|
|
|
|
/* check to see if we should ignore the character
|
|
and mask off conditions that should be ignored
|
|
*/
|
|
|
|
if (status & dport->port.ignore_status_mask) {
|
|
if (++ignore > 100)
|
|
break;
|
|
goto ignore_char;
|
|
}
|
|
/* mask off the error conditions we want to ignore */
|
|
tmp = status & dport->port.read_status_mask;
|
|
|
|
if (tmp & DZ_PERR) {
|
|
flag = TTY_PARITY;
|
|
#ifdef DEBUG_DZ
|
|
debug_console("PERR\n", 5);
|
|
#endif
|
|
} else if (tmp & DZ_FERR) {
|
|
flag = TTY_FRAME;
|
|
#ifdef DEBUG_DZ
|
|
debug_console("FERR\n", 5);
|
|
#endif
|
|
}
|
|
if (tmp & DZ_OERR) {
|
|
#ifdef DEBUG_DZ
|
|
debug_console("OERR\n", 5);
|
|
#endif
|
|
tty_insert_flip_char(tty, ch, flag);
|
|
ch = 0;
|
|
flag = TTY_OVERRUN;
|
|
}
|
|
}
|
|
tty_insert_flip_char(tty, ch, flag);
|
|
ignore_char:
|
|
} while (status & DZ_DVAL);
|
|
|
|
if (tty)
|
|
tty_flip_buffer_push(tty);
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* transmit_char ()
|
|
*
|
|
* This routine deals with outputs to any lines.
|
|
* ------------------------------------------------------------
|
|
*/
|
|
static inline void dz_transmit_chars(struct dz_port *dport)
|
|
{
|
|
struct circ_buf *xmit = &dport->port.info->xmit;
|
|
unsigned char tmp;
|
|
|
|
if (dport->port.x_char) { /* XON/XOFF chars */
|
|
dz_out(dport, DZ_TDR, dport->port.x_char);
|
|
dport->port.icount.tx++;
|
|
dport->port.x_char = 0;
|
|
return;
|
|
}
|
|
/* if nothing to do or stopped or hardware stopped */
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(&dport->port)) {
|
|
dz_stop_tx(&dport->port);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* if something to do ... (rember the dz has no output fifo so we go
|
|
* one char at a time :-<
|
|
*/
|
|
tmp = xmit->buf[xmit->tail];
|
|
xmit->tail = (xmit->tail + 1) & (DZ_XMIT_SIZE - 1);
|
|
dz_out(dport, DZ_TDR, tmp);
|
|
dport->port.icount.tx++;
|
|
|
|
if (uart_circ_chars_pending(xmit) < DZ_WAKEUP_CHARS)
|
|
uart_write_wakeup(&dport->port);
|
|
|
|
/* Are we done */
|
|
if (uart_circ_empty(xmit))
|
|
dz_stop_tx(&dport->port);
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* check_modem_status ()
|
|
*
|
|
* Only valid for the MODEM line duh !
|
|
* ------------------------------------------------------------
|
|
*/
|
|
static inline void check_modem_status(struct dz_port *dport)
|
|
{
|
|
unsigned short status;
|
|
|
|
/* if not ne modem line just return */
|
|
if (dport->port.line != DZ_MODEM)
|
|
return;
|
|
|
|
status = dz_in(dport, DZ_MSR);
|
|
|
|
/* it's easy, since DSR2 is the only bit in the register */
|
|
if (status)
|
|
dport->port.icount.dsr++;
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* dz_interrupt ()
|
|
*
|
|
* this is the main interrupt routine for the DZ chip.
|
|
* It deals with the multiple ports.
|
|
* ------------------------------------------------------------
|
|
*/
|
|
static irqreturn_t dz_interrupt(int irq, void *dev, struct pt_regs *regs)
|
|
{
|
|
struct dz_port *dport;
|
|
unsigned short status;
|
|
|
|
/* get the reason why we just got an irq */
|
|
status = dz_in((struct dz_port *)dev, DZ_CSR);
|
|
dport = &dz_ports[LINE(status)];
|
|
|
|
if (status & DZ_RDONE)
|
|
dz_receive_chars(dport);
|
|
|
|
if (status & DZ_TRDY)
|
|
dz_transmit_chars(dport);
|
|
|
|
/* FIXME: what about check modem status??? --rmk */
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* -------------------------------------------------------------------
|
|
* Here ends the DZ interrupt routines.
|
|
* -------------------------------------------------------------------
|
|
*/
|
|
|
|
static unsigned int dz_get_mctrl(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned int mctrl = TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
|
|
|
|
if (dport->port.line == DZ_MODEM) {
|
|
/*
|
|
* CHECKME: This is a guess from the other code... --rmk
|
|
*/
|
|
if (dz_in(dport, DZ_MSR) & DZ_MODEM_DSR)
|
|
mctrl &= ~TIOCM_DSR;
|
|
}
|
|
|
|
return mctrl;
|
|
}
|
|
|
|
static void dz_set_mctrl(struct uart_port *uport, unsigned int mctrl)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned short tmp;
|
|
|
|
if (dport->port.line == DZ_MODEM) {
|
|
tmp = dz_in(dport, DZ_TCR);
|
|
if (mctrl & TIOCM_DTR)
|
|
tmp &= ~DZ_MODEM_DTR;
|
|
else
|
|
tmp |= DZ_MODEM_DTR;
|
|
dz_out(dport, DZ_TCR, tmp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* -------------------------------------------------------------------
|
|
* startup ()
|
|
*
|
|
* various initialization tasks
|
|
* -------------------------------------------------------------------
|
|
*/
|
|
static int dz_startup(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned long flags;
|
|
unsigned short tmp;
|
|
|
|
/* The dz lines for the mouse/keyboard must be
|
|
* opened using their respective drivers.
|
|
*/
|
|
if ((dport->port.line == DZ_KEYBOARD) ||
|
|
(dport->port.line == DZ_MOUSE))
|
|
return -ENODEV;
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
|
|
/* enable the interrupt and the scanning */
|
|
tmp = dz_in(dport, DZ_CSR);
|
|
tmp |= DZ_RIE | DZ_TIE | DZ_MSE;
|
|
dz_out(dport, DZ_CSR, tmp);
|
|
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* -------------------------------------------------------------------
|
|
* shutdown ()
|
|
*
|
|
* This routine will shutdown a serial port; interrupts are disabled, and
|
|
* DTR is dropped if the hangup on close termio flag is on.
|
|
* -------------------------------------------------------------------
|
|
*/
|
|
static void dz_shutdown(struct uart_port *uport)
|
|
{
|
|
dz_stop_tx(uport);
|
|
}
|
|
|
|
/*
|
|
* get_lsr_info - get line status register info
|
|
*
|
|
* Purpose: Let user call ioctl() to get info when the UART physically
|
|
* is emptied. On bus types like RS485, the transmitter must
|
|
* release the bus after transmitting. This must be done when
|
|
* the transmit shift register is empty, not be done when the
|
|
* transmit holding register is empty. This functionality
|
|
* allows an RS485 driver to be written in user space.
|
|
*/
|
|
static unsigned int dz_tx_empty(struct uart_port *uport)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned short status = dz_in(dport, DZ_LPR);
|
|
|
|
/* FIXME: this appears to be obviously broken --rmk. */
|
|
return status ? TIOCSER_TEMT : 0;
|
|
}
|
|
|
|
static void dz_break_ctl(struct uart_port *uport, int break_state)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned long flags;
|
|
unsigned short tmp, mask = 1 << uport->line;
|
|
|
|
spin_lock_irqsave(&uport->lock, flags);
|
|
tmp = dz_in(dport, DZ_TCR);
|
|
if (break_state)
|
|
tmp |= mask;
|
|
else
|
|
tmp &= ~mask;
|
|
dz_out(dport, DZ_TCR, tmp);
|
|
spin_unlock_irqrestore(&uport->lock, flags);
|
|
}
|
|
|
|
static void dz_set_termios(struct uart_port *uport, struct termios *termios,
|
|
struct termios *old_termios)
|
|
{
|
|
struct dz_port *dport = (struct dz_port *)uport;
|
|
unsigned long flags;
|
|
unsigned int cflag, baud;
|
|
|
|
cflag = dport->port.line;
|
|
|
|
switch (termios->c_cflag & CSIZE) {
|
|
case CS5:
|
|
cflag |= DZ_CS5;
|
|
break;
|
|
case CS6:
|
|
cflag |= DZ_CS6;
|
|
break;
|
|
case CS7:
|
|
cflag |= DZ_CS7;
|
|
break;
|
|
case CS8:
|
|
default:
|
|
cflag |= DZ_CS8;
|
|
}
|
|
|
|
if (termios->c_cflag & CSTOPB)
|
|
cflag |= DZ_CSTOPB;
|
|
if (termios->c_cflag & PARENB)
|
|
cflag |= DZ_PARENB;
|
|
if (termios->c_cflag & PARODD)
|
|
cflag |= DZ_PARODD;
|
|
|
|
baud = uart_get_baud_rate(uport, termios, old_termios, 50, 9600);
|
|
switch (baud) {
|
|
case 50:
|
|
cflag |= DZ_B50;
|
|
break;
|
|
case 75:
|
|
cflag |= DZ_B75;
|
|
break;
|
|
case 110:
|
|
cflag |= DZ_B110;
|
|
break;
|
|
case 134:
|
|
cflag |= DZ_B134;
|
|
break;
|
|
case 150:
|
|
cflag |= DZ_B150;
|
|
break;
|
|
case 300:
|
|
cflag |= DZ_B300;
|
|
break;
|
|
case 600:
|
|
cflag |= DZ_B600;
|
|
break;
|
|
case 1200:
|
|
cflag |= DZ_B1200;
|
|
break;
|
|
case 1800:
|
|
cflag |= DZ_B1800;
|
|
break;
|
|
case 2000:
|
|
cflag |= DZ_B2000;
|
|
break;
|
|
case 2400:
|
|
cflag |= DZ_B2400;
|
|
break;
|
|
case 3600:
|
|
cflag |= DZ_B3600;
|
|
break;
|
|
case 4800:
|
|
cflag |= DZ_B4800;
|
|
break;
|
|
case 7200:
|
|
cflag |= DZ_B7200;
|
|
break;
|
|
case 9600:
|
|
default:
|
|
cflag |= DZ_B9600;
|
|
}
|
|
|
|
if (termios->c_cflag & CREAD)
|
|
cflag |= DZ_RXENAB;
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
|
|
dz_out(dport, DZ_LPR, cflag);
|
|
dport->cflag = cflag;
|
|
|
|
/* setup accept flag */
|
|
dport->port.read_status_mask = DZ_OERR;
|
|
if (termios->c_iflag & INPCK)
|
|
dport->port.read_status_mask |= DZ_FERR | DZ_PERR;
|
|
|
|
/* characters to ignore */
|
|
uport->ignore_status_mask = 0;
|
|
if (termios->c_iflag & IGNPAR)
|
|
dport->port.ignore_status_mask |= DZ_FERR | DZ_PERR;
|
|
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
}
|
|
|
|
static const char *dz_type(struct uart_port *port)
|
|
{
|
|
return "DZ";
|
|
}
|
|
|
|
static void dz_release_port(struct uart_port *port)
|
|
{
|
|
/* nothing to do */
|
|
}
|
|
|
|
static int dz_request_port(struct uart_port *port)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void dz_config_port(struct uart_port *port, int flags)
|
|
{
|
|
if (flags & UART_CONFIG_TYPE)
|
|
port->type = PORT_DZ;
|
|
}
|
|
|
|
/*
|
|
* verify the new serial_struct (for TIOCSSERIAL).
|
|
*/
|
|
static int dz_verify_port(struct uart_port *port, struct serial_struct *ser)
|
|
{
|
|
int ret = 0;
|
|
if (ser->type != PORT_UNKNOWN && ser->type != PORT_DZ)
|
|
ret = -EINVAL;
|
|
if (ser->irq != port->irq)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
static struct uart_ops dz_ops = {
|
|
.tx_empty = dz_tx_empty,
|
|
.get_mctrl = dz_get_mctrl,
|
|
.set_mctrl = dz_set_mctrl,
|
|
.stop_tx = dz_stop_tx,
|
|
.start_tx = dz_start_tx,
|
|
.stop_rx = dz_stop_rx,
|
|
.enable_ms = dz_enable_ms,
|
|
.break_ctl = dz_break_ctl,
|
|
.startup = dz_startup,
|
|
.shutdown = dz_shutdown,
|
|
.set_termios = dz_set_termios,
|
|
.type = dz_type,
|
|
.release_port = dz_release_port,
|
|
.request_port = dz_request_port,
|
|
.config_port = dz_config_port,
|
|
.verify_port = dz_verify_port,
|
|
};
|
|
|
|
static void __init dz_init_ports(void)
|
|
{
|
|
static int first = 1;
|
|
struct dz_port *dport;
|
|
unsigned long base;
|
|
int i;
|
|
|
|
if (!first)
|
|
return;
|
|
first = 0;
|
|
|
|
if (mips_machtype == MACH_DS23100 ||
|
|
mips_machtype == MACH_DS5100)
|
|
base = CKSEG1ADDR(KN01_SLOT_BASE + KN01_DZ11);
|
|
else
|
|
base = CKSEG1ADDR(KN02_SLOT_BASE + KN02_DZ11);
|
|
|
|
for (i = 0, dport = dz_ports; i < DZ_NB_PORT; i++, dport++) {
|
|
spin_lock_init(&dport->port.lock);
|
|
dport->port.membase = (char *) base;
|
|
dport->port.iotype = SERIAL_IO_PORT;
|
|
dport->port.irq = dec_interrupt[DEC_IRQ_DZ11];
|
|
dport->port.line = i;
|
|
dport->port.fifosize = 1;
|
|
dport->port.ops = &dz_ops;
|
|
dport->port.flags = UPF_BOOT_AUTOCONF;
|
|
}
|
|
}
|
|
|
|
static void dz_reset(struct dz_port *dport)
|
|
{
|
|
dz_out(dport, DZ_CSR, DZ_CLR);
|
|
|
|
while (dz_in(dport, DZ_CSR) & DZ_CLR);
|
|
/* FIXME: cpu_relax? */
|
|
|
|
iob();
|
|
|
|
/* enable scanning */
|
|
dz_out(dport, DZ_CSR, DZ_MSE);
|
|
}
|
|
|
|
#ifdef CONFIG_SERIAL_DZ_CONSOLE
|
|
static void dz_console_put_char(struct dz_port *dport, unsigned char ch)
|
|
{
|
|
unsigned long flags;
|
|
int loops = 2500;
|
|
unsigned short tmp = ch;
|
|
/* this code sends stuff out to serial device - spinning its
|
|
wheels and waiting. */
|
|
|
|
spin_lock_irqsave(&dport->port.lock, flags);
|
|
|
|
/* spin our wheels */
|
|
while (((dz_in(dport, DZ_CSR) & DZ_TRDY) != DZ_TRDY) && loops--)
|
|
/* FIXME: cpu_relax, udelay? --rmk */
|
|
;
|
|
|
|
/* Actually transmit the character. */
|
|
dz_out(dport, DZ_TDR, tmp);
|
|
|
|
spin_unlock_irqrestore(&dport->port.lock, flags);
|
|
}
|
|
/*
|
|
* -------------------------------------------------------------------
|
|
* dz_console_print ()
|
|
*
|
|
* dz_console_print is registered for printk.
|
|
* The console must be locked when we get here.
|
|
* -------------------------------------------------------------------
|
|
*/
|
|
static void dz_console_print(struct console *cons,
|
|
const char *str,
|
|
unsigned int count)
|
|
{
|
|
struct dz_port *dport = &dz_ports[CONSOLE_LINE];
|
|
#ifdef DEBUG_DZ
|
|
prom_printf((char *) str);
|
|
#endif
|
|
while (count--) {
|
|
if (*str == '\n')
|
|
dz_console_put_char(dport, '\r');
|
|
dz_console_put_char(dport, *str++);
|
|
}
|
|
}
|
|
|
|
static int __init dz_console_setup(struct console *co, char *options)
|
|
{
|
|
struct dz_port *dport = &dz_ports[CONSOLE_LINE];
|
|
int baud = 9600;
|
|
int bits = 8;
|
|
int parity = 'n';
|
|
int flow = 'n';
|
|
int ret;
|
|
unsigned short mask, tmp;
|
|
|
|
if (options)
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
dz_reset(dport);
|
|
|
|
ret = uart_set_options(&dport->port, co, baud, parity, bits, flow);
|
|
if (ret == 0) {
|
|
mask = 1 << dport->port.line;
|
|
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
|
|
if (!(tmp & mask)) {
|
|
tmp |= mask; /* set the TX flag */
|
|
dz_out(dport, DZ_TCR, tmp);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct console dz_sercons =
|
|
{
|
|
.name = "ttyS",
|
|
.write = dz_console_print,
|
|
.device = uart_console_device,
|
|
.setup = dz_console_setup,
|
|
.flags = CON_CONSDEV | CON_PRINTBUFFER,
|
|
.index = CONSOLE_LINE,
|
|
};
|
|
|
|
void __init dz_serial_console_init(void)
|
|
{
|
|
dz_init_ports();
|
|
|
|
register_console(&dz_sercons);
|
|
}
|
|
|
|
#define SERIAL_DZ_CONSOLE &dz_sercons
|
|
#else
|
|
#define SERIAL_DZ_CONSOLE NULL
|
|
#endif /* CONFIG_SERIAL_DZ_CONSOLE */
|
|
|
|
static struct uart_driver dz_reg = {
|
|
.owner = THIS_MODULE,
|
|
.driver_name = "serial",
|
|
#ifdef CONFIG_DEVFS
|
|
.dev_name = "tts/%d",
|
|
#else
|
|
.dev_name = "ttyS%d",
|
|
#endif
|
|
.major = TTY_MAJOR,
|
|
.minor = 64,
|
|
.nr = DZ_NB_PORT,
|
|
.cons = SERIAL_DZ_CONSOLE,
|
|
};
|
|
|
|
int __init dz_init(void)
|
|
{
|
|
unsigned long flags;
|
|
int ret, i;
|
|
|
|
printk("%s%s\n", dz_name, dz_version);
|
|
|
|
dz_init_ports();
|
|
|
|
save_flags(flags);
|
|
cli();
|
|
|
|
#ifndef CONFIG_SERIAL_DZ_CONSOLE
|
|
/* reset the chip */
|
|
dz_reset(&dz_ports[0]);
|
|
#endif
|
|
|
|
/* order matters here... the trick is that flags
|
|
is updated... in request_irq - to immediatedly obliterate
|
|
it is unwise. */
|
|
restore_flags(flags);
|
|
|
|
if (request_irq(dz_ports[0].port.irq, dz_interrupt,
|
|
SA_INTERRUPT, "DZ", &dz_ports[0]))
|
|
panic("Unable to register DZ interrupt");
|
|
|
|
ret = uart_register_driver(&dz_reg);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < DZ_NB_PORT; i++)
|
|
uart_add_one_port(&dz_reg, &dz_ports[i].port);
|
|
|
|
return ret;
|
|
}
|
|
|
|
MODULE_DESCRIPTION("DECstation DZ serial driver");
|
|
MODULE_LICENSE("GPL");
|