/*
 *  This file is in2000.c, written and
 *  Copyright (C) 1993  Brad McLean
 *	Last edit 1/19/95 TZ
 * Disclaimer:
 * Note:  This is ugly.  I know it, I wrote it, but my whole
 * focus was on getting the damn thing up and out quickly.
 * Future stuff that would be nice:  Command chaining, and
 * a local queue of commands would speed stuff up considerably.
 * Disconnection needs some supporting code.  All of this
 * is beyond the scope of what I wanted to address, but if you
 * have time and patience, more power to you.
 * Also, there are some constants scattered throughout that
 * should have defines, and I should have built functions to
 * address the registers on the WD chip.
 * Oh well, I'm out of time for this project.
 * The one good thing to be said is that you can use the card.
 */

/*
 * This module was updated by Shaun Savage first on 5-13-93
 * At that time the write was fixed, irq detection, and some
 * timing stuff.  since that time other problems were fixed.
 * On 7-20-93 this file was updated for patch level 11
 * There are still problems with it but it work on 95% of
 * the machines.  There are still problems with it working with
 * IDE drives, as swap drive and HD that support reselection.
 * But for most people it will work.
 */
/* More changes by Bill Earnest, wde@aluxpo.att.com
 * through 4/07/94. Includes rewrites of FIFO routines,
 * length-limited commands to make swap partitions work.
 * Merged the changes released by Larry Doolittle, based on input
 * from Jon Luckey, Roger Sunshine, John Shifflett. The FAST_FIFO
 * doesn't work for me. Scatter-gather code from Eric. The change to
 * an IF stmt. in the interrupt routine finally made it stable.
 * Limiting swap request size patch to ll_rw_blk.c not needed now.
 * Please ignore the clutter of debug stmts., pretty can come later.
 */
/* Merged code from Matt Postiff improving the auto-sense validation
 * for all I/O addresses. Some reports of problems still come in, but
 * have been unable to reproduce or localize the cause. Some are from
 * LUN > 0 problems, but that is not host specific. Now 6/6/94.
 */
/* Changes for 1.1.28 kernel made 7/19/94, code not affected. (WDE)
 */
/* Changes for 1.1.43+ kernels made 8/25/94, code added to check for
 * new BIOS version, derived by jshiffle@netcom.com. (WDE)
 *
 * 1/7/95 Fix from Peter Lu (swift@world.std.com) for datalen vs. dataptr
 * logic, much more stable under load.
 *
 * 1/19/95 (zerucha@shell.portal.com) Added module and biosparam support for
 * larger SCSI hard drives (untested).
 */

#include <linux/kernel.h>
#include <linux/head.h>
#include <linux/types.h>
#include <linux/string.h>

#include <linux/sched.h>
#include <asm/dma.h>

#include <asm/system.h>
#include <asm/io.h>
#include "../block/blk.h"
#include "scsi.h"
#include "hosts.h"
#include "sd.h"

#include "in2000.h"

/*#define FAST_FIFO_IO*/

/*#define DEBUG*/
#ifdef DEBUG
#define DEB(x) x
#else
#define DEB(x)
#endif

/* These functions are based on include/asm/io.h */
#ifndef inw
inline static unsigned short inw( unsigned short port )
{
   unsigned short _v;
   
   __asm__ volatile ("inw %1,%0"
		     :"=a" (_v):"d" ((unsigned short) port));
   return _v;
}
#endif

#ifndef outw
inline static void outw( unsigned short value, unsigned short port )
{
   __asm__ volatile ("outw %0,%1"
			: /* no outputs */
			:"a" ((unsigned short) value),
			"d" ((unsigned short) port));
}
#endif

/* These functions are lifted from drivers/block/hd.c */

#define port_read(port,buf,nr) \
__asm__("cld;rep;insw": :"d" (port),"D" (buf),"c" (nr):"cx","di")

#define port_write(port,buf,nr) \
__asm__("cld;rep;outsw": :"d" (port),"S" (buf),"c" (nr):"cx","si")

static unsigned int base;
static unsigned int ficmsk;
static unsigned char irq_level;
static int in2000_datalen;
static unsigned int in2000_nsegment;
static unsigned int in2000_current_segment;
static unsigned short *in2000_dataptr;
static char	in2000_datawrite;
static struct scatterlist * in2000_scatter;
static Scsi_Cmnd *in2000_SCptr = 0;

static void (*in2000_done)(Scsi_Cmnd *);

static int in2000_test_port(int index)
{
    static const int *bios_tab[] = {
	(int *) 0xc8000, (int *) 0xd0000, (int *) 0xd8000 };
    int	i;
    char    tmp;

    tmp = inb(INFLED);
	/* First, see if the DIP switch values are valid */
	/* The test of B7 may fail on some early boards, mine works. */
    if ( ((~tmp & 0x3) != index ) || (tmp & 0x80) || !(tmp & 0x4) )
    	return 0;
    printk("IN-2000 probe got dip setting of %02X\n", tmp);
    tmp = inb(INVERS);
/* Add some extra sanity checks here */
    for(i=0; i < 3; i++)
	if(*(bios_tab[i]+0x04) == 0x41564f4e ||
		*(bios_tab[i]+0xc) == 0x61776c41) {
	  printk("IN-2000 probe found hdw. vers. %02x, BIOS at %06x\n",
		tmp, (unsigned int)bios_tab[i]);
		return 1;
	}
    printk("in2000 BIOS not found.\n");
    return 0;
}


/*
 * retrieve the current transaction counter from the WD
 */

static unsigned in2000_txcnt(void)
{
    unsigned total=0;

    if(inb(INSTAT) & 0x20) return 0xffffff;	/* not readable now */
    outb(TXCNTH,INSTAT);	/* then autoincrement */
    total =  (inb(INDATA) & 0xff) << 16;
    outb(TXCNTM,INSTAT);
    total += (inb(INDATA) & 0xff) << 8;
    outb(TXCNTL,INSTAT);
    total += (inb(INDATA) & 0xff);
    return total;
}

/*
 * Note: the FIFO is screwy, and has a counter granularity of 16 bytes, so
 * we have to reconcile the FIFO counter, the transaction byte count from the
 * WD chip, and of course, our desired transaction size.  It may look strange,
 * and could probably use improvement, but it works, for now.
 */

static void in2000_fifo_out(void)	/* uses FIFOCNTR */
{
    unsigned count, infcnt, txcnt;

    infcnt = inb(INFCNT)& 0xfe;	/* FIFO counter */
    do {
	txcnt = in2000_txcnt();
/*DEB(printk("FIw:%d %02x %d\n", in2000_datalen, infcnt, txcnt));*/
	count = (infcnt << 3) - 32;	/* don't fill completely */
	if ( count > in2000_datalen )
	    count = in2000_datalen;	/* limit to actual data on hand */
	count >>= 1;		/* Words, not bytes */
#ifdef FAST_FIFO_IO
	if ( count ) {
		port_write(INFIFO, in2000_dataptr, count);
		in2000_datalen -= (count<<1);
	}
#else
	while ( count-- )
	    {
		outw(*in2000_dataptr++, INFIFO);
		in2000_datalen -= 2;
	    }
#endif
    } while((in2000_datalen > 0) && ((infcnt = (inb(INFCNT)) & 0xfe) >= 0x20) );
    /* If scatter-gather, go on to next segment */
    if( !in2000_datalen && ++in2000_current_segment < in2000_nsegment)
      {
      in2000_scatter++;
      in2000_datalen = in2000_scatter->length;
      in2000_dataptr = (unsigned short*)in2000_scatter->address;
      }
    if ( in2000_datalen <= 0 )
    {
	ficmsk = 0;
	count = 32;	/* Always says to use this much flush */
	while ( count-- )
	    outw(0, INFIFO);
	outb(2, ININTR); /* Mask FIFO Interrupts when done */
    }
}

static void in2000_fifo_in(void)	/* uses FIFOCNTR */
{
    unsigned fic, count, count2;

    count = inb(INFCNT) & 0xe1;
    do{
	count2 = count;
	count = (fic = inb(INFCNT)) & 0xe1;
    } while ( count != count2 );
DEB(printk("FIir:%d %02x %08x\n", in2000_datalen,fic,(unsigned int )in2000_dataptr));
    do {
	count2 = in2000_txcnt();	/* bytes yet to come over SCSI bus */
DEB(printk("FIr:%d %02x %08x %08x\n", in2000_datalen,fic,count2,(unsigned int)in2000_dataptr));
	if(count2 > 65536) count2 = 0;
	if(fic > 128) count = 1024;
	  else if(fic > 64) count = 512;
	    else if (fic > 32) count = 256;
	      else if ( count2 < in2000_datalen ) /* if drive has < what we want */
	        count = in2000_datalen - count2;	/* FIFO has the rest */
	if ( count > in2000_datalen )	/* count2 is lesser of FIFO & rqst */
	    count2 = in2000_datalen >> 1;	/* converted to word count */
	else
	    count2 = count >> 1;
	count >>= 1;		/* also to words */
	count -= count2;	/* extra left over in FIFO */
#ifdef FAST_FIFO_IO
	if ( count2 ) {
		port_read(INFIFO, in2000_dataptr, count2);
		in2000_datalen -= (count2<<1);
	}
#else
	while ( count2-- )
	{
	    *in2000_dataptr++ = inw(INFIFO);
	    in2000_datalen -=2;
	}
#endif
    } while((in2000_datalen > 0) && (fic = inb(INFCNT)) );
DEB(printk("FIer:%d %02x %08x\n", in2000_datalen,fic,(unsigned int )in2000_dataptr));
/*    while ( count-- )
    	inw(INFIFO);*/	/* Throw away some extra stuff */
    if( !in2000_datalen && ++in2000_current_segment < in2000_nsegment)
      {
      in2000_scatter++;
      in2000_datalen = in2000_scatter->length;
      in2000_dataptr = (unsigned short*)in2000_scatter->address;
      }
    if ( ! in2000_datalen ){
	outb(2, ININTR); /* Mask FIFO Interrupts when done */
	ficmsk = 0;}
}

static void in2000_intr_handle(int irq, struct pt_regs *regs)
{
    int result=0;
    unsigned int count,auxstatus,scsistatus,cmdphase,scsibyte;
    int action=0;
    Scsi_Cmnd *SCptr;

  DEB(printk("INT:%d %02x %08x\n", in2000_datalen, inb(INFCNT),(unsigned int)in2000_dataptr));

    if (( (ficmsk & (count = inb(INFCNT))) == 0xfe ) ||
		( (inb(INSTAT) & 0x8c) == 0x80))
	{	/* FIFO interrupt or WD interrupt */
   	auxstatus = inb(INSTAT);	/* need to save now */
   	outb(SCSIST,INSTAT);
   	scsistatus = inb(INDATA); /* This clears the WD intrpt bit */
   	outb(TARGETU,INSTAT);	/* then autoincrement */
   	scsibyte = inb(INDATA);	/* Get the scsi status byte */
   	outb(CMDPHAS,INSTAT);
   	cmdphase = inb(INDATA);
   	DEB(printk("(int2000:%02x %02x %02x %02x %02x)\n",count,auxstatus,
		scsistatus,cmdphase,scsibyte));

	/* Why do we assume that we need to send more data here??? ERY */
   	if ( in2000_datalen )	/* data xfer pending */
   	    {
   	    if ( in2000_dataptr == NULL )
		printk("int2000: dataptr=NULL datalen=%d\n",
			in2000_datalen);
	    else if ( in2000_datawrite )
		in2000_fifo_out();
	    else
		in2000_fifo_in();
   	    } 
	if ( (auxstatus & 0x8c) == 0x80 )
	    {	/* There is a WD Chip interrupt & register read good */
	    outb(2,ININTR);	/* Disable fifo interrupts */
	    ficmsk = 0;
	    result = DID_OK << 16;
	    /* 16=Select & transfer complete, 85=got disconnect */
	    if ((scsistatus != 0x16) && (scsistatus != 0x85)
		&& (scsistatus != 0x42)){
/*	   	printk("(WDi2000:%02x %02x %02x %02x %02x)\n",count,auxstatus,
			scsistatus,cmdphase,scsibyte);*/
/*		printk("QDAT:%d %08x %02x\n",
		in2000_datalen,(unsigned int)in2000_dataptr,ficmsk);*/
		;
	    }
		switch ( scsistatus & 0xf0 )
		    {
		    case	0x00:	/* Card Reset Completed */
			action = 3;
			break;
		    case	0x10:	/* Successful Command Completion */
			if ( scsistatus & 0x8 )
		    	    action = 1;
			break;
		    case	0x20:	/* Command Paused or Aborted */
			if ( (scsistatus & 0x8) )
		    	    action = 1;
			else if ( (scsistatus & 7) < 2 )
		    		action = 2;
			     else
		    		result = DID_ABORT << 16;
			break;
		    case	0x40:	/* Terminated early */
			if ( scsistatus & 0x8 )
		     	    action = 1;
			else if ( (scsistatus & 7) > 2 )
		     		action = 2;
			     else
		    		result = DID_TIME_OUT << 16;
			break;
		    case	0x80:	/* Service Required from SCSI bus */
			if ( scsistatus & 0x8 )
			    action = 1;
			else
			    action = 2;
			break;
		    }		/* end switch(scsistatus) */
		outb(0,INFLED);
		switch ( action )
		    {
		    case	0x02:	/* Issue an abort */
			outb(COMMAND,INSTAT);
			outb(1,INDATA); 	/* ABORT COMMAND */
			result = DID_ABORT << 16;
		    case	0x00:	/* Basically all done */
			if ( ! in2000_SCptr )
			    return;
			in2000_SCptr->result = result | scsibyte;
			SCptr = in2000_SCptr;
			in2000_SCptr = 0;
			if ( in2000_done )
		     	    (*in2000_done)(SCptr);
			break;
		    case	0x01:	/* We need to reissue a command */
			outb(CMDPHAS,INSTAT);
			switch ( scsistatus & 7 )
			    {
			    case	0:	/* Data out phase */
		    	    case	1:	/* Data in phase */
		    	    case	4:	/* Unspec info out phase */
		    	    case	5:	/* Unspec info in phase */
		    	    case	6:	/* Message in phase */
		    	    case	7:	/* Message in phase */
				outb(0x41,INDATA); /* rdy to disconn */
				break;
		    	    case	2:	/* command phase */
				outb(0x30,INDATA); /* rdy to send cmd bytes */
				break;
		    	    case	3:	/* status phase */
				outb(0x45,INDATA); /* To go to status phase,*/
				outb(TXCNTH,INSTAT); /* elim. data, autoinc */
				outb(0,INDATA);
				outb(0,INDATA);
				outb(0,INDATA);
				in2000_datalen = 0;
				in2000_dataptr = 0;
				break;
			    }	/* end switch(scsistatus) */
			outb(COMMAND,INSTAT);
			outb(8,INDATA);	 /* RESTART THE COMMAND */
			break;
		    case	0x03:	/* Finish up a Card Reset */
			outb(TIMEOUT,INSTAT);	/* I got these values */
						/* by reverse Engineering */
			outb(IN2000_TMOUT,INDATA); /* the Always' bios. */
			outb(CONTROL,INSTAT);
			outb(0,INDATA);
			outb(SYNCTXR,INSTAT);
			outb(0x40,INDATA);	/* async, 4 cyc xfer per. */
			break;
		    }		/* end switch(action) */
	    }			/* end if auxstatus for WD int */
	}			/* end while intrpt active */
}

int in2000_queuecommand(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *))
{
    unchar direction;
    unchar *cmd = (unchar *) SCpnt->cmnd;
    unchar target = SCpnt->target;
    void *buff = SCpnt->request_buffer;
    unsigned long flags;
    int bufflen = SCpnt->request_bufflen;
    int timeout, size, loop;
    int i;

    /*
     * This SCSI command has no data phase, but unfortunately the mid-level
     * SCSI drivers ask for 256 bytes of data xfer.  Our card hangs if you
     * do this, so we protect against it here.  It would be nice if the mid-
     * level could be changed, but who knows if that would break other host
     * adapter drivers.
     */
    if ( *cmd == TEST_UNIT_READY )
	bufflen = 0;

    /*
     * What it looks like.  Boy did I get tired of reading its output.
     */
    if (*cmd == READ_10 || *cmd == WRITE_10) {
	i = xscsi2int((cmd+1));
    } else if (*cmd == READ_6 || *cmd == WRITE_6) {
	i = scsi2int((cmd+1));
    } else {
	i = -1;
    }
#ifdef DEBUG
    printk("in2000qcmd: pos %d len %d ", i, bufflen);
    printk("scsi cmd:");
    for (i = 0; i < SCpnt->cmd_len; i++) printk("%02x ", cmd[i]);
    printk("\n");
#endif
    direction = 1;	/* assume for most commands */
    if (*cmd == WRITE_10 || *cmd == WRITE_6)
	direction = 0;
    size = SCpnt->cmd_len;	/* CDB length */ 
    /*
     * Setup our current pointers
     * This is where you would allocate a control structure in a queue,
     * If you were going to upgrade this to do multiple issue.
     * Note that datalen and dataptr exist because we can change the
     * values during the course of the operation, while managing the
     * FIFO.
     * Note the nasty little first clause.  In theory, the mid-level
     * drivers should never hand us more than one command at a time,
     * but just in case someone gets cute in configuring the driver,
     * we'll protect them, although not very politely.
     */
    if ( in2000_SCptr )
    {
	printk("in2000_queue_command waiting for free command block!\n");
	while ( in2000_SCptr )
	    barrier();
    }
    for ( timeout = jiffies + 5; timeout > jiffies; )
    {
	if ( ! ( inb(INSTAT) & 0xb0 ) )
	{
	    timeout = 0;
	    break;
	}
	else
	{
	    inb(INSTAT);
	    outb(SCSIST,INSTAT);
	    inb(INDATA);
	    outb(TARGETU,INSTAT); 	/* then autoinc */
	    inb(INDATA);
	    inb(INDATA);
	}
    }
    if ( timeout )
    {
	printk("in2000_queue_command timeout!\n");
	SCpnt->result = DID_TIME_OUT << 16;
	(*done)(SCpnt);
	return 1;
    }
    /* Added for scatter-gather support */
    in2000_nsegment = SCpnt->use_sg;
    in2000_current_segment = 0;
    if(SCpnt->use_sg){
      in2000_scatter = (struct scatterlist *) buff;
      in2000_datalen = in2000_scatter->length;
      in2000_dataptr = (unsigned short*)in2000_scatter->address;
    } else {
      in2000_scatter = NULL;
      in2000_datalen = bufflen;
      in2000_dataptr = (unsigned short*) buff;
    };
    in2000_done = done;
    in2000_SCptr = SCpnt;
    /*
     * Write the CDB to the card, then the LUN, the length, and the target.
     */
    outb(TOTSECT, INSTAT);	/* start here then autoincrement */
    for ( loop=0; loop < size; loop++ )
	outb(cmd[loop],INDATA);
    outb(TARGETU,INSTAT);
    outb(SCpnt->lun & 7,INDATA);
    SCpnt->host_scribble = NULL;
    outb(TXCNTH,INSTAT);	/* then autoincrement */
    outb(bufflen>>16,INDATA);
    outb(bufflen>>8,INDATA);
    outb(bufflen,INDATA);
    outb(target&7,INDATA);
    /*
     * Set up the FIFO
     */
    save_flags(flags);
    cli();		/* so FIFO init waits till WD set */
    outb(0,INFRST);
    if ( direction == 1 )
    {
	in2000_datawrite = 0;
	outb(0,INFWRT);
    }
    else
    {
	in2000_datawrite = 1;
	for ( loop=16; --loop; ) /* preload the outgoing fifo */
	    {
		outw(*in2000_dataptr++,INFIFO);
		if(in2000_datalen > 0) in2000_datalen-=2;
	    }
    }
    ficmsk = 0xff;
    /*
     * Start it up
     */
    outb(CONTROL,INSTAT);	/* WD BUS Mode */
    outb(0x4C,INDATA);
    if ( in2000_datalen )		/* if data xfer cmd */
        outb(0,ININTR);		/* Enable FIFO intrpt some boards? */
    outb(COMMAND,INSTAT);
    outb(0,INNLED);
    outb(8,INDATA);		/* Select w/ATN & Transfer */
    restore_flags(flags);			/* let the intrpt rip */
    return 0;
}

static volatile int internal_done_flag = 0;
static volatile int internal_done_errcode = 0;

static void internal_done(Scsi_Cmnd * SCpnt)
{
    internal_done_errcode = SCpnt->result;
    ++internal_done_flag;
}

int in2000_command(Scsi_Cmnd * SCpnt)
{
    in2000_queuecommand(SCpnt, internal_done);

    while (!internal_done_flag);
    internal_done_flag = 0;
    return internal_done_errcode;
}

int in2000_detect(Scsi_Host_Template * tpnt)
{
/* Order chosen to reduce conflicts with some multi-port serial boards */
    int base_tab[] = { 0x220,0x200,0x110,0x100 };
    int int_tab[] = { 15,14,11,10 };
    struct Scsi_Host * shpnt;
    int loop, tmp;

    DEB(printk("in2000_detect: \n"));
    
    for ( loop=0; loop < 4; loop++ )
    {
	base = base_tab[loop];
	if ( in2000_test_port(loop))  break;
    }
    if ( loop == 4 )
	return 0;

  /* Read the dip switch values again for miscellaneous checking and
     informative messages */
  tmp = inb(INFLED);

  /* Bit 2 tells us if interrupts are disabled */
  if ( (tmp & 0x4) == 0 ) {
    printk("The IN-2000 is not configured for interrupt operation\n");
    printk("Change the DIP switch settings to enable interrupt operation\n");
  }

  /* Bit 6 tells us about floppy controller */
  printk("IN-2000 probe found floppy controller on IN-2000 ");
  if ( (tmp & 0x40) == 0)
    printk("enabled\n");
  else
    printk("disabled\n");

  /* Bit 5 tells us about synch/asynch mode */
  printk("IN-2000 probe found IN-2000 in ");
  if ( (tmp & 0x20) == 0)
    printk("synchronous mode\n");
  else
    printk("asynchronous mode\n");

    irq_level = int_tab [ ((~inb(INFLED)>>3)&0x3) ];

    printk("Configuring IN2000 at IO:%x, IRQ %d"
#ifdef FAST_FIFO_IO
		" (using fast FIFO I/O code)"
#endif
		"\n",base, irq_level);

    outb(2,ININTR);	/* Shut off the FIFO first, so it won't ask for data.*/
    if (request_irq(irq_level,in2000_intr_handle, 0, "in2000"))
    {
	printk("in2000_detect: Unable to allocate IRQ.\n");
	return 0;
    }
    outb(0,INFWRT);	/* read mode so WD can intrpt */
    outb(SCSIST,INSTAT);
    inb(INDATA);	/* free status reg, clear WD intrpt */
    outb(OWNID,INSTAT);
    outb(0x7,INDATA);	/* we use addr 7 */
    outb(COMMAND,INSTAT);
    outb(0,INDATA);	/* do chip reset */
    shpnt = scsi_register(tpnt, 0);
    /* Set these up so that we can unload the driver properly. */
    shpnt->io_port = base;
    shpnt->n_io_port = 12;
    shpnt->irq = irq_level;
    request_region(base, 12,"in2000");  /* Prevent other drivers from using this space */
    return 1;
}

int in2000_abort(Scsi_Cmnd * SCpnt)
{
    DEB(printk("in2000_abort\n"));
    /*
     * Ask no stupid questions, just order the abort.
     */
    outb(COMMAND,INSTAT);
    outb(1,INDATA);	/* Abort Command */
    return 0;
}

static inline void delay( unsigned how_long )
{
    unsigned long time = jiffies + how_long;
    while (jiffies < time) ;
}

int in2000_reset(Scsi_Cmnd * SCpnt)
{
    DEB(printk("in2000_reset called\n"));
    /*
     * Note: this is finished off by an incoming interrupt
     */
    outb(0,INFWRT);	/* read mode so WD can intrpt */
    outb(SCSIST,INSTAT);
    inb(INDATA);
    outb(OWNID,INSTAT);
    outb(0x7,INDATA);	/* ID=7,noadv, no parity, clk div=2 (8-10Mhz clk) */
    outb(COMMAND,INSTAT);
    outb(0,INDATA);	/* reset WD chip */
    delay(2);
#ifdef SCSI_RESET_PENDING
    return SCSI_RESET_PENDING;
#else
    if(SCpnt) SCpnt->flags |= NEEDS_JUMPSTART;
    return 0;
#endif
}

int in2000_biosparam(Disk * disk, int dev, int* iinfo)
	{
	  int size = disk->capacity;
    DEB(printk("in2000_biosparam\n"));
    iinfo[0] = 64;
    iinfo[1] = 32;
    iinfo[2] = size >> 11;
/* This should approximate the large drive handling that the DOS ASPI manager
   uses.  Drives very near the boundaries may not be handled correctly (i.e.
   near 2.0 Gb and 4.0 Gb) */
    if (iinfo[2] > 1024) {
	iinfo[0] = 64;
	iinfo[1] = 63;
	iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]);
	}
    if (iinfo[2] > 1024) {
	iinfo[0] = 128;
	iinfo[1] = 63;
	iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]);
	}
    if (iinfo[2] > 1024) {
	iinfo[0] = 255;
	iinfo[1] = 63;
	iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]);
	if (iinfo[2] > 1023)
	    iinfo[2] = 1023;
	}
    return 0;
    }

#ifdef MODULE
/* Eventually this will go into an include file, but this will be later */
Scsi_Host_Template driver_template = IN2000;

#include "scsi_module.c"
#endif

