#define SI_SERIAL_VERSION_STRING "0.5.9a"

/*
 *  For use with Linux V1.2.0 and above.
 *
 *  linux/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 total of 32 on 4 TA's.
 *
 *  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 it was based heavily on serial.c
 *  for which credit & thanks go to (among others) Linus Torvalds and Ted Ts'o.
 *
 *
 *  *** WARNING *** *** WARNING *** *** WARNING *** *** WARNING ***  
 *
 *  Alpha code. Use at your own risk.
 *  It may possibly contain security holes. Etc, etc...
 *
 *  Don't say you haven't been warned !!
 *
 * ****************************************************************************
 * ****************************************************************************
 *
 *  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 very mature so it is a near certainty
 *  that any problems you encounter will be caused by this driver or other
 *  hardware or software 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.
 *   Fixed SERIAL_BH foolishness. Should now be possible to use this driver
 *   with the normal serial driver.
 *   THIS VERSION HAS BROKEN ISA IRQ SUPPORT!
 *
 *  Version 0.5.8 - 17.4.95
 *   Fixed ISA hardware IRQ support. Must have been broken by EISA mods.
 *   Input break processing fixed.
 *   Signal processing fixed.
 *   Output break processing added.
 *   Sever bug in receiver code fixed. Flip buffer update not atomic - all sorts
 *   of havoc stopped ;-) Important because we run isr with interrupts ON!
 *   Fixed elusive bug in startup code. Symptoms were no interrupts from card
 *   on *warm boot only*. Only spotted it by chance.
 *   Added debugging stats and command ioctls(). Very sketchy and subject to
 *   change at the drop of a hat although they would be nice to keep in one
 *   form or another for the production driver.
 *   Cleaned up line discipline interaction.  Output *and* input cooking in hardware
 *   is now supported.
 *   Fixed bug that caused NULL deref kernel trap when killing a processes that was
 *   waiting for a port to open.
 *   Initialisation banner rationalised - now only 2 lines instead of four hundred...
 *   Cleaned up diagnostic output.
 *
 *  Version 0.5.9 - 26.4.95
 *   Numerous minor updates to allow driver to run with the new 1.2.x kernels.
 *
 *  Version 0.5.9.a - 1.5.95
 *   Non-public release to trial MK1 card mods - Define HAVE_MK1_SIO in the
 *   file si_serial.h to enable MK1 card support.
 *   Support for old style request_irq() removed.
 *
 *
 *  TO DO : (These are notes for my own reference more than anything)
 *
 *   - Fix bug in si_init() that registers all 32 ports even when we have
 *     less than the maximum.
 *
 *   - Redesign driver to allow direct reading and writing the buffers on the
 *     card. At the moment writing goes through a 4K kernel buffer which may
 *     be unnecessary. This can be changed as soon as I have time to do it.
 *     Reading is more difficult because the code that needs to be
 *     changed is in n_tty.c. specifically, the wakeup() when enough
 *     characters are available is done in n_tty.c so we're a bit stuck there
 *     for now. Potentially good performance gains for receiving could be
 *     had if this is done (possibly;-).
 *
 *   - Interrupts are NOT enabled on entry to si_interrupt even though we
 *     *don't* specify SA_INTERRUPT in setup. Adding sti() to the head of
 *     the isr makes this work properly. Also adding a couple of sti's in
 *     the assembler before do_IRQ() seems to solve this too?!
 *
 *   - Output stops unexplainably on some ports when all 32 ports are being
 *     run together.  If Ctrl-C is pressed (yes Ctrl-C!) on a terminal connected
 *     to that port, output starts up again for a bit then stops again.  The
 *     other ports don't start when you do this which suggests it's *not*
 *     interrupt related. I think this is a bug outside of the driver.
 *     If anyone else sees this let me know the kernel version you're using.
 *
 *   - Shutdown code is broken. When a process is killed - if it isn't the
 *     only process on a port then the kernel emmits NULL pointer deref error.
 *     *** FIXED ***
 *
 *     We sometimes get a command timeout when toggling DCD very quickly - weird!
 *     *** NOT FIXED (but I've not seen this recently either;-) ***
 *
 *   - Problem with calling check_modem with NULL info->tty.
 *     This is fixed by rearangement of DCD checks.
 *
 *   - Need to relook at draining. particularly split si_shutdown()
 *
 *   - hardware drain does not work now. I commented it out to fix the
 *     problem with being called in soft_int
 *
 *   - SLIP & PPP tested but not very much!  Switching line disciplines
 *     will probably be broken for a start...
 *
 *   - Fixes needed due to EISA patches.
 *       - Remove HAVE_EISA in favour of EISA_bus but check depend first.
 *         Can't do this anyway because we might have SIO(ISA) in EISA bus PC :-(
 *
 *   - Could be problem with drain_hardware() timing out while in XOFF
 *     condition.
 *
 *   - Need cleanup of code in conditionals for SIO Mk1, SIO MK2 and ISA/EISA.
 *     There are far too many #defs... :-(
 *
 * Acknowledgements.
 *  Many thanks to Specialix Research for providing documentation for the
 *  hardware and for their kind permission to allow distribution of the
 *  download code with the driver (See si_download.c for copyright conditions).
 *
 *  Thanks to for all the testers who have helped - you know who you are.
 *
 *  Thanks to Russel Brown for generous help with EISA support
 *  (I don't have any EISA H/W myself :-)
 *
 *
 * This module exports the function(s):
 *
 *	long si_init(long);
 */


#include <linux/config.h>

#define CONFIG_SI_SERIAL /* FIXME - Remove when we have this in config */

#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 <linux/mm.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);

/* 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;

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

#define SERIAL_PARANOIA_CHECK
#define SERIAL_DO_RESTART

#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);
static void si_set_hw_cooking_flags(struct tty_struct *tty);
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 just uses a linear list.
 */
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];

static void si_interrupt_redir(int irq, struct pt_regs *regs);

char stats_rxcount[32];
char stats_txcount[32];

#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;
}

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

	info = tty->driver_data;

	/* printk("si_stop called!\n"); */

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

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

	info = tty->driver_data;

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

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

		cli();

		tty->flip.count++;

		*tty->flip.flag_buf_ptr++ = 0;
		*tty->flip.char_buf_ptr++ = ch;

		sti();
	}


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

#ifdef 0
	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");
#endif
	in_rx = 0;
}

static _INLINE_ void si_transmit_chars(struct async_struct *info)
{
   int count;
   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];

	/* Store number of chars in hardware buffer in stats table.
	 * This is done here on the grounds that it is most useful to see
	 * the state of the hardware tx buffer at the worst possible time!
	 * ie. just before it is time to service it again.
	 */
	stats_txcount[line] = (si_chc->hi_txipos - si_chc->hi_txopos) & 0xff;
	stats_rxcount[line]++; /* Used as check that we get here for now */

   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++];

         si_chc->hi_txipos = txipos + 1;
#ifdef 0
         if(si_chc->hi_txipos == txipos)
         	printk("Aieeee, port locked, line=%d!!!\n", line);
#endif
			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)
	{
		/* printk("xmit_cnt=%d - wakeup called\n", info->xmit_cnt); */
		si_sched_event(info, SI_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... */

/*	FIXME - URGENT - this condition happens too often it is broken :-(
	printk("Modem HI_STATE = %d\n", modem_change); */ 

	if(modem_change & ST_BREAK)
	{
		printk("Break key pressed on %s, port %d.\n", SI_ID_STR, line); 
		si_chc->hi_state &= ~ST_BREAK; /* I agree... */
		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 this than flakey.
    */

	current_dcd = si_chc->hi_ip & IP_DCD;

	if( (info->flags & ASYNC_CHECK_CD) && (modem_change & ST_DCD))
	{ /* Change in Modem signal states */
		si_chc->hi_state &= ~ST_DCD; /* Clear DCD changed flag */
		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, SI_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, struct pt_regs *regs)
{
/* This sti() helps keep the system clock right!  This should already have been done
 * in the interrupt setup phase and looking at the assembler code it looks as
 * if it is but somehow it appears that interrups are STILL staying off!
 * Perhaps someone can do a bit of disassembly or something and see what
 * is going on.  Please drop me a line if you find out...
 */
	sti(); /* !!! */


	si_interrupt_redir(irq, (struct pt_regs *)0);
	return;
}


static void si_interrupt_redir(int irq, struct pt_regs *regs)
{
   int chars_in_buff;
   int chan;
   struct si_channel_control *si_chc;
   struct async_struct * info;
   int done;
   struct si_card_control *si_cc;
	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) /* Debugging stuff - some of this should go away... */
	{
		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:
			/* printk("interrupt from vector %d\n", irq); */
   		if(si_irq != 0)
   		{
      		si_cc->int_pending = 0;
#ifdef HAVE_EISA
				inb(0xc03+EISA_SLOT);
#else
#ifdef HAVE_MK1_SIO
BOLL
      		si_base[SIMK1IRQCLR] = 0x00; 
      		si_base[SIMK1IRQCLRCLR] = 0x00;
#else
      		si_base[SIPLIRQCLR] = 0x00; 
      		si_base[SIPLIRQCLR] = 0x10;
#endif
#endif
   		}
			start_timer = TRUE;
#ifdef 0
			if(in_isr == 1) /* Ask Buzz about this one. */
				printk("si_serial : 1201 Program Alarm.\n");
#endif
		break;
	}

	if(in_isr == 1)
		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);
	}

   /* Check all Tx queues and all Rx queues */

   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;			
      }
   }

   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;			
      }
   }

	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;

}

/*
 * 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(SI_EVENT_HANGUP, &info->event)) {
		tty_hangup(tty);
		wake_up_interruptible(&info->open_wait);
		info->flags &= ~(ASYNC_NORMAL_ACTIVE|
				 ASYNC_CALLOUT_ACTIVE);
	}

	if (clear_bit(SI_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.
 */
static void si_timer(void)
{
	/* If ports are active simulate an int otherwise just spin the timer again */
	if(si_ports_enabled)
	{
		si_interrupt(INT_FROM_TIMER, (struct pt_regs *)0);
	}
	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;
}

/*
 * 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); */

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

	line = info->line;

   si_chc = si_chan_p[line];

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

	if(info->xmit_buf)
	{
		free_page((unsigned long) info->xmit_buf);
		info->xmit_buf = 0;
	}

/* 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 as it were...)
 */

#ifdef FIXME_DRAIN_DOWN 
	residue = drain_hardware_tx(info);
#endif

   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("%s : Line %d, Residue!=0, Forcing hardware to close\n", SI_ID_STR, line);
		si_chc->hi_hstat = FORCE_CLOSED; /* Force close */
	}
	else
	{
		/* printk("Sending close to H/W\n"); */
		si_chc->hi_hstat = CLOSE; /* Normal close */
	}

	info->flags &= ~ASYNC_INITIALIZED;

	return ;
}

/* This function waits for the output buffer to drain.  It uses a delay based
 * on the current baud rate and the number of characters left in the buffer.
 */
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
	{
		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("%s : Line %d, Timed out waiting for H/W output to drain.\n",
				SI_ID_STR, line);
			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 >= si_channels_configured)
      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... !
 */
	/* printk("Entered change_speed()\n"); */
	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)
	{
		case B0		: /* Ignore... */ ; 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)) /* We ALWAYS do flow control in the hardware. */
  		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) && (!(I_OTHER(tty))))
      si_chc->hi_mask &= 0x7F;

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

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

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

	if(I_BRKINT(tty) && (!(I_OTHER(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;


	/* Finally, set the hardware cooking flags for n_tty to see. */
	si_set_hw_cooking_flags(tty);

   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;

	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, (struct pt_regs*)0 );
#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;

	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("%s : NEGATIVE INC in si_write() on port %d\n", SI_ID_STR, info->line);
		info->xmit_cnt += c;

		sti();
		buf += c;
		count -= c;
		total += c;
	}

/* 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, (struct pt_regs*)0); /* 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("%s : Negative return value in si_write_room()!!\n", SI_ID_STR);

	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
	 * tty_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
	 * tty_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_ioctl() and friends
 * ------------------------------------------------------------
 */

/* FIXME - Debugging info for now - used to gather statistics for performance
 * monitoring.
 */

static int get_si_serial_hwdata(struct async_struct *info,
			   struct si_hardware_struct *hw)
{
	struct si_hardware_struct tmp;
	struct si_channel_control *si_chc;
	struct si_card_control *si_cc;
	int line;

	if (!hw)
		return -EFAULT;

	/* Do we need to be super user to gather stats? */

   si_cc = (struct si_card_control *) si_base;
	line = info->line;
	si_chc = si_chan_p[line];

	memset(&tmp, 0, sizeof(tmp));

	tmp.rx_chars = stats_rxcount[line];
	tmp.tx_chars = stats_txcount[line];

	tmp.int_count = si_cc->int_count;

	memcpy_tofs(hw,&tmp,sizeof(*hw));
	return 0;
}

static int set_si_serial_hwdata(struct async_struct * info,
			   struct si_hardware_struct *new_hw)
{
	struct si_hardware_struct tmp;
	struct si_card_control *si_cc;

	if (!new_hw)
		return -EFAULT;

	if (!suser())
	{
		return -EPERM;
	}

   si_cc = (struct si_card_control *) si_base;

	memcpy_fromfs(&tmp,new_hw,sizeof(new_hw));

	/* Debugging - Change interrupt throttle count  - used for tuning
	 * the card while it is running. FIXME - we might do a sanity check here too...
	 */
	si_cc->int_count = tmp.int_count;

	return 0;
}



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

	/* FIXME - Is this routine needed ? */
	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)
{
	/* 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 ? */

#ifdef 0
	struct serial_struct new_serial;
	struct async_struct old_info;
	unsigned int		i,change_irq,change_port;
	int 			retval = 0;

	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;

#endif

}

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];

/* 
 * The SI-Host is configured as DCE (Data Comms Equip.) rather than the usual
 * Unix 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)
{
#ifdef 0
	unsigned int arg = get_fs_long((unsigned long *) value);
#endif
/* FIXME */
	printk("%s : Warning!! set_modem_info() called - No handler\n", SI_ID_STR);

	return 0;

#ifdef 0
	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;
	}

	return 0;

#endif

}


/*
 * Send break level for 'duration' on specified port.
 */
static void send_break(	struct async_struct * info, int duration)
{
   struct si_channel_control *si_chc;
	int line = info->line;	
   si_chc = si_chan_p[line];

#ifdef 0
	printk("%s : Output Start break on line %d for duration %d....", SI_ID_STR, line, duration);
#endif

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

	cli();
   si_chc->hi_hstat = SBREAK; /* Start break... */
	schedule();
   si_chc->hi_hstat = EBREAK; /* and stop break. */
	sti();

#ifdef 0
	printk("stop break\n");
#endif

}

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;
			tty_wait_until_sent(tty, 0);
			if (!arg)
			{
				drain_hardware_tx(info);
				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;
			tty_wait_until_sent(tty, 0);
			drain_hardware_tx(info);
			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 TIOCGSISERIAL: /* Debugging - subject to change - SPA */
			error = verify_area(VERIFY_WRITE, (void *) arg,
						sizeof(struct si_hardware_struct));
			if (error)
				return error;
			return get_si_serial_hwdata(info,
					       (struct si_hardware_struct *) arg);

		case TIOCSSISERIAL: /* Debugging - subject to change - SPA */
			return set_si_serial_hwdata(info,
					       (struct si_hardware_struct *) 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("%s : Assertion error - Reached end of IOCTL\n", SI_ID_STR);
	return 0;
}

static void si_set_hw_cooking_flags(struct tty_struct *tty)
{

/* Tell line discipline whether we will do input cooking */
   if(I_OTHER(tty))
   {
		clear_bit(TTY_HW_COOK_IN, &tty->flags);
   }
   else
   {
		set_bit(TTY_HW_COOK_IN, &tty->flags);
   }

/* Tell line discipline whether we will do output cooking.
 * If OPOST is set and no other output flags are set then we can do output
 * processing.  Even if only *one* other flag in the O_OTHER group is set
 * we do cooking in software.
 */
   if(O_OPOST(tty) && !O_OTHER(tty))
   {
		set_bit(TTY_HW_COOK_OUT, &tty->flags);
   }
   else
   {
		clear_bit(TTY_HW_COOK_OUT, &tty->flags);
	}

}

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;	
   si_chc = si_chan_p[line];

	si_set_hw_cooking_flags(tty); /* FIXME - change to tty->flags */

	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)
	{
		/* printk("Closing but not freeing structs because info->count=0x%x\n",
			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);
		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);

#ifdef 0
	printk("block_til_ready before block: ttys%d, count = %d\n",
	       info->line, info->count);
#endif

	info->count--;
	info->blocked_open++;
	/* printk("Waiting for DCD... info->count=0x%x, tty->count=0x%x\n",
		info->count, tty->count); */
	while (1) {

		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 wait because do_clocal\n");  */
				break;
			}
			if(status & IP_DCD)
			{
				/* printk("break wait because IP_DCD\n"); */
				break;
			}
		}
		if (current->signal & ~current->blocked)
		{
			/* printk("SIGNAL REC in block_til_ready()\n"); */
			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.  */
		{
		/* This message isn't strictly correct as a signal will break this... */
			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 >= si_channels_configured))
		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.
 * ---------------------------------------------------------------------
 */


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

	bh_base[SI_SERIAL_BH].routine = do_si_serial_bh;
	enable_bh(SI_SERIAL_BH);

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


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

#endif

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

#ifndef HAVE_EISA
   /* Clear interrupts */
   if(si_irq != 0)
   {
#ifdef HAVE_MK1_SIO
      si_base[SIMK1IRQCLR] = 0x00; 
#else
      si_base[SIPLIRQCLR] = 0x00; 
      si_base[SIPLIRQSET] = 0x10; 
#endif
   }
#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 */

/* FIXME - we could check the cards RAM at this point by reading back
 * the byte we've just written... Something to think about.
 */
   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 /* NOT EISA */

#ifdef HAVE_MK1_SIO
   si_base[SIMK1RESETCLR] = 0x00; 
#else
   si_base[SIPLRESET] = 0x10; 
#endif

#endif /* End NOT EISA */

#endif /* End SI_DOWNLOAD_CODE  */

#ifndef HAVE_EISA
   switch(si_irq)
   {
      case 0:
      	/* Nothing... */
      break;

      case 11:
         if(request_irq(11, si_interrupt, 0, "si_serial"))
         {
            printk("%s : Error. Can't get IRQ-11\n", SI_ID_STR);
            return(kmem_start);
         }
#ifndef HAVE_MK1_SIO
         si_base[SIPLIRQ11] = 0x10;
#endif
      break;

      case 12:
         if(request_irq(12, si_interrupt, 0, "si_serial"))
         {
            printk("%s : Error. Can't get IRQ-12\n", SI_ID_STR);
            return(kmem_start);
         }
#ifndef HAVE_MK1_SIO
         si_base[SIPLIRQ12] = 0x10;
#endif
      break;

      case 15:
         if(request_irq(15, si_interrupt, 0, "si_serial"))
         {
            printk("%s : Error. Can't get IRQ-15\n", SI_ID_STR);
            return(kmem_start);
         }
#ifndef HAVE_MK1_SIO
         si_base[SIPLIRQ15] = 0x10;
#endif
      break;

      default:
         printk("%s : Error. Invalid IRQ (%d). Must be 0, 11, 12 or 15\n",
				SI_ID_STR, si_irq);
         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 */
	{
   	if(request_irq(si_irq, si_interrupt, 0, "si_serial"))
   	{
      	printk("%s : Error. Can't get IRQ-%d\n", SI_ID_STR, si_irq);
      	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);

#endif /* Set IRQ on EISA bus */ 


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

/* Busy wait for card to initialise....  */
   /* 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("%s : Error. Host card failed to initialise\n", SI_ID_STR);
         return(kmem_start);
      }
   }

   switch(si_base[0])
   {
      case 0xff:
         printk("%s : Error. No Terminal Adapter modules found.\n", SI_ID_STR);
         return(kmem_start);
      break;

      case 0x01:
         /* Card and modules OK here... */
      break;

      default:
         printk("%s : Error. Unknown status after initialise command - %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;


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

   /* 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 */

/**************************************************************************
 * FIXME URGENT! - This should reflect the number of ports detected
 * while probing for modules!   I can't be bothered to do it now
 * as it requires me to move some code about - namely the tty_register_driver
 * calls AND this line need to be done AFTER the modules have been scanned.
 * Not difficult, but heck, I'm just lazy...
 **************************************************************************/
	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;
	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;

	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;

/* FIXME - We could just print a warning here instead of throwing a complete
 * wobbler but we'd have to do some cleaning up before continuing...
 */
	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");

/* 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("%s : Error. TA Module number %d type(%x) unknown.\n",
					SI_ID_STR, module,si_mod->type);
         break;

         case MODULE_RS422:
            printk("Warning, 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("Warning, 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("Warning, 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("Warning, 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("%s : Error. TA Module #%d has %d ports (Should be 4 or 8).\n",
               SI_ID_STR, module,num_ports);
            break;
         }

         /* Initialise each port found... */
         for (i = 0, info = &si_table[linnum]; i < num_ports; i++,info++)
         {
            si_chan_p[linnum] = (struct si_channel_control *)
					(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;
      }

		num_modules++;

      if(si_mod->next==0) /* No more modules. */
      {
         break;
      }

      /* Next module is at 0x300 times number of ports found + 0x100 !... */
      /* si_mod_p = (char *)si_mod->next; */

      si_mod_p += (num_ports*0x300+0x100);
   }

	ports_present = linnum;

#ifdef HAVE_EISA
	printk("%s : Intelligent Multiport Serial System on EISA Bus (Slot %d).\n",
		SI_ID_STR,
		EISA_SLOT>>12);
#else
	printk("%s : Intelligent Multiport Serial System on ISA Bus.\n", SI_ID_STR);
#endif

	printk("%s : Ver=%s, F/W Ver=%d.%d, RAM=%dK, Base=0x%x, IRQ=%d, Ports=%d(%d)\n",
		SI_ID_STR,
		SI_SERIAL_VERSION_STRING,
      ((si_cc->revision) >> 8)&0xff, (si_cc->revision)&0xff,
      si_cc->mem_size,
      (int)si_base,
      si_irq,
		si_channels_configured,
   	num_modules);

	return kmem_start;
}

#else /* ifdef CONFIG_SI_SERIAL */
long si_init(long kmem_start)
{
	/* This is just a warning and will probably disappear when SIO driver is in the
	 * Kernel proper.
	 */
	printk("ERROR!!  HOST SERIAL DRIVER NOT CONFIGURED IN.\n");
	return kmem_start;
}
#endif



/* END OF FILE */



