#define SI_SERIAL_VERSION_STRING "0.5.7"

/*
 *  For use with Linux V1.1.35 and above.
 *
 *  drivers/char/si_serial.c
 *
 *  Copyright (C) May 1994  Simon Allen, Coventry, UK.
 *  E-Mail : simonallen@cix.compulink.co.uk
 *
 *
 *  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, 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.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *  Specialix SI-Host driver.
 *  -------------------------
 *
 *  This is the Linux device driver for the Specialix SI-Host
 *  modular intelligent serial port adapter.  The SI-Host contains
 *  a Z280 CPU, memory and interface circuitry, and the UARTS are
 *  contained in external Terminal Adapters (TA's) - hence modular.
 *  Each TA supports 4 or 8 serial lines giving a theoretical total
 *  of 32 on 4 TA's.  Only one 8 port TA  tested so far.
 *
 *  This driver does not contain any code that is proprietary to
 *  Specialix or anyone else.  It does contain code from other parts
 *  of the Linux Kernel, in particular serial.c for which thanks go to
 *  (among others) Linus and Ted.
 *
 *
 *  *** WARNING *** *** WARNING *** *** WARNING *** *** WARNING ***  
 *
 *  Very Alpha code. Use at your own risk.  This is likely to crash.
 *  It may possibly also contain security holes. Etc, etc...
 *  Some basic Unix tty functions aren't even implemented at all yet.
 *
 *  Worse than that, because the code is based on serial.c there is a lot
 *  of redundant code that is commented out and some that isn't.  Also,
 *  the comments themselves may be very misleading (Including this one!!).
 *
 *  If you see any code here you don't understand it's likely to be
 *  my fault not yours. It's probably just plain wrong... ;-)
 *
 *  If you use this code as it is at the moment you will likely fall into
 *  a pit...
 *
 *  Don't use this code unless you are an ALPHA TESTER.
 * 
 *  Don't say you haven't been warned !!
 *
 *  *** END OF WARNING *** *** END OF WARNING *** *** END OF WARNING ***
 *
 * ****************************************************************************
 * ****************************************************************************
 *
 *  THIS IS ***NOT*** A SPECIALIX DRIVER.
 *  This driver is not supported by them so don't hassle them with your problems
 *  The SI-Host family of products is quite mature so it is a near certainty
 *  that any problems you encounter will be caused by this driver or other
 *  hardware in your PC and NOT the Specialix kit.
 *
 *  If you suspect a problem with the Specialix hardware it would be best to
 *  reproduce the problem using a Specialix driver under DOS or SCO UNIX
 *  (or another OS) BEFORE contacting them.
 *
 *  In any event, please report problems to me first.
 *
 * ****************************************************************************
 * ****************************************************************************
 *
 *
 * CHANGES.
 *
 *  Version 0.0.0 - 28.5.94 - Initial attempt based on serial.c
 *   Does nothing - just download code and init stuff.
 *
 *  Versions 0.0.1 to 0.3.0 superceeded by changes to kernel tty interface.
 *
 *  Version 0.4.0 - ?.6.94 - (Lost watch...)
 *   tty interface in kernel changed - all change...
 *
 *  Version 0.4.2 - 15.6.94
 *   Re-worked interrupt code to spread TX chars over all lines instead
 *   of outputting all of line one then two then three etc.
 *
 *  Version 0.4.3 - 16.6.94
 *   Changed n_tty.c to allow for hardware ONLCR cooking.
 *   Things are beginning to settle down some more.
 *  
 *  Version 0.4.8 - 21.8.94
 *   Tided up somewhat in preparation for Alpha test release.
 *   Redone all of si_serial.h
 *
 *  Version 0.5.0 - 27.8.94
 *   Major changes. Removed hard coded hardware setup. Now uses termios just like
 *   real Unix ;-)  The driver is now much further along and I am
 *   about ready to send it out for alpha test.
 *
 *  Version 0.5.1 - 29.8.94
 *   Added wait_for_stat(). Should fix a number of problems.
 *   It cured the closing problem which was my main aim.
 *   Changed default baud rate to 38400.  This isn't correct Unix standard
 *   but it's good for speed measurements and will probably remain like that for
 *   alpha versions of the driver.
 *
 *  Version 0.5.2 - 29.8.94
 *   Changed code to allow up to 32 ports instead of 8. Modified the transmitter
 *   to make it more efficient- still needs work. Removed all timer code.
 *   Serious bug. Kernel Panic when DCD switched very quickly (ie when you
 *   waggle the data lead!). Must resolve this!
 *   Found problem. Caused by null info->tty on entry to check_modem_blah().
 *   Simple cure for moment is to test for it.  I want to find out why it's
 *   happening tho.
 *   Added adaptive drain down time code based on number of chars in buffer
 *   and baud rate we're using. Gives better interactive response.
 *   Much better now we're using the old transmitter.
 *   
 *  Version 0.5.3 -  1.9.94
 *   Added timer code back. Some rare occasions we lost interrupts and the whole
 *   shebang froze up. Anyway, nice side effect is that we have polling back.
 *   
 *  Version 0.5.4 -  2.9.94
 *   Re-arranged the DCD checks in the modem isr handler. More firm but still
 *   related problems.  See below. SLIP tested OK. Haven't tested closing SLIP
 *   down and returning to Ldisc zero. The driver will prob. remain in this state
 *   now for a number of days or even weeks. I've run out of time! Still much
 *   to do :-( Anyway, I've been spending a lot of time on it lately (every
 *   waking hour!) so it'll probably be good for the driver (and me!) if I
 *   leave it for a bit!
 *
 *  Version 0.5.5 -  10.10.94
 *   Minor bug fixes after some response from alpha testers (cheers you guys!)
 *   In particular request_irq() instead of irqaction() to allow use on >1.1.35
 *   kernels (I thought I had done this already!).
 *
 *  Version 0.5.6 - 18.10.94
 *   Added preliminary support for EISA cards.
 *
 *  Version 0.5.7 - 20.10.94
 *   Some EISA cleanups. Reports that EISA code locks up PC!
 *   Holding at this version number while EISA lockups sorted out.
 *   It appears that polling on EISA works OK but not interrupt driven.
 *   Can someone confirm this please? ;-)
 *   EISA lockups have now been cured by reordering isr handling.
 *
 *  TO DO : (These are notes for my reference more than anything)
 *
 *   - There is a problem with the hangup/wait til ready code. To cause the error
 *     do the following. Start up PC with getty running on a port. Make sure
 *     that that ports DCD is inactive. Press Ctrl-D on the terminal connected
 *     to that port then do stty -a < /dev/port then ctrl-c the stty. Cool!
 *     We also get a command timeout when toggling DCD very quickly - weird!
 *
 *   - Problem with calling check_modem with NULL info->tty.
 *     Think this is solved by rearangement of DCD checks.
 *
 *   - termios settings are not consistent.  Different after boot and
 *     after getty has been active on the line.
 *
 *   - Need to relook at draining. particularly split si_shutdown()
 *
 *   - Remove changes from n_tty.c and add functional equiv. to si_ioctl
 *
 *   - hardware drain does not work now. I commented it out to fix the
 *     problem with being called in soft_int
 *
 *   - Break processing is still kaput.
 *
 *   - Seem to be getting unexpected 1201 errors. Weird.
 *
 *   - SLIP & PPP tested but not very much!  Switching line disciplines
 *     will probably be broken for a start...
 *
 *   - If we're serious about using polling we might improve performance by
 *     adding a counter. It might give us say 50 or 100 or more quick interrupts
 *     after we've processed data. This could then be throttled back after we're
 *     sure that we're not going to get more data. Good for interactive performc
 *     Prob. not worth the effort.
 *
 *   - Fixes needed due to EISA patches.
 *       - Remove HAVE_EISA in favour of EISA_bus but check depend first.
 *
 * Acknowledgements.
 *  Many thanks to Specialix Research for providing documentation for the
 *  hardware. Thanks to for all the testers who have helped - you know who
 *  you are.
 *
 *
 * This module exports the functions:
 *
 *	long si_init(long);
 */


#include <linux/config.h>

#ifdef CONFIG_SI_SERIAL


#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
#include <linux/ctype.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/delay.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/bitops.h>

#include "si_serial.h"

/* The file 'si_download.c' contains the host Z280 machine code binary image
 * that is downloaded to the card.  It's contained in an array called z280code[].
 */

#ifdef SI_DOWNLOAD_CODE
#include "si_download.c" 
#endif

int EISA_SLOT;

DECLARE_TASK_QUEUE(tq_si_serial);

/* FIXME_URGENT */
/* Any other flag set in o_flag excluding O_OPOST and O_ONLCR */
#define O_OTHER(tty) \
		((O_OLCUC(tty))  ||\
		(O_OCRNL(tty))   ||\
		(O_ONOCR(tty))   ||\
		(O_ONLRET(tty))  ||\
		(O_OFILL(tty))   ||\
		(O_OFDEL(tty))   ||\
		(O_NLDLY(tty))   ||\
		(O_CRDLY(tty))   ||\
		(O_TABDLY(tty))  ||\
		(O_BSDLY(tty))   ||\
		(O_VTDLY(tty))   ||\
		(O_FFDLY(tty)))

/* For debugging. Tells us where the interrupt came from. */
#define INT_FROM_TIMER 20
#define INT_FROM_FLUSH 21
#define INT_FROM_SLIP  22

struct tty_driver si_serial_driver, si_callout_driver;
static int si_serial_refcount;

#ifdef 0 /*REMOVE */
static struct async_struct *si_async_str[32]; /* One per channel - Max 32 */
#endif

static struct si_channel_control *si_chan_p[32]; /* One per channel - Max 32 */
static int si_channels_configured = 0; /* si_init updates this */

static int in_isr = 0;        /* In interrupt service routine flag */
static int ports_present = 0; /* Number of ports found/configured in si_init() */
static int port_processed = FALSE; /* Did isr process any ports ? */

/* si_serial subtype definitions */
#define SERIAL_TYPE_NORMAL	   1
#define SERIAL_TYPE_CALLOUT	2

/*
 * Serial driver configuration section.  Here are the various options:
 *
 * SERIAL_PARANOIA_CHECK
 * 		Check the magic number for the async_structure where
 * 		ever possible.
 */

#define SERIAL_PARANOIA_CHECK
#define SERIAL_DO_RESTART
#define CONFIG_SERIAL_NEW_ISR

#undef SERIAL_DEBUG_INTR
#undef SERIAL_DEBUG_OPEN

#define _INLINE_ inline
 
/* Bitmap of ports enabled or disabled */
static int si_ports_enabled = 0;

/* Table of each ports last known DCD state */
static int si_ports_dcd[32];

/* FIXME - This must go...!*/
#define BASE_BAUD ( 1843200 / 16 )

static void change_speed(struct async_struct *info);
int drain_hardware_tx(struct async_struct * info);
int wait_for_stat(struct si_channel_control *si_chc, int st);

/* These are the delay factors for different baud rates. They represent
 * the number of jiffies it takes to transmit a full buffer (256 chars)
 */
#ifdef HAVE_SIO_SYSTEM
int delay_table[] = {
340000, 230000, 600, 170000, 85000, 43000, 22000, 13000, 11000, 5300, 14000, 2600, 1300, 400 };
#else
int delay_table[] = {
340000, 200, 600, 170000, 85000, 43000, 22000, 13000, 11000, 5300, 14000, 2600, 1300, 400 };
#endif

/* SI flags */
#define STD_SI_FLAGS (0) /* FIXME - Could do better... */

/* FIXME - subject to change...
 * The size of this table governs the value of NR_SI_PORTS
 * We might want to think about our own version of async_struct...
 * Also note.  The linked list of async structs isn't maintained in this
 * driver (yet).  The driver doesn't use it and no other modules use this.
 */
struct async_struct si_table[] = {
	/* UART CLK   PORT IRQ     FLAGS        */

   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu0 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu1 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu2 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu3 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu4 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu5 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu6 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu7 */

   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu8 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu9 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu10 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu11 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu12 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu13 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu14 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyu15 */

   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv0 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv1 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv2 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv3 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv4 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv5 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv6 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv7 */

   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv8 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv9 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv10 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv11 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv12 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv13 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv14 */
   { BASE_BAUD, 0x00 , 0, STD_SI_FLAGS },    /* ttyv15 */
};

#define NR_SI_PORTS	(sizeof(si_table)/sizeof(struct async_struct))

static struct tty_struct *si_serial_table[NR_SI_PORTS];
static struct termios *si_serial_termios[NR_SI_PORTS];
static struct termios *si_serial_termios_locked[NR_SI_PORTS];


/* This array of termios structs stores the *REAL* termios state
 * after we've finished buggering about with it
 */
/* FIXME - this is here awaiting new OPOST handler...
static struct termios si_real_termios[NR_SI_PORTS];
*/


#ifndef MIN
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#endif

static inline int si_serial_paranoia_check(struct async_struct *info,
					dev_t device, const char *routine)
{
#ifdef SERIAL_PARANOIA_CHECK
	static const char *badmagic =
		"Warning: bad magic number for si_serial struct (%d, %d) in %s\n";
	static const char *badinfo =
		"Warning: null async_struct for (%d, %d) in %s\n";

	if (!info) {
		printk(badinfo, MAJOR(device), MINOR(device), routine);
		return 1;
	}
	if (info->magic != SI_SERIAL_MAGIC) {
		printk(badmagic, MAJOR(device), MINOR(device), routine);
		return 1;
	}
#endif
	return 0;
}

static inline unsigned int si_serial_in(struct async_struct *info, int offset)
{ /* FIXME - Remove (eventually!) */
	return 0;
}

static inline unsigned int si_serial_inp(struct async_struct *info, int offset)
{ /* FIXME - Remove (eventually!) */
	return 0;
}

static inline void si_serial_out(struct async_struct *info, int offset, int value)
{ /* FIXME - Remove (eventually!) */
	return ;
}

static inline void si_serial_outp(struct async_struct *info, int offset,
			       int value)
{ /* FIXME - Remove (eventually!) */
	return ;
}

/*
 * si_stop() and si_start()
 */
static void si_stop(struct tty_struct *tty)
{ /* FIXME - REMOVE? */
	struct async_struct *info;
	unsigned long flags;

	printk("si_stop() called\n");

	info = tty->driver_data;

	if (si_serial_paranoia_check(info, tty->device, "si_stop"))
		return;
	
	return;

	if (info->flags & ASYNC_CLOSING) {
		tty->stopped = 0;
		tty->hw_stopped = 0;
		return;
	}

	save_flags(flags); cli();
	if (info->IER & UART_IER_THRI) {
		info->IER &= ~UART_IER_THRI;
		si_serial_out(info, UART_IER, info->IER);
	}
	restore_flags(flags);
}

static void si_start(struct tty_struct *tty)
{ /* FIXME - REMOVE? */
	struct async_struct *info;
	unsigned long flags;

	printk("si_start() called\n");

	info = tty->driver_data;

	if (si_serial_paranoia_check(info, tty->device, "si_start"))
		return;
	
	return;
	save_flags(flags); cli();
	if (info->xmit_cnt && !info->xmit_buf &&
	    !(info->IER & UART_IER_THRI)) {
		info->IER |= UART_IER_THRI;
		si_serial_out(info, UART_IER, info->IER);
	}
	restore_flags(flags);
}

/*
 * ----------------------------------------------------------------------
 * Interrupt handling routines follow.
 * -----------------------------------------------------------------------
 */

/*
 * This routine is used by the interrupt handler to schedule
 * processing in the software interrupt portion of the driver.
 */
static _INLINE_ void si_sched_event(struct async_struct *info, int event)
{
	info->event |= 1 << event;
	queue_task_irq_off(&info->tqueue, &tq_si_serial);
	mark_bh(SERIAL_BH);
}

static _INLINE_ void si_receive_chars(struct async_struct *info)
{
	struct tty_struct *tty;
	unsigned char ch;
   struct si_channel_control *si_chc;
	int line, num_input_chars;
	static int in_rx = 0;

	tty = info->tty;


	if(in_rx == 1)
	{
		/* printk("Warning : Re-entered si_receive_chars\n"); */
		return;
	}
	in_rx = 1;

   line = info->line;

   si_chc = si_chan_p[line];

   while( (num_input_chars = (si_chc->hi_rxipos - si_chc->hi_rxopos) & 0xff)>0)
	{
		/* Copy char from SI-Host board to buffer */
      ch = si_chc->hi_rxbuf[si_chc->hi_rxopos];
      si_chc->hi_rxopos++;
/*
		if (*status & info->ignore_status_mask)
			continue;
*/
		if (tty->flip.count >= TTY_FLIPBUF_SIZE)
		{
			printk("flip.count >= TTYFLIPBUF_SIZE %d chars left\n", num_input_chars);
			break;
		}

		tty->flip.count++;

/* FIXME - sort out what needs doing here and what can be left.
 * BREAK processing is relly done in the isr.
 *
 * PARITY is handled by the board in the standard Unix way, that is,
 * it sends 0377 / 0 / c so we just let things proceed without even
 * trying to detect it at this level.
 */
#ifdef 0
		if (*status & info->read_status_mask)
		{
			if (*status & (UART_LSR_BI))
			{
				*tty->flip.flag_buf_ptr++ = TTY_BREAK;
				if (info->flags & ASYNC_SAK)
					do_SAK(tty); /* Secure Attention Key (Blimey!) */
			}
			else if (*status & UART_LSR_PE)
			{
				*tty->flip.flag_buf_ptr++ = TTY_PARITY;
			}
			else if (*status & UART_LSR_FE)
			{
				*tty->flip.flag_buf_ptr++ = TTY_FRAME;
			}
			else if (*status & UART_LSR_OE) 
			{ /* I just don't know how to handle this.
			   * OK, so it's unlikely, but not impossible... 
			   * Specialix docs don't mention the possibility.
			   */
				*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
			}
			else
			{
				*tty->flip.flag_buf_ptr++ = 0;
			}
		}
		else
		{
#endif
			*tty->flip.flag_buf_ptr++ = 0;
/*
		}
*/
		*tty->flip.char_buf_ptr++ = ch;
	}


	queue_task_irq_off(&tty->flip.tqueue, &tq_timer);

/*
	if(((si_chc->hi_rxipos - si_chc->hi_rxopos) & 0xff)>0)
		printk("<%d> chars residue!\n", (si_chc->hi_rxipos - si_chc->hi_rxopos) & 0xff);
	else
		printk("\n");
*/
	in_rx = 0;
}








static _INLINE_ void si_transmit_chars(struct async_struct *info)
{
   int count;
/*	char char_to_go; FIXME - REMOVE */
   struct si_channel_control *si_chc;
	int line;
	static int in_tx = 0;
	register unsigned char txipos, txopos;


	line=info->line;

	if(in_tx == 1)
	{
		/* printk("Warning : Re-entered si_transmit_chars\n"); */
		return;
	}
	in_tx = 1;

   count = info->xmit_fifo_size;
   si_chc = si_chan_p[line];

   while (count--)
	{
		txipos = si_chc->hi_txipos;
		txopos = si_chc->hi_txopos;

      if(((txipos-txopos)&0xff)<0xff)
      {
         si_chc->hi_txbuf[txipos] = info->xmit_buf[info->xmit_tail++];

/*			printk("%c", char_to_go); */

         si_chc->hi_txipos = txipos + 1;

			if (--info->xmit_cnt == 0)
				break;
      }
      else
      {
         count++;
         break;
      }

		info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
   }

	if (info->xmit_cnt < WAKEUP_CHARS)
		si_sched_event(info, RS_EVENT_WRITE_WAKEUP);

	in_tx = 0;
	return;
}




/* This function gets called out of the isr. We are informed about
 * these changes by an interrupt, which is nice ;-)
 */
static _INLINE_ void check_modem_status(struct async_struct *info)
{
	int	current_dcd;
	int	modem_change;
   struct si_channel_control *si_chc;
	int line;

	if(info==NULL)
		{ printk("Aiee, NULL info in check_modem_status\n"); return; }

	if(info->tty==NULL)
		{ printk("Aiee, NULL info->tty in check_modem_status\n"); return; }

	if (si_serial_paranoia_check(info, info->tty->device, "si_shutdown"))
		return;

	line = info->line;

   si_chc = si_chan_p[line];

	modem_change = si_chc->hi_state;

	if(!modem_change) /* If this flag is zero then there's nothing to do */
		return;       /* This flag is only nonzero if RTS, DCD or DTR (or RI)
                     * have changed or if break received. This probably
                     * saves lots of time... */

	/* printk("Modem change HI_STATE = %d\n", modem_change);*/


/* FIXME - this is broken... */
	if(modem_change & ST_BREAK)
	{
		printk("BREAK PRESSED.\n"); 
		if (info->flags & ASYNC_SAK)
		{
			printk("and SAK flagged.\n"); 
			do_SAK(info->tty); /* Secure Attention Key (Blimey!) */
		}
		port_processed = TRUE;
	}

	/* FIXME - We attempt to keep the state of DCD so that we can divine
	 * *change of state* of DCD. It's over elaborate but I've had a lot
	 * of trouble with this part. Rather it's that than flakey. We still have
	 * a couple of problems with the hangup stuff.  See notes at top of this file.
    */

	current_dcd = si_chc->hi_ip & IP_DCD;

	if( (info->flags & ASYNC_CHECK_CD) && (modem_change & ST_DCD))
	{ /* Change in Modem signal states */
		if( si_ports_dcd[line] != current_dcd ) /* old!=current - DCD changed */
		{
			/* OLD_DCD = CURRENT_DCD */
			si_ports_dcd[line] = current_dcd;

			if(current_dcd) /* CURRENT_DCD is HIGH */
			{ /* DCD Low to High transition detected */
   			if( (~(info->flags & ASYNC_NORMAL_ACTIVE) || 
					 ~(info->flags & ASYNC_CALLOUT_ACTIVE)) &&
					 (si_chc->hi_hstat != IDLE_CLOSED) ) /* Are we blocking in open?*/
				{
					printk("DCD RAISED, wake_up open\n");
					wake_up_interruptible(&info->open_wait);
					port_processed = TRUE;
				}
				else
					printk("DCD RAISED - Spurious\n");
			}
			else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) &&
			   	(info->flags & ASYNC_CALLOUT_NOHUP)))
			{ /* DCD High to Low transition detected */
   			if( ((info->flags & ASYNC_NORMAL_ACTIVE) || 
					 (info->flags & ASYNC_CALLOUT_ACTIVE)) &&
					 (si_chc->hi_hstat != IDLE_CLOSED) ) /* Is port open?*/
				{
					printk("DCD LOWERED, tty_hangup() scheduled\n");
					si_sched_event(info, RS_EVENT_HANGUP); 
					port_processed = TRUE;
				}
				else
					printk("DCD Lowered - Spurious\n");
			}
		}
		/* else must have been another signal here */
	}
	/* else either we're not checking DCD or it didn't change */


	/* CTS flow control is always done by the H/W (if it's enabled;-) */
	/* So we never need to check it here */

	info->tty->hw_stopped = 0;


	return;
}


/*
 * This is the serial driver's interrupt routine
 * Interrupts occur if one of the following conditions happen :
 * 1. Transmitter drained to its low water mark.
 * 2. Character is received.
 * 3. Break received.
 * 4. Change in modem signals (DTR|RTS|DCD|RI) if checking is enabled.
 * 5. Channel has been opened, closed or configured.
 * Received character interrupts are batched together for efficiency.
 */

static void si_interrupt(int irq)
{
   int chars_in_buff;
/* FIXME - REMOVE   char char_to_output; */
   int chan;
   struct si_channel_control *si_chc;
   struct async_struct * info;
   int done/* FIXME - REMOVE, dummy*/;
/* REMOVE -   int timeout = jiffies+si_timer_service; */
   struct si_card_control *si_cc;
/* FIXME - REMOVE	int total_chan_done; */
	int start_timer = FALSE;

	port_processed = FALSE;


   si_cc = (struct si_card_control *) si_base;

   done = 1;

   /* Turn off the cards interrupts */
   /* and ack int */

	switch(irq)
	{
		case INT_FROM_TIMER:
			printk("interrupt from timer\n");
		break;
		case INT_FROM_FLUSH:
			printk("interrupt from flush\n");
		break;
		case INT_FROM_SLIP:
			printk("interrupt from slip\n");
		break;
		default:
   		if(si_irq != 0)
   		{
      		si_cc->int_pending = 0;
#ifdef HAVE_EISA
				inb(0xc03+EISA_SLOT);
#else
      		si_base[SIPLIRQCLR] = 0x00; 
      		si_base[SIPLIRQCLR] = 0x10; /* FIXME - Moved from end of isr - see below */
#endif
   		}
			/* printk("interrupt from vector [%d] ??\n", irq); */
			start_timer = TRUE;
		break;
	}

	if(in_isr == 1)
	{  /* Reentered isr. Just exit, not serious. */
		/* Ask Buzz about this one. */
		printk("si_serial : 1201 Program Alarm.\n");
		return;
	}
	in_isr = 1; /* Flag we're in isr. */

/* Check modem status's */
  	for(chan=0; chan<ports_present; chan++) 
  	{
      if( !(si_ports_enabled & (1<<chan)) ) /* NOT enabled */
			continue;
      info = &si_table[chan];

		check_modem_status(info);
	}


#undef SI_SPREAD_TX /* FIXME - This will almost certainly go soon :-) */
#ifdef SI_SPREAD_TX

	while(1)
	{
		total_chan_done = 0;
   	for(chan=0; chan<ports_present; chan++) /* TRANSMITTER */
   	{
      	info = &si_table[chan];
      	if( !(si_ports_enabled & (1<<chan)) ) /* NOT enabled */
         	continue;

      	si_chc = si_chan_p[chan];
      	chars_in_buff = (si_chc->hi_txipos - si_chc->hi_txopos) & 0xff;

      	if((chars_in_buff <= 0xfe) && (info->xmit_cnt > 0 ))
      	{
         	char_to_output = info->xmit_buf[info->xmit_tail];
         	si_chc->hi_txbuf[si_chc->hi_txipos] = char_to_output;

         	si_chc->hi_txipos++;

         	info->xmit_tail++;
				info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
				info->xmit_cnt--;

				total_chan_done++;

				if (info->xmit_cnt < WAKEUP_CHARS)
				{
					si_sched_event(info, RS_EVENT_WRITE_WAKEUP);
				}
				port_processed = TRUE;			
   		}
   	}
   	if(total_chan_done==0)
   		break;
   }

#else
   for(chan=0; chan<ports_present; chan++) /* TRANSMITTER */
   {
      info = &si_table[chan];

      if( !(si_ports_enabled & (1<<chan)) ) /* NOT enabled */
      { /* chan is closed !*/
         continue;
      }

      if( info->xmit_cnt > 0 )
      {
         si_transmit_chars(info);
			port_processed = TRUE;			
      }
   }
#endif

   /* check all 8 (32) rx queues and all 8 (32) tx queues */

   for(chan=0; chan<ports_present; chan++) /* RECEIVER */
   {
      info = &si_table[chan];

      if( !(si_ports_enabled & (1<<chan)) ) /* NOT enabled */
      { /* no tty struct because chan is closed !*/
         continue;
      }
      si_chc = si_chan_p[chan];

      chars_in_buff = (si_chc->hi_rxipos - si_chc->hi_rxopos) & 0xff;

      if( chars_in_buff>0 )
      {
         si_receive_chars(info);
			port_processed = TRUE;			
      }
   }


#ifdef 0
/* FIXME - This is redundant now.  I'll leave it in for now tho... */
   /* re-enable interrupts from the card...*/
   if(si_irq != 0)
   {
#ifdef HAVE_EISA

#ifdef 0
	/* FIXME - This is the bit I don't understand */
		outb(0, 0xc03+EISA_SLOT);
#endif

#else
      /* FIXME - Changed - see above. si_base[SIPLIRQCLR] = 0x10;  */
#endif
   }
#endif

	if(port_processed)
	{
		timer_table[SI_TIMER].expires = jiffies + SI_ACTIVE_TIMER_DELAY;
		timer_active |= 1 << SI_TIMER;
	}
	else
	{
		timer_table[SI_TIMER].expires = jiffies + SI_INACTIVE_TIMER_DELAY;
		timer_active |= 1 << SI_TIMER;
	}

	in_isr = 0;
   return; /* FOO */

}

/*
 * This routine is used to handle the "bottom half" processing for the
 * serial driver, known also the "software interrupt" processing.
 * This processing is done at the kernel interrupt level, after the
 * si_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
 * is where time-consuming activities which can not be done in the
 * interrupt driver proper are done; the interrupt driver schedules
 * them using si_sched_event(), and they get done here.
 */

static void do_si_serial_bh(void *unused)
{
	run_task_queue(&tq_si_serial);
}

static void do_softint(void *private)
{
	struct async_struct	*info;
	struct tty_struct	*tty;

	info = (struct async_struct *) private;
	
	tty = info->tty;

	if (!tty)
		return;

   if( !(si_ports_enabled & (1<<info->line)) ) /* NOT enabled */
	{
		printk("%s : Running softint for non enabled port!!\n", SI_ID_STR);
		return;
	}

	if (clear_bit(RS_EVENT_HANGUP, &info->event)) {
		tty_hangup(tty);
		wake_up_interruptible(&info->open_wait);
		info->flags &= ~(ASYNC_NORMAL_ACTIVE|
				 ASYNC_CALLOUT_ACTIVE);
	}

	if (clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) {
		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
		    tty->ldisc.write_wakeup)
			(tty->ldisc.write_wakeup)(tty);
		wake_up_interruptible(&tty->write_wait);
	}
}


/*
 * This subroutine is called when the SI_TIMER goes off.
 * If we don't get any interrupts back from the card within SI_INT_LIMIT time
 * we simulate interrupts at a rate specified by SI_*_TIMER_DELAY
 */
static void si_timer(void)
{
/*	printk("si_timer() called.\n"); */

	/* if ports are active simulate an int otherwise just spin the timer again */
	if(si_ports_enabled)
		si_interrupt(INT_FROM_TIMER);
	else
	{
		timer_table[SI_TIMER].expires = jiffies + SI_INACTIVE_TIMER_DELAY;
		timer_active |= 1 << SI_TIMER;
	}

	port_processed = FALSE;
}

static int si_startup(struct async_struct * info)
{
	struct si_channel_control *si_chc;
	int line;

	line = info->line;

	if (info->flags & ASYNC_INITIALIZED)
	{
		return 0;
	}

	if (!info->type) {
		if (info->tty)
			set_bit(TTY_IO_ERROR, &info->tty->flags);
		printk("si_startup() bailout : info->type bad.\n");
		return 0;
	}

	if (!info->xmit_buf) {
		info->xmit_buf = (unsigned char *) get_free_page(GFP_KERNEL);
		if (!info->xmit_buf)
		{
			printk("si_startup() bailout : ENOMEM\n");
			return -ENOMEM;
		}
	}

   si_ports_enabled |= ( 1 << line ); /* Flag that port is enabled */

   si_chc = si_chan_p[line];

	/* FIXME - These should be changed for the correct startup defaults */
	/* NOT hard coded here. Oh well. */
   si_chc->hi_txon      =  '\021'; /* XON Char */
   si_chc->hi_txoff     =  '\023'; /* XOFF Char */

   si_chc->hi_op     =  OP_CTS  | OP_DSR; /* Modem on. */

	change_speed(info);

   si_chc->hi_hstat = LOPEN; /* and finally open chan */

	wait_for_stat(si_chc, IDLE_OPEN);

	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;

	info->flags |= ASYNC_INITIALIZED;

	timer_table[SI_TIMER].expires = jiffies + SI_ACTIVE_TIMER_DELAY;
	timer_active |= 1 << SI_TIMER;

   return 0;




/* FIXME - What's this ? 
	if (info->tty)
		clear_bit(TTY_IO_ERROR, &info->tty->flags);
*/

}

/*
 * This routine will shutdown a serial port
 * DTR is dropped if the hangup on close termio flag is on.
 */
static void si_shutdown(struct async_struct * info, char *caller)
{
	int line;
	struct si_channel_control *si_chc;
	int residue = 0;

	/* printk("si_shutdown() called from [%s]\n", caller); */
	
	line = info->line;
   si_chc = si_chan_p[line];

	if (si_serial_paranoia_check(info, info->tty->device, "si_shutdown"))
		return;


	if (!(info->flags & ASYNC_INITIALIZED))
	{
		printk("Warning : NOT doing si_shutdown()\n");
		return;
	}

/* FIXME - Debug this */
	if(info->xmit_buf)
	{
		free_page((unsigned long) info->xmit_buf);
		info->xmit_buf = 0;
	}


/* FIXME_URGENT - Oh Lord, is this right? It's a big hack alright! */

	if(current->signal & ~current->blocked)
	{
		printk("FORCE CLOSED DUE TO SIGNAL\n");
		si_chc->hi_hstat = FORCE_CLOSED; /* Force close */
		info->flags &= ~ASYNC_INITIALIZED;
   	si_ports_enabled &= ~( 1 << line ); /* Flag that port is disabled */
		return;
	}


/* Wait until the SI-Host hardware buffer has drained down.
 * FIXME_URGENT - si_shutdown gets called from the bottom half so
 * we need to create two si_shutdown functions - i think...;)
 */

/*
	residue = drain_hardware_tx(info);
*/

   si_ports_enabled &= ~( 1 << line ); /* Flag that port is disabled */

   si_chc->hi_op = ~(OP_CTS | OP_DSR); /* Modem off */

   /* Send command to SI-Host to CLOSE */
   /* FIXME - Should we just force close in all cases ? */
	if(residue>0) 
	{
		printk("Residue!=0, Forcing hardware to close\n");
		si_chc->hi_hstat = FORCE_CLOSED; /* Force close */
	}
	else
	{
		si_chc->hi_hstat = CLOSE; /* Normal close */
	}

	info->flags &= ~ASYNC_INITIALIZED;

	return ;
}

/* We use very simple calculation for drain down wait time.
 * It takes 260ms to transmit 256 chars @ 9600 baud.
 * If we're using higher baud than 9600 we wait too long
 * and if we're lower we wait too many times ! Feel free to
 * improve.  This case is ``optimal'' for 9600 baud.
 * I actually use 270ms to hopefully insure that we don't just miss
 * and have one or two chars still in the queue.  In reality flow
 * control probably makes a nonsense of this anyway and we'll probably
 * end up waiting a whole 270ms more anyway.
 *
 * I've fixed this up to set the duration based on number of chars in queue
 * and baud rate.
 */
int drain_hardware_tx(struct async_struct * info)
{
	int line;
	struct si_channel_control *si_chc;

	unsigned int duration = 27; /* 27 (100ths of sec) */
	unsigned int timeout = 6000; /* 60 seconds, in case something goes wrong... */
	unsigned int total_time = 0;
	unsigned int baud_index;

	int num_chars;

	line = info->line;

   si_chc = si_chan_p[line];

/* Upper 4 bits of hi_csr for tx baud */
	baud_index = (si_chc->hi_csr & 0xf0) >> 4;

	num_chars = (si_chc->hi_txipos - si_chc->hi_txopos) & 0xff;

	do
	{
/* I'll simplify this when I have time...! */
		duration=delay_table[baud_index]/((25600/((num_chars+1)*100))*100)+1;

#ifdef 0
		printk("DELAY Baud Index=%d, Delay[BI]=%d, Numchars=%d, Duration=%d\n", 
			baud_index, delay_table[ baud_index ], num_chars, duration);
#endif

   	current->state = TASK_INTERRUPTIBLE;
   	current->timeout = jiffies + duration;
   	cli();
   	schedule();
   	sti();
		num_chars = (si_chc->hi_txipos - si_chc->hi_txopos) & 0xff;

		total_time += duration; /* How long have we waited so far. */
		if(total_time>timeout) /* Don't wait forever.  */
		{
			printk("Timed out waiting for H/W output to drain.\n");
			break;
		}
   }
	while(num_chars>0);

	return(num_chars);
}

/*
 * This routine is called to set the SI-Host to 
 * the specified baud rate for a serial port.
 */
static void change_speed(struct async_struct *info)
{
	int residue;
	struct tty_struct *tty;
   unsigned cflag;
   int   line;
   struct si_channel_control *si_chc;
   unsigned char si_baudrate = U9600;

	line = info->line;

   si_chc = si_chan_p[line];

   if (line >= NR_SI_PORTS)
      return;

   if (!info->tty || !info->tty->termios)
      return;

   cflag = info->tty->termios->c_cflag;
	tty = info->tty;


/* FIXME - drain_hardware_tx() in wrong place.
 * Now I don't think that this should be done here.  It should only be done
 * if the ioctl call requires it.  But it appears that this is how Specialix
 * do it.  I'm in contact with them about this so be patient.
 * The fact is that it's a pain in the ass to do right...;-)
 *
 * Spoke to Specialix. They say that they've never had any complaints so... !
 */
	residue = drain_hardware_tx(info);


/* ------------------------ Baud Rate ---- (FIRST USE CSR) --------------------*/

/* These are the Linux (U*ix) standard baud rates.  However, the SI-Host
 * card is a lot more flexible than this.  You encode the receive baud
 * rate in the UPPER 4 bits and the transmit baud rate in the LOWER 4
 * bits of a char.  This is how 1200/75 and 75/1200 are done.
 * See si_serial.h for more details.
 */

	switch(cflag & CBAUD)
	{
		/* FIXME - URGENT I'm not sure if we should be looking for B0 here... */
		case B0		: printk("Assertion error : This shouldn't happen!\n"); break; 
   	case B50    : si_baudrate = U50     ; break;  /* 57600   baud */
   	case B75    : si_baudrate = U75     ; break;  /* 75      baud */
		case B110   : si_baudrate = U110    ; break;  /* 110     baud (or XIO is 11520) */
   	case B134   : si_baudrate = U134    ; break;  /* 1200/75 baud */
   	case B150   : si_baudrate = U150    ; break;  /* 150     baud */
   	case B200   : si_baudrate = U200    ; break;  /* 75/1200 baud */
   	case B300   : si_baudrate = U300    ; break;  /* 300     baud */
   	case B600   : si_baudrate = U600    ; break;  /* 600     baud */
   	case B1200  : si_baudrate = U1200   ; break;  /* 1200    baud */
   	case B1800  : si_baudrate = U1800   ; break;  /* 2000    baud */
   	case B2400  : si_baudrate = U2400   ; break;  /* 2400    baud */
   	case B4800  : si_baudrate = U4800   ; break;  /* 4800    baud */
   	case B9600  : si_baudrate = U9600   ; break;  /* 9600    baud */
   	case B19200 : si_baudrate = U19200  ; break;  /* 19200   baud */
   	case B38400 : si_baudrate = U38400  ; break;  /* 38400   baud */

   	default: /* FIXME - I think this is an assertion error...? */
      	printk("%s : Invalid baudrate on port #%d - Setting to 9600\n", 
				SI_ID_STR, line);
      	si_baudrate = U9600;
   	break;
	}

/* Put new baud rate in clock select register... */
   si_chc->hi_csr = si_baudrate;

/* ---------------------------- Enable Parity ------ (FIRST USE MR1) ----------*/

   if (C_PARENB(tty))
      si_chc->hi_mr1 = MR1_WITH;
   else
      si_chc->hi_mr1 = MR1_NONE; /* 0x10 */

/* ---------------------------- ODD (otherwise EVEN) parity -------------------*/

   if (C_PARODD(tty))
      si_chc->hi_mr1 |= MR1_ODD;
	else
      si_chc->hi_mr1 |= MR1_EVEN;

/* ---------------------------- Word Size -------------------------------------*/

	switch(cflag & CSIZE)
	{
		case CS8: si_chc->hi_mr1 |= MR1_8_BITS; si_chc->hi_mask = 0xFF; break;
		case CS7: si_chc->hi_mr1 |= MR1_7_BITS; si_chc->hi_mask = 0x7F; break;
		case CS6: si_chc->hi_mr1 |= MR1_6_BITS; si_chc->hi_mask = 0x3F; break;
		case CS5: si_chc->hi_mr1 |= MR1_5_BITS; si_chc->hi_mask = 0x1F; break;

   	default: /* FIXME - I think this is an assertion error too...? */
      	printk("%s : Invalid wordsize on port #%d - Setting to CS8\n", 
				SI_ID_STR, line);
			si_chc->hi_mr1 |= MR1_8_BITS;
			si_chc->hi_mask = 0x1F;
   	break;
	}

/* ---------------------------- Stop Bit ------ (FIRST USE OF MR2) --------------------------------*/

   if (C_CSTOPB(tty))
      si_chc->hi_mr2 = MR2_2_STOP;
   else
      si_chc->hi_mr2 = MR2_1_STOP;

/* ---------------------------- Hardware Flow Control -------------------------*/

	if(C_CRTSCTS(tty))
	{
      si_chc->hi_mr1 |= MR1_CTSCONT;
      si_chc->hi_mr2 |= MR2_RTSCONT;
	}
   else
	{
      si_chc->hi_mr1 &= ~MR1_CTSCONT;
      si_chc->hi_mr2 &= ~MR2_RTSCONT;
	}

/* ---------------------------- Software Flow Control --- (FIRST USE PRTCL) ---*/

	if(I_IXON(tty))
  		si_chc->hi_prtcl = SP_TXEN;
	else
  		si_chc->hi_prtcl = 0;

/* ---------------------------- Software Flow Control Characters --------------*/

	if(I_IXANY(tty))
  		si_chc->hi_prtcl  |=  SP_TANY;
	else /* Only allow XON char to restart TX */
  		si_chc->hi_prtcl  &=  ~SP_TANY;

/* ---------------------------- OPOST Handling --------------------------------*/

	if(O_OPOST(tty) && O_ONLCR(tty) && (!O_OTHER(tty)))
  		si_chc->hi_prtcl  |=  SP_CEN; /* Hardware Cooking */
	else
  		si_chc->hi_prtcl  &=  ~SP_CEN; /* No Hardware Cooking */

/* ---------------------------- Signal (DCD etc) Handling ---------------------*/

	if(C_HUPCL(tty)) /* FIXME - && IF NOT CALLOUT DEVICE */
  		si_chc->hi_prtcl  |=  SP_DCEN;
	else
  		si_chc->hi_prtcl  &=  ~SP_DCEN;

/* ---------------------------- Strip 8th bit ---------------------------------*/

   if (I_ISTRIP(tty))
      si_chc->hi_mask &= 0x7F;

/* ---------------------------- Break Handling ------ (FIRST USE BREAK) -------*/

	if(I_IGNBRK(tty))
  		si_chc->hi_break  =  BR_IGN;
	else
  		si_chc->hi_break  =  0;

/* ---------------------------- Break Interrupt Handling ----------------------*/

	if(I_BRKINT(tty))
  		si_chc->hi_break  |=  BR_INT;
	else
  		si_chc->hi_break  &=  ~BR_INT;

/* ----------------------------------------------------------------------------*/
		

/* FIXME - Should wait for HS_IDLE or some such here before proceeding
 * or somewhere else anyway... What the hell am I talking about... */

   si_chc->hi_hstat = CONFIG; /* Send configure command... */
	wait_for_stat(si_chc, IDLE_OPEN);

	/* Can't see cflag in the isr so... */
   if (cflag & CLOCAL)
      info->flags &= ~ASYNC_CHECK_CD;
   else
      info->flags |= ASYNC_CHECK_CD;

/* Done. */
   return;
}


static void si_put_char(struct tty_struct *tty, unsigned char ch)
{
	struct async_struct *info = tty->driver_data;

	if (si_serial_paranoia_check(info, tty->device, "si_put_char"))
		return;

   if (!tty || !info->xmit_buf)
      return;

   if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
      return;

	info->xmit_buf[info->xmit_head++] = ch;
	info->xmit_head &= SERIAL_XMIT_SIZE-1;
	info->xmit_cnt++;
}

static void si_flush_chars(struct tty_struct *tty)
{
	struct async_struct *info = tty->driver_data;
/* FIXME - REMOVE	int dummy; */

	if (si_serial_paranoia_check(info, tty->device, "si_flush_chars"))
		return;

	if (info->xmit_cnt == 0 || !info->xmit_buf)
		return;

	if (info->xmit_cnt < 0)
		return;

#ifdef SI_SPREAD_TX
	si_interrupt(INT_FROM_FLUSH);
#else
	si_transmit_chars(info);
#endif

	return;
}

static int si_write(struct tty_struct * tty, int from_user,
		    unsigned char *buf, int count)
{
	int	c, total = 0;
	struct async_struct *info = tty->driver_data;
	unsigned long flags;

	if (si_serial_paranoia_check(info, tty->device, "si_write"))
		return 0;

	if (!tty || !info->xmit_buf)
		return 0;
	    
	while (1) {
		c = MIN(count, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
				   SERIAL_XMIT_SIZE - info->xmit_head));
		if (!c)
			break;

		if (from_user)
			memcpy_fromfs(info->xmit_buf + info->xmit_head,
				      buf, c);
		else
			memcpy(info->xmit_buf + info->xmit_head, buf, c);
		info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
		cli();
		if(c<0)
			printk("NEGATIVE INC");
		info->xmit_cnt += c;

		sti();
		buf += c;
		count -= c;
		total += c;
	}
	save_flags(flags); cli();
	if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
	    !(info->IER & UART_IER_THRI)) {
		info->IER |= UART_IER_THRI;
		si_serial_out(info, UART_IER, info->IER);
	}
	restore_flags(flags);

/* FIXME - I don't think this should be here, It is only here to make
 * SLIP & PPP work reliably.  I've heard that someone else had the
 * bug where SLIP stalls for seconds and then continues.
 */
	si_interrupt(INT_FROM_SLIP); /* Fix for SLIP & PPP */

	return total;
}

static int si_write_room(struct tty_struct *tty)
{
	struct async_struct *info = tty->driver_data;
				
	if (si_serial_paranoia_check(info, tty->device, "si_write_room"))
		return 0;
		if((SERIAL_XMIT_SIZE - info->xmit_cnt - 1) < 0)
			printk("NEG RET VAL");
	return SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
}

static int si_chars_in_buffer(struct tty_struct *tty)
{
	struct async_struct *info = tty->driver_data;
	struct si_channel_control *si_chc;
				
	if (si_serial_paranoia_check(info, tty->device, "si_chars_in_buffer"))
		return 0;

   si_chc = si_chan_p[info->line];

#ifdef 0
	printk("CHARS IN BUFFER (%d SW), (%d HW)\n",
		info->xmit_cnt , ((si_chc->hi_txipos - si_chc->hi_txopos) & 0xff));
#endif

	return info->xmit_cnt;

	/* We need to do the equiv of this but it won't work because the function
	 * wait_until_sent waits on the tx queue. So when the hardware drains
	 * down we don't get woken up :-(.  I'd like to add a driver hook in
	 * wait_until_sent() but for now we make do by calling drain_hardware_tx()
	 * before we change any termios in change_speed().
	 * return info->xmit_cnt + ((si_chc->hi_txipos - si_chc->hi_txopos) & 0xff);
 	 */
}

static void si_flush_buffer(struct tty_struct *tty)
{
	struct async_struct *info = tty->driver_data;
				
	if (si_serial_paranoia_check(info, tty->device, "si_flush_buffer"))
		return;
	cli();
	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
	sti();
	wake_up_interruptible(&tty->write_wait);
	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
	    tty->ldisc.write_wakeup)
		(tty->ldisc.write_wakeup)(tty);
}

/*
 * ------------------------------------------------------------
 * si_throttle()
 * 
 * This routine is called by the upper-layer tty layer to signal that
 * incoming characters should be throttled. WRONG! Not used ! REMOVE
 * We do throttling in HW for the SI-Host.
 * ------------------------------------------------------------
 */
/* FIXME - This is still called. Investigate */
static void si_throttle(struct tty_struct * tty)
{
	struct async_struct *info = tty->driver_data;
	char	buf[64];

	printk("throttle %s: %d....\n", _tty_name(tty, buf),
	       tty->ldisc.chars_in_buffer(tty));

	if (si_serial_paranoia_check(info, tty->device, "si_throttle"))
		return;
	
	if (I_IXOFF(tty))
		info->x_char = STOP_CHAR(tty);

	info->MCR &= ~UART_MCR_RTS;
	info->MCR_noint &= ~UART_MCR_RTS;
	cli();
	si_serial_out(info, UART_MCR, info->MCR);
	sti();
}

static void si_unthrottle(struct tty_struct * tty)
{
	struct async_struct *info = tty->driver_data;
	char	buf[64];
	
	printk("unthrottle %s: %d....\n", _tty_name(tty, buf),
	       tty->ldisc.chars_in_buffer(tty));

	if (si_serial_paranoia_check(info, tty->device, "si_unthrottle"))
		return;
	
	if (I_IXOFF(tty)) {
		if (info->x_char)
			info->x_char = 0;
		else
			info->x_char = START_CHAR(tty);
	}
	info->MCR |= UART_MCR_RTS;
	info->MCR_noint |= UART_MCR_RTS;
	cli();
	si_serial_out(info, UART_MCR, info->MCR);
	sti();
}

/*
 * ------------------------------------------------------------
 * si_ioctl() and friends
 * ------------------------------------------------------------
 */

static int get_si_serial_info(struct async_struct * info,
			   struct serial_struct * retinfo)
{
	struct serial_struct tmp;

	/* FIXME - Is this routine needed for the SI-Host ? */
	return -EPERM;
  
	if (!retinfo)
		return -EFAULT;
	memset(&tmp, 0, sizeof(tmp));
	tmp.type = info->type;
	tmp.line = info->line;
	tmp.port = info->port;
	tmp.irq = info->irq;
	tmp.flags = info->flags;
	tmp.baud_base = info->baud_base;
	tmp.close_delay = info->close_delay;
	tmp.custom_divisor = info->custom_divisor;
	tmp.hub6 = info->hub6;
	memcpy_tofs(retinfo,&tmp,sizeof(*retinfo));
	return 0;
}

static int set_si_serial_info(struct async_struct * info,
			   struct serial_struct * new_info)
{
	struct serial_struct new_serial;
	struct async_struct old_info;
	unsigned int		i,change_irq,change_port;
	int 			retval = 0;

	/* FIXME - Is this routine needed for the SI-Host ? */
   /* We might use it but not for the functions described below i think. */
	return -EPERM; /* FIXME - Ret code ? */

	if (!new_info)
		return -EFAULT;
	memcpy_fromfs(&new_serial,new_info,sizeof(new_serial));
	old_info = *info;

	change_irq = new_serial.irq != info->irq;
	change_port = (new_serial.port != info->port) || (new_serial.hub6 != info->hub6);

	if (!suser()) {
		if (change_irq || change_port ||
		    (new_serial.baud_base != info->baud_base) ||
		    (new_serial.type != info->type) ||
		    (new_serial.close_delay != info->close_delay) ||
		    ((new_serial.flags & ~ASYNC_USR_MASK) !=
		     (info->flags & ~ASYNC_USR_MASK)))
			return -EPERM;
		info->flags = ((info->flags & ~ASYNC_USR_MASK) |
			       (new_serial.flags & ASYNC_USR_MASK));
		info->custom_divisor = new_serial.custom_divisor;
		goto check_and_exit;
	}

	if (new_serial.irq == 2)
		new_serial.irq = 9;

	if ((new_serial.irq > 15) || (new_serial.port > 0xffff) ||
	    (new_serial.type < PORT_UNKNOWN) || (new_serial.type > PORT_MAX)) {
		return -EINVAL;
	}

	/* Make sure address is not already in use */
	for (i = 0 ; i < NR_SI_PORTS; i++)
		if ((info != &si_table[i]) &&
		    (si_table[i].port == new_serial.port) && si_table[i].type)
			return -EADDRINUSE;

	if ((change_port || change_irq) && (info->count > 1))
		return -EBUSY;

	/*
	 * OK, past this point, all the error checking has been done.
	 * At this point, we start making changes.....
	 */

	info->baud_base = new_serial.baud_base;
	info->flags = ((info->flags & ~ASYNC_FLAGS) |
			(new_serial.flags & ASYNC_FLAGS));
	info->custom_divisor = new_serial.custom_divisor;
	info->type = new_serial.type;
	info->close_delay = new_serial.close_delay;

	if (change_port || change_irq) {
		/*
		 * We need to shutdown the serial port at the old
		 * port/irq combination.
		 */
		si_shutdown(info, "set_si_serial_info");
		info->irq = new_serial.irq;
		info->port = new_serial.port;
		info->hub6 = new_serial.hub6;
	}
	
check_and_exit:
	if (!info->port || !info->type)
		return 0;
	if (info->flags & ASYNC_INITIALIZED) {
		if (((old_info.flags & ASYNC_SPD_MASK) !=
		     (info->flags & ASYNC_SPD_MASK)) ||
		    (old_info.custom_divisor != info->custom_divisor))
			change_speed(info);
	} else
		retval = si_startup(info);
	return retval;
}

static int get_modem_info(struct async_struct * info, unsigned int *value)
{
	unsigned char input_status, output_status;
	unsigned int result = 0, line;
	struct si_channel_control *si_chc;

	printk("get_modem_info\n");

	line = info->line;	
	si_chc = si_chan_p[line];

/* FIXME some of this doesn't seem to work ???? Why not?
 *
 * The SI-Host is configured as DCE (Data Comms Equip.) rather than the usual
 * U*ix or PC serial port which are configured as DTE (Data Terminal Equip.)
 *
 * This makes it easy to plug terminals and other DTE equipment into the port
 * (you just use a 1 to 1 cable) but a bit more tricky for DCE equipment such
 * as modems which need a crossover cable.  This is opposite to the ``normal''
 * serial connection between PC's and peripherals and has a nasty side effect.
 *
 * Yes, you've guessed it, you have to transpose pretty much all of the
 * hand shaking signals in the get_modem_info() function. Delightful...
 *
 *
 * SI card status             Linux internal status
 * -------------------------------------------------
 * Clear To Send..............Request To Send
 * Data Set Ready.............Data Terminal Ready
 * Ring Indicator.............Ring Indicator
 * Data Carrier Detect........Data Carrier Detect
 * Request to Send............Clear To Send
 * Data Terminal Ready........Data Set Ready
 * -------------------------------------------------
 *
 */

	cli();
	input_status  = si_chc->hi_ip;
	output_status = si_chc->hi_op;
	sti();

	if( output_status & OP_CTS ) result |= TIOCM_RTS;
	if( output_status & OP_DSR ) result |= TIOCM_DTR;

	if( input_status  & IP_RI  ) result |= TIOCM_RNG;
	if( input_status  & IP_DCD ) result |= TIOCM_CAR;
	if( input_status  & IP_RTS ) result |= TIOCM_CTS;
	if( input_status  & IP_DTR ) result |= TIOCM_DSR;

	put_fs_long(result,(unsigned long *) value);

	return 0;
}

static int set_modem_info(struct async_struct * info, unsigned int cmd,
			  unsigned int *value)
{
	unsigned int arg = get_fs_long((unsigned long *) value);
/* FIXME */
	printk("set_modem_info - ooerr - handler broken\n");

	switch (cmd) {
	case TIOCMBIS: 
		if (arg & TIOCM_RTS) {
			info->MCR |= UART_MCR_RTS;
			info->MCR_noint |= UART_MCR_RTS;
		}
		if (arg & TIOCM_DTR) {
			info->MCR |= UART_MCR_DTR;
			info->MCR_noint |= UART_MCR_DTR;
		}
		break;
	case TIOCMBIC:
		if (arg & TIOCM_RTS) {
			info->MCR &= ~UART_MCR_RTS;
			info->MCR_noint &= ~UART_MCR_RTS;
		}
		if (arg & TIOCM_DTR) {
			info->MCR &= ~UART_MCR_DTR;
			info->MCR_noint &= ~UART_MCR_DTR;
		}
		break;
	case TIOCMSET:
		info->MCR = ((info->MCR & ~(UART_MCR_RTS | UART_MCR_DTR))
			     | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
			     | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
		info->MCR_noint = ((info->MCR_noint
				    & ~(UART_MCR_RTS | UART_MCR_DTR))
				   | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
				   | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
		break;
	default:
		return -EINVAL;
	}
	cli();
	si_serial_out(info, UART_MCR, info->MCR);
	sti();
	return 0;
}


/*
 * This routine sends a break character out the serial port.
 */
static void send_break(	struct async_struct * info, int duration)
{
	printk("Send break - handler broken\n");

	if (!info->port)
		return;
	current->state = TASK_INTERRUPTIBLE;
	current->timeout = jiffies + duration;
	cli();
	si_serial_out(info, UART_LCR, si_serial_inp(info, UART_LCR) | UART_LCR_SBC);
	schedule();
	si_serial_out(info, UART_LCR, si_serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
	sti();
}

static int si_ioctl(struct tty_struct *tty, struct file * file,
		    unsigned int cmd, unsigned long arg)
{
	int error;
	struct async_struct * info = tty->driver_data;
	int retval;

	if (si_serial_paranoia_check(info, tty->device, "si_ioctl"))
		return -ENODEV;

	switch (cmd) {

		case TCSBRK:	/* SVID version: non-zero arg --> no break */
			retval = tty_check_change(tty);
			if (retval)
				return retval;
			wait_until_sent(tty, 0);
			if (!arg)
				send_break(info, HZ/4);	/* 1/4 second */
			return 0;
		case TCSBRKP:	/* support for POSIX tcsendbreak() */
			retval = tty_check_change(tty);
			if (retval)
				return retval;
			wait_until_sent(tty, 0);
			send_break(info, arg ? arg*(HZ/10) : HZ/4);
			return 0;
		case TIOCGSOFTCAR:
			error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
			if (error)
				return error;
			put_fs_long(C_CLOCAL(tty) ? 1 : 0,
				    (unsigned long *) arg);
			return 0;
		case TIOCSSOFTCAR:
			arg = get_fs_long((unsigned long *) arg);
			tty->termios->c_cflag =
				((tty->termios->c_cflag & ~CLOCAL) |
				 (arg ? CLOCAL : 0));
			return 0;
		case TIOCMGET:
			error = verify_area(VERIFY_WRITE, (void *) arg,
				sizeof(unsigned int));
			if (error)
				return error;
			return get_modem_info(info, (unsigned int *) arg);
		case TIOCMBIS:
		case TIOCMBIC:
		case TIOCMSET:
			return set_modem_info(info, cmd, (unsigned int *) arg);
		case TIOCGSERIAL:
			error = verify_area(VERIFY_WRITE, (void *) arg,
						sizeof(struct serial_struct));
			if (error)
				return error;
			return get_si_serial_info(info,
					       (struct serial_struct *) arg);
		case TIOCSSERIAL:
			return set_si_serial_info(info,
					       (struct serial_struct *) arg);
		/* FIXME - Not appropriate for this HW ?*/
		case TIOCSERCONFIG:
			return -ENOIOCTLCMD; /* FIXME - Is this correct ret code ? */
			/* Was - ``return do_autoconfig(info);'' */
		case TIOCSERGWILD:
			return -ENOIOCTLCMD; /* FIXME - Is this correct ret code ? */

		/* FIXME - Not appropriate for this HW */
		case TIOCSERSWILD:
			return -ENOIOCTLCMD; /* FIXME - Is this correct ret code ? */
		default:
			return -ENOIOCTLCMD;
		}
	printk("Reached end of IOCTL\n");
	return 0;
}

/* SPA - New function added to spoof the line discipline */
#ifdef 0
 NOT USED YET. This is in preparation for new OPOST handler
int si_get_termios(struct tty_struct * tty, struct termio * termio)
{
	struct async_struct *info = tty->driver_data;
	int line = info->line;	
   int i;
   struct termio tmp_termio;

   i = verify_area(VERIFY_WRITE, termio, sizeof (struct termio));
   if (i)
      return i;

   tmp_termio.c_iflag = si_real_termios[line].c_iflag;
   tmp_termio.c_oflag = si_real_termios[line].c_oflag;
   tmp_termio.c_cflag = si_real_termios[line].c_cflag;
   tmp_termio.c_lflag = si_real_termios[line].c_lflag;
   tmp_termio.c_line  = si_real_termios[line].c_line;

   for(i=0 ; i < NCC ; i++)
      tmp_termio.c_cc[i] = si_real_termios[line].c_cc[i];

   memcpy_tofs(termio, &tmp_termio, sizeof (struct termio));

   return 0;
}
#endif


static void si_set_termios(struct tty_struct *tty, struct termios *old_termios)
{
	struct async_struct *info = tty->driver_data;
   struct si_channel_control *si_chc;
	int line = info->line;	
/* FIXME - REMOVE	int residue; */
   si_chc = si_chan_p[line];


		change_speed(info);

	/* FIXME - Need to check what this is doing.  Don't have time now ;-) */
		if ((old_termios->c_cflag & CRTSCTS) &&
	    	!(tty->termios->c_cflag & CRTSCTS)) {
			tty->hw_stopped = 0;
			si_start(tty);
		}

	/* FIXME - This seems OK. Check later to be sure ! */
		if (!(old_termios->c_cflag & CLOCAL) &&
	    	(tty->termios->c_cflag & CLOCAL))
			wake_up_interruptible(&info->open_wait);

}

/*
 * ------------------------------------------------------------
 * si_close()
 * 
 * This routine is called when the serial port gets closed.  First, we
 * wait for the last remaining data to be sent.  Then, we unlink its
 * async structure from the interrupt chain if necessary, and we free
 * that IRQ if nothing is left in the chain.
 * ------------------------------------------------------------
 */
static void si_close(struct tty_struct *tty, struct file * filp)
{
	struct async_struct * info = tty->driver_data;

	if (!info || si_serial_paranoia_check(info, tty->device, "si_close"))
		return;
	
	if (tty_hung_up_p(filp))
		return;
	
	printk("si_close ");
	printk("ttys%d, count = %d\n", info->line, info->count);

	if ((tty->count == 1) && (info->count != 1)) {
		/*
		 * Uh, oh.  tty->count is 1, which means that the tty
		 * structure will be freed.  Info->count should always
		 * be one in these conditions.  If it's greater than
		 * one, we've got real problems, since it means the
		 * serial port won't be shutdown.
		 */
		printk("si_close: bad serial port count; tty->count is 1, "
		       "info->count is %d\n", info->count);
		info->count = 1;
	}
	if (--info->count < 0) {
		printk("si_close: bad serial port count for ttys%d: %d\n",
		       info->line, info->count);
		info->count = 0;
	}
	if (info->count)
		return;
	info->flags |= ASYNC_CLOSING;
	info->flags &= ~ASYNC_CTS_FLOW;
	/*
	 * Save the termios structure, since this port may have
	 * separate termios for callout and dialin.
	 */
	if (info->flags & ASYNC_NORMAL_ACTIVE)
		info->normal_termios = *tty->termios;
	if (info->flags & ASYNC_CALLOUT_ACTIVE)
		info->callout_termios = *tty->termios;
	tty->stopped = 0;		/* Force flush to succeed */
	tty->hw_stopped = 0;
	if (info->flags & ASYNC_INITIALIZED) {
		si_start(tty);
		wait_until_sent(tty, 6000); /* 60 seconds timeout */
	}
	si_shutdown(info, "si_close");
	if (tty->driver.flush_buffer)
		tty->driver.flush_buffer(tty);
	if (tty->ldisc.flush_buffer)
		tty->ldisc.flush_buffer(tty);
	info->event = 0;
	info->tty = 0;
	if (tty->ldisc.num != ldiscs[N_TTY].num) {
		if (tty->ldisc.close)
			(tty->ldisc.close)(tty);
		tty->ldisc = ldiscs[N_TTY];
		tty->termios->c_line = N_TTY;
		if (tty->ldisc.open)
			(tty->ldisc.open)(tty);
	}
	if (info->blocked_open) {
		if (info->close_delay) {
			tty->count++; /* avoid race condition */
			current->state = TASK_INTERRUPTIBLE;
			current->timeout = jiffies + info->close_delay;
			schedule();
			tty->count--;
		}
		wake_up_interruptible(&info->open_wait);
	}
	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE|
			 ASYNC_CLOSING);
	wake_up_interruptible(&info->close_wait);
}

/*
 * si_hangup() --- called by tty_hangup() when a hangup is signaled.
 */
void si_hangup(struct tty_struct *tty)
{
	struct async_struct * info = tty->driver_data;
	
	if (si_serial_paranoia_check(info, tty->device, "si_hangup"))
		return;
	
	si_shutdown(info, "si_hangup");
	info->event = 0;
	info->count = 0;
	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
	info->tty = 0;
	wake_up_interruptible(&info->open_wait);
}

/*
 * ------------------------------------------------------------
 * si_open() and friends
 * ------------------------------------------------------------
 */
static int block_til_ready(struct tty_struct *tty, struct file * filp,
			   struct async_struct *info)
{
	struct wait_queue wait = { current, NULL };
	int		retval;
	int		do_clocal = C_CLOCAL(tty);
   struct si_channel_control *si_chc;
	int status;


	/*
	 * If the device is in the middle of being closed, then block
	 * until it's done, and then try again.
	 */
	if (info->flags & ASYNC_CLOSING)
	{
		interruptible_sleep_on(&info->close_wait);

#ifdef SERIAL_DO_RESTART
		if (info->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
#else
		return -EAGAIN;
#endif

	}
	/*
	 * If this is a callout device, then just make sure the normal
	 * device isn't being used.
	 */
	if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) {
		if (info->flags & ASYNC_NORMAL_ACTIVE)
			return -EBUSY;
		if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    (info->flags & ASYNC_SESSION_LOCKOUT) &&
		    (info->session != current->session))
		    return -EBUSY;
		if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    (info->flags & ASYNC_PGRP_LOCKOUT) &&
		    (info->pgrp != current->pgrp))
		    return -EBUSY;
		info->flags |= ASYNC_CALLOUT_ACTIVE;
		return 0;
	}
	
	/*
	 * If non-blocking mode is set, then make the check up front
	 * and then exit.
	 */
	if (filp->f_flags & O_NONBLOCK) {
		if (info->flags & ASYNC_CALLOUT_ACTIVE)
			return -EBUSY;
		info->flags |= ASYNC_NORMAL_ACTIVE;
		return 0;
	}

	/*
	 * Block waiting for the carrier detect and the line to become
	 * free (i.e., not in use by the callout).  While we are in
	 * this loop, info->count is dropped by one, so that
	 * si_close() knows when to free things.  We restore it upon
	 * exit, either normal or abnormal.
	 */
	retval = 0;
	add_wait_queue(&info->open_wait, &wait);
/*
	printk("block_til_ready before block: ttys%d, count = %d\n",
	       info->line, info->count);
*/

	info->count--;
	info->blocked_open++;
	while (1) {
		cli();
		if (!(info->flags & ASYNC_CALLOUT_ACTIVE))
			si_serial_out(info, UART_MCR,
				   si_serial_inp(info, UART_MCR) |
				   (UART_MCR_DTR | UART_MCR_RTS));
		sti();
		current->state = TASK_INTERRUPTIBLE;
		if (tty_hung_up_p(filp) ||
		    !(info->flags & ASYNC_INITIALIZED)) {
#ifdef SERIAL_DO_RESTART
			if (info->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
			{
				retval = -ERESTARTSYS;	
			}
#else
			retval = -EAGAIN;
#endif
			break;
		}

   	si_chc = si_chan_p[info->line];
		status = si_chc->hi_ip;

		if (!(info->flags & ASYNC_CALLOUT_ACTIVE) &&
		    !(info->flags & ASYNC_CLOSING))
		{
			if(do_clocal)
			{
/*				printk("break cos do_clocal\n"); */
				break;
			}
			if(status & IP_DCD)
			{
				/*printk("break IP_DCD\n");*/
				break;
			}
		}
		if (current->signal & ~current->blocked)
		{
			retval = -ERESTARTSYS;
			break;
		}
/*
		printk("block_til_ready blocking: ttys%d, count = %d\n",
		       info->line, info->count);
*/
		schedule();
	}
	current->state = TASK_RUNNING;
	remove_wait_queue(&info->open_wait, &wait);
	if (!tty_hung_up_p(filp))
		info->count++;
	info->blocked_open--;


#ifdef SERIAL_DEBUG_OPEN
	printk("block_til_ready after blocking: ttys%d, count = %d\n",
	       info->line, info->count);
#endif
	if (retval)
	{
/*
		printk("ASYNC_CLOSING - retval = %d\n",retval);
*/
		return retval;
	}
	info->flags |= ASYNC_NORMAL_ACTIVE;
	return 0;
}	


/* This delays for a little until the command to the SI-Host has
 * had chance to complete.  It doesn't take long but it's important to do.
 */
int wait_for_stat(struct si_channel_control *si_chc, int st)
{
	unsigned int duration = 2;   /* 2 (100ths of sec) = 20ms */
	unsigned int timeout = 6000; /* 60 seconds, in case something goes wrong... */
	unsigned int total_time = 0;

	do
	{
   	current->state = TASK_INTERRUPTIBLE;
   	current->timeout = jiffies + duration;

   	cli();
   	schedule();
   	sti();

		total_time += duration; /* How long have we waited so far. */
		if(total_time>timeout)  /* Don't wait forever.  */
		{
			printk("ERROR : Timed out waiting for SI-Host command to complete.\n");
			return(1);
		}
   }
	while(si_chc->hi_hstat != st);

	return(0);
}





/*
 * This routine is called whenever a serial port is opened.
 */
int si_open(struct tty_struct *tty, struct file * filp)
{
	struct async_struct	*info;
	int 			retval, line;

	line = MINOR(tty->device) - tty->driver.minor_start;

	if ((line < 0) || (line >= NR_SI_PORTS))
		return -ENODEV;

	info = si_table + line;

	if (si_serial_paranoia_check(info, tty->device, "si_open"))
		return -ENODEV;

	info->count++;
	tty->driver_data = info;
	info->tty = tty;

	if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
		if (tty->driver.subtype == SERIAL_TYPE_NORMAL)
			*tty->termios = info->normal_termios;
		else 
			*tty->termios = info->callout_termios;
	}

	/*
	 * Start up serial port
	 */
	retval = si_startup(info);
	if (retval)
		return retval;

	retval = block_til_ready(tty, filp, info);
	if (retval) {
#ifdef SERIAL_DEBUG_OPEN
		printk("si_open returning after block_til_ready with %d\n",
		       retval);
#endif
		return retval;
	}


	info->session = current->session;
	info->pgrp = current->pgrp;


	return 0;
}

/*
 * ---------------------------------------------------------------------
 * si_init() and friends
 *
 * si_init() is called at boot-time to initialize the serial driver.
 * ---------------------------------------------------------------------
 */

/*
 * This routine prints out the appropriate serial driver version
 * number, and err, that's it.
 */
static void show_si_serial_version(void)
{
#ifdef HAVE_EISA
	printk("%s serial driver version %s configured for EISA bus\n",
		SI_ID_STR, SI_SERIAL_VERSION_STRING);
#else
	printk("%s serial driver version %s configured for ISA bus\n", SI_ID_STR, SI_SERIAL_VERSION_STRING);
#endif
}

/*
 * The serial driver boot-time initialization code!
 */
long si_init(long kmem_start)
{
	int i, iter, module, linnum, num_ports;
	int found=0;
	unsigned char *p, c;
	struct async_struct * info;
	struct si_card_control *si_cc;
/* FIXME - Remove
   struct sigaction  sa;
*/

	struct si_module *si_mod;
	char *si_mod_p;

	bh_base[SERIAL_BH].routine = do_si_serial_bh;

   timer_table[SI_TIMER].fn = si_timer;
   timer_table[SI_TIMER].expires = 0;

	show_si_serial_version();

#ifndef HAVE_EISA
   /* Probe for SI-Host cards ISA signature. */
   printk("Probing for %s Host card...", SI_ID_STR);
   for(p = si_base + SIPLSIG, c=7; p<si_base+SIPLSIG+8; p++, c--)
   {
      if( ( (*p) & 0x07) != c ) /* Mask with 00000111 */
      {
         printk("%s Host card not found.\n", SI_ID_STR);
         return(kmem_start);
      }
   }

   printk("%s Host card found at %x.\n", SI_ID_STR, (unsigned)si_base);

#else
	/* Probe for SI-Host cards EISA signature. */
	for(EISA_SLOT=0x1000; EISA_SLOT<0x10000; EISA_SLOT+=0x1000)
	{
		if((inb(EISA_SLOT+0xc80)==0x4d)&&
			(inb(EISA_SLOT+0xc81)==0x98))
      {
			found=1;
			break;
     	}
	}	

	if(found==0)
	{
		printk(" %s Host card not found on EISA bus.\n", SI_ID_STR);
		return(kmem_start);
	}

/* Obtain SIO base address... */
	si_base = ((inb(0xc01+EISA_SLOT) << 8) + inb(0xc00+EISA_SLOT)) << 16;

   printk("%s Host card found at %x in EISA slot %d.\n",
		SI_ID_STR, (unsigned)si_base, EISA_SLOT>>12);

#endif

#ifdef SI_DOWNLOAD_CODE
   /* Reset card */
#ifndef HAVE_EISA
   si_base[SIPLRESET] = 0x00; 
#endif
#endif

#ifndef HAVE_EISA
   /* Clear interrupts */
   if(si_irq != 0)
   {
      si_base[SIPLIRQCLR] = 0x00; 
   }
#endif

#ifndef HAVE_EISA
   switch(si_irq)
   {
      case 0:
         printk("Using POLLING for %s Host\n", SI_ID_STR);
      break;

      case 11:
#ifdef OLD_REQUEST_IRQ
         if(request_irq(11, si_interrupt))
#else
         if(request_irq(11, si_interrupt, 0, "si_serial"))
#endif
         {
            printk("Can't get IRQ11 for %s Host\n", SI_ID_STR);
            return(kmem_start);
         }
         si_base[SIPLIRQ11] = 0x10;
         printk("Using IRQ11 for %s Host\n", SI_ID_STR);
      break;

      case 12:
#ifdef OLD_REQUEST_IRQ
         if(request_irq(12, si_interrupt))
#else
         if(request_irq(12, si_interrupt, 0, "si_serial"))
#endif
         {
            printk("Can't get IRQ12 for %s Host\n", SI_ID_STR);
            return(kmem_start);
         }
         si_base[SIPLIRQ12] = 0x10;
         printk("Using IRQ12 for %s Host\n", SI_ID_STR);
      break;

      case 15:
#ifdef OLD_REQUEST_IRQ
         if(request_irq(15, si_interrupt))
#else
         if(request_irq(15, si_interrupt, 0, "si_serial"))
#endif
         {
            printk("Can't get IRQ15 for %s Host\n", SI_ID_STR);
            return(kmem_start);
         }
         si_base[SIPLIRQ15] = 0x10;
         printk("Using IRQ15 for %s Host\n", SI_ID_STR);
      break;

      default:
         printk("Invalid IRQ (%d) for %s Host. Must be 0, 11, 12 or 15\n",
				si_irq, SI_ID_STR);
         return(kmem_start);
      break;
   }
#else /* Set IRQ for SI-Host on EISA bus. */
     
	if( si_irq != 0 ) /* Don't grab IRQ 0 - We're polling */
	{
#ifdef OLD_REQUEST_IRQ
   	if(request_irq(si_irq, si_interrupt))
#else
   	if(request_irq(si_irq, si_interrupt, 0, "si_serial"))
#endif
   	{
      	printk("Can't get IRQ%d for %s Host\n", si_irq, SI_ID_STR);
      	return(kmem_start);
   	}
	}

	/* Combined set-irq and reset card. It's OK to use IRQ 0 here - the
	 * card interprets this as polling.
	 */
	outb( si_irq<<4 , 0xc02+EISA_SLOT);
   printk("Using IRQ%d for %s Host\n", si_irq, SI_ID_STR);

#endif

#ifdef SI_DOWNLOAD_CODE

#ifdef HAVE_EISA
/* Tell card download code that it's running on an EISA bus card */
	z280code[0x42] = 1;
#endif
   /* Download code to card */
   printk("Ports (in download code) = %d\n", (int) z280code[0x40]);
   for(i=0; i<sizeof(z280code) ; i++)
      si_base[i] = z280code[i];


/* Start card */

#ifdef HAVE_EISA
	outb( (si_irq<<4)|4 , 0xc02+EISA_SLOT);
#else
   si_base[SIPLRESET] = 0x10; 
#endif

#endif

   /* Enable card interrupts */
#ifdef HAVE_EISA
	inb(EISA_SLOT+0xc03);
#else
   si_base[SIPLIRQCLR] = 0x10; 
   si_base[SIPLIRQSET] = 0x10; 
#endif

/* FIXME - Busy waiting like this ????? */

   /* Check cards status */
   for(i=0; i<150000; i++)
      continue;

   for(iter=0; si_base[0] == 0; iter++ )
   {
      for(i=0; i<150000; i++)
         continue;

      if(iter>50)
      {
         printk("\nError : %s Host card failed to initialise\n", SI_ID_STR);
         return(kmem_start);
      }
   }

   switch(si_base[0])
   {
      case 0xff:
         printk("\nError : No %s I/O modules found.\n", SI_ID_STR);
         return(kmem_start);
      break;

      case 0x01:
         /* This message doesn't make too much sense...! */
         printk("%s I/O module(s) found.\n", SI_ID_STR);
      break;

      default:
         printk("\nError : %s Host Unknown status - %x.\n",
				SI_ID_STR, si_base[0]);
         return(kmem_start);
      break;
   }

   /* Set up pointer to card control structure */
   si_cc = (struct si_card_control *) si_base;

   printk("%s Host OK. Card has %dK RAM Firmware Rev. %d.%d\n", SI_ID_STR,
      si_cc->mem_size, ((si_cc->revision) >> 8)&0xff, (si_cc->revision)&0xff);


   /* Interrupt register Int's per second= 25E+6/(8*int_count) */ 
   si_cc->int_count = INT_COUNT; /* FIXME Was 25000 initial value */

   /* RX interrupt throttle */
   si_cc->rx_int_count = RX_INT_COUNT; 


	/* Initialize the tty_driver structure */
	
	memset(&si_serial_driver, 0, sizeof(struct tty_driver));
	si_serial_driver.magic = TTY_DRIVER_MAGIC;
	si_serial_driver.name = "stt";
	si_serial_driver.major = SI_TTY_MAJOR;
	si_serial_driver.minor_start = SI_TTY_MINOR_START; /* Was 64 */
	si_serial_driver.num = NR_SI_PORTS;
	si_serial_driver.type = TTY_DRIVER_TYPE_SERIAL;
	si_serial_driver.subtype = SERIAL_TYPE_NORMAL;
	si_serial_driver.init_termios = tty_std_termios;
	si_serial_driver.init_termios.c_cflag =
		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	si_serial_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_HW_ONLCR;
	si_serial_driver.refcount = &si_serial_refcount;
	si_serial_driver.table = si_serial_table;
	si_serial_driver.termios = si_serial_termios;
	si_serial_driver.termios_locked = si_serial_termios_locked;

	si_serial_driver.open = si_open;
	si_serial_driver.close = si_close;
	si_serial_driver.write = si_write;
	si_serial_driver.put_char = si_put_char;
	si_serial_driver.flush_chars = si_flush_chars;
	si_serial_driver.write_room = si_write_room;
	si_serial_driver.chars_in_buffer = si_chars_in_buffer;
	si_serial_driver.flush_buffer = si_flush_buffer;
	si_serial_driver.ioctl = si_ioctl;
	si_serial_driver.throttle = 0;   /* si_throttle; FIXME */
	si_serial_driver.unthrottle = 0; /* si_unthrottle; FIXME */
	si_serial_driver.set_termios = si_set_termios;

/* FIXME - SPA awaiting the wisdom of Ted...*/
/*
	si_serial_driver.get_termios = si_get_termios;
*/

	si_serial_driver.stop = si_stop;
	si_serial_driver.start = si_start;
	si_serial_driver.hangup = si_hangup;

	/*
	 * The callout device is just like normal device except for
	 * major number and the subtype code.
	 */
	si_callout_driver = si_serial_driver;
	si_callout_driver.name = "cua";
	si_callout_driver.major = SI_TTYAUX_MAJOR;
	si_callout_driver.subtype = SERIAL_TYPE_CALLOUT;

	if (tty_register_driver(&si_serial_driver))
		panic("Couldn't register SI-Host serial driver\n");

	if (tty_register_driver(&si_callout_driver))
		panic("Couldn't register SI-Host callout driver\n");

   printk("Initialize per chan info - %d ports configured...\n", NR_SI_PORTS);

/* Initialize the per channel info...
 * This is ALL subject to change if/when I've sorted out a SI-Host
 * version of async_struct...
 */
   linnum = 0;
   /* Initial module to offset 0x80 */
   si_mod_p = si_base+0x80;
   for(module = 0; module<4; module++)
   {
      si_mod = (struct si_module *) si_mod_p;

      switch(((si_mod->type)&0xe0)) /* Mask 11100000 */
      {
         default:
            printk("Module number %d type(%x) unknown.\n", module,si_mod->type);
         break;

         case MODULE_RS422:
            printk("This version of the %s Host driver doesn't support\n", SI_ID_STR);
            printk("RS-422 interfaces. Module number %d.\n", module);
         break;
         case MODULE_SYNC:
            printk("This version of the %s Host driver doesn't support\n", SI_ID_STR);
            printk("8530 Sync interfaces. Module number %d.\n", module);
         break;
         case MODULE_CENT:
            printk("This version of the %s Host driver doesn't support\n", SI_ID_STR);
            printk("Centronics interfaces. Module number %d.\n", module);
         break;
         case MODULE_NET:
            printk("This version of the %s Host driver doesn't support\n", SI_ID_STR);
            printk("Network interfaces. Module number %d.\n", module);
         break;

         case MODULE_RS232:
         num_ports = si_mod->type & 0x1f;
         if( num_ports != 4 && num_ports!=8)
         {
            printk("Error:Module #%d has %d ports (Should be 4 or 8).\n",
               module,num_ports);
            break;
         }

         printk("Module #%d has %d RS-232 ports. (Type=%x)\n",
				module, num_ports, si_mod->type);

         /* Initialise each port found... */
         for (i = 0, info = si_table; i < num_ports; i++,info++)
         {
/*   REMOVE si_async_str[linnum] = info; */
            /* FIXME - This needs to be looked at properly. */
            si_chan_p[linnum] = (char *) (si_mod_p + 0x100 + (0x300*i));
				info->magic = SI_SERIAL_MAGIC;
				info->line = linnum;
				info->tty = 0;
				info->type = PORT_SI_HOST;
				info->custom_divisor = 0;
				info->close_delay = 50;
				info->x_char = 0;
				info->event = 0;
				info->count = 0;
				info->blocked_open = 0;
				info->tqueue.routine = do_softint;
				info->tqueue.data = info;
				info->callout_termios =si_callout_driver.init_termios;
				info->normal_termios = si_serial_driver.init_termios;
				info->open_wait = 0;
				info->close_wait = 0;
				info->next_port = 0;
				info->prev_port = 0;
            info->xmit_fifo_size = 256;
            linnum++;
            si_channels_configured++;
         }
         break;
      }

      if(si_mod->next==0) /* No more modules. */
      {
         printk("No more modules to do.\n");
         break;
      }

      /* Next module is at 0x300 times number of ports found + 0x100 !... */
      /* si_mod_p += (num_ports*0x300+0x100); or...*/

      si_mod_p = (char *)si_mod->next;

   }

	ports_present = linnum;

   printk("Finished %s Host initialisation (%d ports total).\n",
		SI_ID_STR, ports_present);

	return kmem_start;
}

#else /* ifdef CONFIG_SI_SERIAL */
long si_init(long kmem_start)
{
	printk("%s HOST SERIAL DRIVER NOT CONFIGURED IN.\n", SI_ID_STR);
	return kmem_start;
}
#endif
/* END OF FILE */
