/*
    FILE:	iomega_pc2.c
    PURPOSE:	Driver code for IOMEGA pc2 interface driver.  Provides 
   		some #defines and functions for the kernel to interface
   		with the pc2 card 
    AUTHOR:	Brian Hughes
    EMAIL:	bwh8918@rit.edu
    COPYRIGHT:	IOMEGA PC2/2B Driver for Linux
                Copyright (c) 1994 Brian W. Hughes

        This driver is free software; you can distribute it and/or
        modify it under the terms of the GNU General Public License as
        published by the Free Software Foundation; either version 2 of
        the License or (at your option) any later version.

        This driver 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 driver; if not, write to the Free
        Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
        USA.

    THANKS:	...to everyone who write something I used below.  I 
		    wouldn't/couldn't have done this without these
		    references

                Rickard E. Faith again for the Future Domain driver
                    code.  I looked at this a lot.

                Drew Eckardt for the mid-level driver code.  I also
                    looked at this a lot!

                Hannu Savolainen for the sound driver's DMA code.  I
                    figured out how to DMA from this.

    REFERENCES: "IOMEGA PC2/2B ADAPTER Jolley Notes"
                Courtesy of IOMEGA, Jul. 1986

    		"ALPHA-10H/10.5H Technical Description Manual"
    		IOMEGA Corporation, Dec. 1984

    		IOMEGA's address is:
    		    IOMEGA Corporation
    		    1821 West Iomega Way
    		    Roy, Utah 84067

		"Linux Kernel Hacker's Guide" version 0.5 I think
		Micheal K. Johnson (johnsonm@sunsite.unc.edu)
		SCSI Portion by Rickard E. Faith (faith@cs.unc.edu)

		"The Programmer's PC Sourcebook"
		Thom Hogan, Microsoft Press
		(Some DMA controller reference stuff)
		This is the only good thing with the word `Microsoft' on it
*/

#ifndef IOMEGA_PC2_C
    #define IOMEGA_PC2_C
#endif

#include <linux/sched.h>
#include <asm/io.h>
#include <asm/dma.h>
#include "../block/blk.h"
#include "scsi.h"
#include "hosts.h"
#include "sd.h"
#include "iomega_pc2.h"
#include <asm/system.h>
#include <linux/string.h>
#include "iomega_pc2.cfg"

/*
     IO PORT OFFSETS  
	 All ports are 8 bit.  Add these to the base IO address to get 
	 these ports
*/

#define PC2_SWITCH_OFFSET	0x00  /* Read the switch setting out of here */
#define PC2_STATUS_OFFSET	0x01  /* SCSI BUS status, reading resets int. */
#define PC2_ID_OFFSET	 	0x02  /* ID port, see below... */
#define PC2_CONTROL_OFFSET 	0x02  /* Control port, see below... */
#define PC2_DATA_OFFSET	 	0x03  /* Data (read/write) */

/*
    CONTROL PORT BIT MASKS  
        Bits in the control port.  
*/

#define PC2_CTRL_RESET		0x01	/* Resets SCSI bus */
#define PC2_CTRL_SELECT		0x02	/* Raises select signal */
#define PC2_CTRL_DMA_ENABLE	0x04	/* Enables DMA */
#define PC2_CTRL_INT_ENABLE	0x08	/* Enables interrupt */

/*
    STATUS PORT BIT MASKS  
        The status port reports the SCSI BUS status.  Reading this 
        port will reset the card interrupt.
*/

#define PC2_STAT_REQ		0x01	/* Request line */
#define PC2_STAT_IO		0x02	/* Input/Output: 1=input */
#define PC2_STAT_CD		0x04	/* Command/Data on bus */
#define PC2_STAT_MSG		0x08	/* Controller sending message */
#define PC2_STAT_BSY		0x10	/* Bus busy */
#define PC2_STAT_PARITY		0x20	/* Parity line */
#define PC2_STAT_ACK		0x40	/* Acknowledge */
#define PC2_STAT_LATCH		0x80	/* Latch bit */

/*
    SWITCH PORT BIT MASKS
        These correspond directly to the switch setting on the card.
        Useful for determining the number of drives attached to the card
        and lots of other fun stuff.  Switch 1 is at the top, switch 8
        is at the bottom.

        When the switch is up, a 0 will appear in that bit

        For IO port selection:

        SW1 SW2   PORT_ADDRESS
        ------------------------
         0   0     340H-345H  (Factory preset)
         1   0     350H-355H
         0   1     360H-365H
         1   1     370H-375H
*/

#define PC2_SW_PORT	0x03	/* 2 switches to select IO address */
#define PC2_SW_DMA_CH3  0x04	/* 0 = channel 1, 1 = channel 3 */
#define PC2_SW_USE_DMA	0x08	/* 1 = DMA enabled */
				/* This spot reserved */
				/* This spot reserved too*/
#define PC2_SW_2D_2	0x40	/* 1 = 2 drives on second controller */
#define PC2_SW_2D_1	0x80	/* 1 = 2 drives on first controller */

/*  
    POSSIBLE IO PORTS AND SWITCH SETTINGS
        These are the base IO ports the PC2 supports and the switch
        for each port address
*/

struct address_rec {
    unsigned address ; 
    unsigned char switches ;
} ;

static struct address_rec io_bases [] = {
    0x340, 0x00, 
    0x350, 0x02,
    0x360, 0x01,
    0x370, 0x03
} ;

#define ADDRESS_CNT (sizeof(io_bases) / sizeof(struct address_rec))

/*
    FIXED CONSTANTS
        Stuff the driver needs to know that nobody can change.

    IRQ
        The card is stuck at IRQ 5

    PC2/2B SIGNATURE 
        should number be present in the ID port at all times.  "If it 
        doesn't say this, it's not a PC2/2B"

	NOTE: The top 4 bits are don't cares, so we should probably mask
	      them before comparing them.

    BLK_SIZE
        The size of a SCSI sized block (i think).  Used to figure out
        how much data needs to be transferred when using DMA
*/

#define PC2_IRQ		5
#define PC2_ID		0x02	
#define BLK_SIZE	256

/*
    FUNCTION PROTOTYPES
        See function definitions for details...
*/

static int find_card(void) ;
static void check_parity(void) ;
static int select(int) ;
static void init_command(void (*)(Scsi_Cmnd *)) ;
static void do_command(void) ;
static void finish_command(void) ;

#if defined USE_DMA || defined PC2_WATCH_XFER_SIZE
    static unsigned data_size(unsigned char *) ;
#endif

#ifdef USE_DMA
    static void iomega_pc2_intr(int) ;  
    static int dma_data(unsigned) ;
    static void grab_irq_dma(void) ;
#endif

#ifdef BABYSIT_ALPHA
    static void screen_result(void) ;
#endif

/*
    GLOBAL VARIABLES 
	These variables are used by all the functions below to address
	the card.

        io_base:         The address of the card
        switches:        The switches (so we don't have to read them all 
                         the time)
	cur_cmnd:	 Command being processed
	last_result:	 Result from last command
	complete:	 Whether or not the current command completed
	can_dma: 	 Whether or not we have the DMA channel and the 
			 IRQ.  
*/

static unsigned io_base           = 0 ;
static unsigned char switches     = 0 ;
static Scsi_Cmnd *cur_cmnd        = NULL ;
static unsigned last_result       = 0 ;

static char complete	= 0 ;
static char parity_ok 	= 0 ;
static char can_dma   	= 0 ;

char iomega_pc2_info_string [75] = "IOMEGA PC2/2B SCSI Adapter: " ;

/*
    MACRO:	make_result
    PURPOSE:	Makes a result (unsigned int) out of the given bytes

    		s = SCSI status byte
    		m = SCSI message byte
    		h = Host message
                d = driver (mid-level, always 0) message.  Not included
                    so it is always 0
*/

#define make_result(h, m, s) (((h) << 16) | ((m) << 8) | ((s) & 0x0f))

/*
    MACRO:	DMA_CHANNEL
    PURPOSE:	Figures out which DMA channel the card wants using 
    		`switches' above
*/

#define DMA_CHANNEL ((switches & PC2_SW_DMA_CH3) ? 3 : 1)

/*
    MACRO:	bit
    PURPOSE:	Makes a bit mask out of the given number
*/

#define bit(num) (1 << num)

/*
    MACROS:	PC2_XXXX_PORT
    PURPOSE:	Adds the appropriate offset to the io_base 
*/

#define PC2_SWITCH_PORT 	io_base + PC2_SWITCH_OFFSET
#define PC2_STATUS_PORT		io_base + PC2_STATUS_OFFSET
#define PC2_ID_PORT		io_base + PC2_ID_OFFSET
#define PC2_CONTROL_PORT 	io_base + PC2_CONTROL_OFFSET
#define PC2_DATA_PORT	 	io_base + PC2_DATA_OFFSET

#ifdef USE_DMA

    /*
	FUNCTION:	grab_irq_dma
	PURPOSE:	Gets the IRQ and requested DMA channel from the kernel 
		        and sets the `can_dma' flag appropriately
    */

    static void grab_irq_dma(void)

    {
	if( (request_irq(PC2_IRQ, iomega_pc2_intr, SA_INTERRUPT, 
            "iomega_pc2") == 0) && (request_dma(DMA_CHANNEL, 
            "iomega_pc2") == 0) )
	{
	    can_dma = 1 ;
	}
	else
	{
	    printk("iomega_pc2: Unable to get IRQ or DMA channel, will ") ;
	    printk(" use polling I/O\n") ;
	}
    }

#endif

/*
    FUNCTION:	check_parity
    PURPOSE:	Checks the parity algorithm on the card and sets the 
    		global `parity_ok' flag to 1 if it's ok.  See
    		the Jolley Notes for more info...
*/

static void check_parity(void)

{
    outb(0, PC2_CONTROL_PORT) ;
    inb(PC2_SWITCH_PORT) ;

    if((inb(PC2_STATUS_PORT) & PC2_STAT_PARITY) == 0)
    {
        /* 
            Old, borken algorithm is on your card
	*/

	parity_ok = 0 ;
	printk("iomega_pc2: borken parity algorithm on card- will ignore\n") ;
    }

    else
    {
        /*
            New, working algorithm is on your card
	*/

	parity_ok = 1 ;
    }
}

/*
    FUNCTION:	find_card
    PURPOSE:	finds a PC2/2B card on the system if it exists.  Returns
    		1 if it's found, 0 otherwise.  Sets `io_base' to the io base
    		of the card if it's found
*/

static int find_card(void) 

{
    unsigned i = 0 ;	/* index into `io_bases' array */

    while(i < ADDRESS_CNT)
    {
        io_base = io_bases[i].address ;

        if((inb(PC2_SWITCH_PORT) & 0x03) != io_bases[i].switches)
        {
            ++i ;
            continue ;
	}

	if((inb(PC2_ID_PORT) & 0x0f) != PC2_ID)
	{
	    ++i ;
	    continue ;
	}

	outb(PC2_CTRL_DMA_ENABLE, PC2_CONTROL_PORT) ;
	if((inb(PC2_STATUS_PORT) & PC2_STAT_LATCH) != 0)
	{
	    ++i ;
	    continue ;
	}

	outb(0, PC2_CONTROL_PORT) ;
	if((inb(PC2_STATUS_PORT) & PC2_STAT_LATCH) == 0)
	{
	    ++i ;
	    continue ;
	}

	return 1 ;
    }

    io_base = 0 ;
    return 0 ;
}

/*
    FUNCTION:	iomega_pc2_detect
    PURPOSE:	Detects the PC2 card on the system if it exists via
    		a call to `find_card' and pieces together the info 
    		string for the mid-level driver.
*/

int iomega_pc2_detect(struct SHT *host) 

{
    char temp [70] ;	/* Temporary string */
    char *dest ;

    printk("iomega_pc2: Driver version 1.1\n") ;

    if(find_card() != 0)
    {
	iomega_pc2_reset(NULL) ;

	#ifdef USE_DMA
	    grab_irq_dma() ;
	#endif

	switches = inb(PC2_SWITCH_PORT) ;

	check_parity() ;

        sprintf(temp, "port 0x%3x, DMA ch%i %s, %i drive(s)", 
            io_base, DMA_CHANNEL, 
            (switches & PC2_SW_USE_DMA) ? "(enabled)" : "(disabled)",
            (switches & PC2_SW_2D_1) ? 2 : 1) ;

	/*
	    strcat (yes, the kernel version) didn't work for me.  GCC
	    says "string.h:66: `asm' needs too many reloads"
	*/

	dest = iomega_pc2_info_string ;
	while(*dest != '\0') 
	{
	    ++dest ;
	}

	strcpy(dest, temp) ;

        return 1 ;
    }

    return 0 ;
}

/*
    FUNCTION:	iomega_pc2_info
    PURPOSE:	Returns a string describing the PC2 card 
    		(defined above somewhere)
*/

const char *iomega_pc2_info(void) 

{
    return iomega_pc2_info_string ;
}

#if defined USE_DMA || defined PC2_WATCH_XFER_SIZE

    /*
	FUNCTION:	data_size
	PURPOSE:	Determines the number of bytes of data the given
			command needs transferred
    */

    static unsigned data_size(unsigned char *cmnd) 

    {
	switch(cmnd[0])
	{
	    case 0x03:
		return cmnd[4] <= 4 ? 4 : cmnd[4] ;
		break ;
	    case 0x08:
	    case 0x0a:
	    case 0xe5:
		return (cmnd[4] == 0 ? 256 : cmnd[4]) * BLK_SIZE ;
		break ;
	    case 0x12:
		return cmnd[4] ;
		break ;
	    case 0x25:
		return 8 ;
		break ;
	    case 0x28:
	    case 0x2a:
		return ((cmnd[7] << 8) | cmnd[8]) * BLK_SIZE ;
		break ;
	    default:
		return 0 ;
	}
    }

#endif

#ifdef USE_DMA

    /*
	FUNCTION:   dma_data
	PURPOSE:    Attempts to DMA in from or out to the drive.  Figures
		    out which way we are going from the 'status' passed
		    in.  Returns 1 on success, 0 on failure
    */

    static int dma_data(unsigned status)

    {
	if( ((switches & PC2_SW_USE_DMA) == 0) || (can_dma == 0) )
	{
	    return 0 ;
	}

	#ifdef PC2_DEBUG_DMA
	    printk("dma%02xs%u ", cur_cmnd->cmnd[0], 
	        data_size(cur_cmnd->cmnd)) ;
	#endif

	cli() ;
	disable_dma(DMA_CHANNEL) ;
	clear_dma_ff(DMA_CHANNEL) ;
	set_dma_mode(DMA_CHANNEL, 
	    (status & PC2_STAT_IO) ? DMA_MODE_READ : DMA_MODE_WRITE) ;
	set_dma_addr(DMA_CHANNEL, (long)cur_cmnd->SCp.ptr) ;
	set_dma_count(DMA_CHANNEL, data_size(cur_cmnd->cmnd)) ;
	cur_cmnd->SCp.ptr += data_size(cur_cmnd->cmnd) ;
	outb(PC2_CTRL_INT_ENABLE | PC2_CTRL_DMA_ENABLE, PC2_CONTROL_PORT) ;
	enable_dma(DMA_CHANNEL) ;
	sti() ;

	return 1 ;
    }

    /*
	FUNCTION:   iomega_pc2_intr
	PURPOSE:    Acknowledges the PC2/2B interrupt.  
		    The interrupt is generated with the SCSI bus reaches
		    the ending phase and wants the send status and message.
		    We have to read the status port to formally acknowledge
		    the interrupt and then finish the command
    */

    void iomega_pc2_intr(int unused) 

    {
	unsigned data = 0 ;		   /* Data from card */

	outb(0, PC2_CONTROL_PORT) ;
	data = inb(PC2_STATUS_PORT) ;

	#ifdef PC2_WATCH_INT
	    printk("+int%02x ", data) ;
	#endif

	while(data & PC2_STAT_BSY)
	{
	    switch(data & 0x0f)
	    {
		case 0x07:
		    cur_cmnd->SCp.Status = inb(PC2_DATA_PORT) ;

		    #ifdef PC2_WATCH_COMMAND
			printk("is%02x ", cur_cmnd->SCp.Status) ;
		    #endif

		    break ;

		case 0x0f:
		    cur_cmnd->SCp.Message = inb(PC2_DATA_PORT) ;

		    #ifdef PC2_WATCH_COMMAND
			printk("im%02x ", cur_cmnd->SCp.Message) ;
		    #endif

		    complete = 1 ;
		    break ;

		default:

		    if((data & PC2_STAT_REQ) != 0)
		    {
			printk("iomega_pc2: Got unexpected request from ") ;
			printk("controller (%02x)\n", data) ;
			do_command() ;
		    }

		    break ;
	    }

	    data = inb(PC2_STATUS_PORT) ;
	}

	finish_command() ;

	#ifdef PC2_WATCH_INT
	    printk("-int ") ;
	#endif
    }

#endif

/*
    FUNCTION:	select
    PURPOSE:	Selects the given target.  Returns DID_OK on success,
    		DID_BUS_BSY or DID_NO_CONNECT on failure
*/

static int select(int target)

{
    unsigned data = 0 ;		/* Data read from card */
    unsigned long mark = 0 ;	/* Timer mark */

    if(inb(PC2_STATUS_PORT) & PC2_STAT_BSY)
    {
        return DID_BUS_BUSY ;
    }

    outb(bit(target), PC2_DATA_PORT) ;
    outb(PC2_CTRL_SELECT, PC2_CONTROL_PORT) ;

    data = inb(PC2_STATUS_PORT) ;

    mark = jiffies ;
    while( ((data & PC2_STAT_BSY) == 0) && (jiffies < mark + SELECT_WAIT) )
    {
        /* Waiting for something to respond... */ ;
	data = inb(PC2_STATUS_PORT) ;
    }

    outb(0, PC2_CONTROL_PORT) ;

    return (data & PC2_STAT_BSY) ? DID_OK : DID_NO_CONNECT ;
}

/*
    FUNCTION:	init_command
    PURPOSE:	Initializes global variables for the next command
*/

static void init_command(void (* done_fn)(Scsi_Cmnd *)) 

{
    cur_cmnd->result = 0 ;		            /* Return result */
    cur_cmnd->scsi_done = done_fn ;	            /* Done function */
    cur_cmnd->SCp.sent_command = 0 ;	            /* Command bytes sent out */
    cur_cmnd->SCp.ptr = cur_cmnd->request_buffer ;  /* Data buffer */
    cur_cmnd->SCp.Status = 0 ;		            /* SCSI status */
    cur_cmnd->SCp.Message = 0 ;		            /* SCSI Message */

    complete = 0 ;
    last_result = 0 ;
}

/*
    FUNCTION:	do_command
    PURPOSE:	Communicates with the controller and sends it what
    		it wants.  This is kind of done blindly- if the
    		controller messes up and askes for the wrong thing,
    		we could (worst case) segmentation fault.
*/

static void do_command(void) 

{
    unsigned data = 0 ;		/* Data read from card */

    #ifdef PC2_WATCH_XFER_SIZE
	unsigned out = 0 ;	/* Number of data bytes transferred out */
	unsigned in = 0 ;	/* Number of data bytes transferred in */
    #endif

    data = inb(PC2_STATUS_PORT) ;

    #ifdef PC2_WATCH_INT
        printk("nc: ") ;
    #endif

    while( (data & PC2_STAT_BSY) && (data & PC2_STAT_LATCH) )
    { 
        switch(data & 0x0f)
        {
	    case 0x05:
	        outb(cur_cmnd->cmnd[cur_cmnd->SCp.sent_command++], 
	            PC2_DATA_PORT) ;

		#ifdef PC2_WATCH_COMMAND
		    printk("c%02x ", 
		        cur_cmnd->cmnd[cur_cmnd->SCp.sent_command - 1]) ;
		#endif

		break ;

	    case 0x03:
	        #ifdef USE_DMA
	            if(dma_data(data) == 0)
		#endif

	        *(cur_cmnd->SCp.ptr++) = inb(PC2_DATA_PORT) ;

	        #ifdef PC2_WATCH_XFER_SIZE
		    in++ ;
		#endif

	        break ;

	    case 0x01:
	        #ifdef USE_DMA
	            if(dma_data(data) == 0)
		#endif

	        outb(*(cur_cmnd->SCp.ptr++), PC2_DATA_PORT) ;

	        #ifdef PC2_WATCH_XFER_SIZE
		    out++ ;
		#endif

	        break ;

	    case 0x07:
	        cur_cmnd->SCp.Status = inb(PC2_DATA_PORT) ;

		#ifdef PC2_WATCH_COMMAND
		    printk("s%02x ", cur_cmnd->SCp.Status) ;
		#endif

	        break ;

	    case 0x0f:
	        cur_cmnd->SCp.Message = inb(PC2_DATA_PORT) ;

		#ifdef PC2_WATCH_COMMAND
		    printk("m%02x ", cur_cmnd->SCp.Message) ;
		#endif

	        complete = 1 ;
	        break ;

	    default :
	        if((data & PC2_STAT_REQ) != 0)
	        {
	            printk("iomega_pc2: Got unexpected request from ") ;
	            printk("controller (%02x)\n", data) ;
		}
	        break ;
	}

	data = inb(PC2_STATUS_PORT) ;
    }

    #ifdef PC2_WATCH_XFER_SIZE
        printk("<cmnd:%02x exp:%i out: %i, in:%i>\n", 
            cur_cmnd->cmnd[0], data_size(cur_cmnd->cmnd), out, in) ;
    #endif
}	    

/*
    FUNCTION:	finish_command
    PURPOSE:	Cleans up after command execution.
*/

static void finish_command(void)

{
    Scsi_Cmnd *command = NULL ;		/* Command we're cleaning up after */

    #if DEBUG_PAUSE > 0
        unsigned long mark = 0 ;	/* Timer mark */
    #endif

    if(cur_cmnd->result == 0)
    {
	cur_cmnd->result = make_result(complete ? DID_OK : DID_ERROR, 
				    cur_cmnd->SCp.Message, 
				    cur_cmnd->SCp.Status) ;

	last_result = cur_cmnd->result ;
    }

    #ifdef BABYSIT_ALPHA
        screen_result() ;
    #endif

    /*
        If queuecommand gets a new command to execute before we set
        cur_cmnd to NULL it's going to go nuts, so we save a pointer
        to this command, set cur_cmnd to NULL, and finish cleanup
    */

    command = cur_cmnd ;
    cur_cmnd = NULL ;

    #ifdef PC2_WATCH_COMMAND
	printk("0x%08x!\n", last_result) ;
    #endif

    if(command->scsi_done)
    {
	command->scsi_done(command) ;
    }


    #if DEBUG_PAUSE > 0
        mark = jiffies ;
        while(jiffies < mark + DEBUG_PAUSE)
            /* Wait... */ ;
    #endif

}
        
/*
    FUNCTION:	iomega_pc2_command
    PURPOSE:	Calls queuecommand and waits for the command to complete,
    		then returns the result
*/

int iomega_pc2_command(Scsi_Cmnd *command) 

{
    iomega_pc2_queuecommand(command, NULL) ;

    while(cur_cmnd != NULL) ;

    return last_result ;
}

/*
    FUNCTION:	iomega_pc2_queuecommand
    PURPOSE:	Sets up and executes a command.  If we have DMA
    		capability, will 'fall through' and wait for the
    		interrupt.  If we're waiting and a new command
    		comes along, it gets queued and is executed
    		when this one finishes
*/

int iomega_pc2_queuecommand(Scsi_Cmnd *command, void (* done_fn)(Scsi_Cmnd *)) 

{
    unsigned sel_result = 0 ;		/* Result from select */

    sel_result = select(command->target) ;

    if(sel_result != DID_OK)
    {
	command->result = make_result(sel_result, 0, 0) ;
	if(done_fn)
	{
	    done_fn(command) ;
	}
    }

    else 
    {
	command->result = 0 ;
	cur_cmnd = command ;
	init_command(done_fn) ;
	do_command() ;

	if( (inb(PC2_STATUS_PORT) & PC2_STAT_LATCH) && (cur_cmnd != NULL) )
	{
	    finish_command() ;
	}

	#ifdef PC2_WATCH_INT
	    else
	    {
		printk("-qc:s=0x%02x ", inb(PC2_STATUS_PORT)) ;
	    }
	#endif
    }

    return 0 ;
}

/*
    FUNCTION:	iomega_pc2_abort
    PURPOSE:	Doesn't really do anything
*/

int iomega_pc2_abort(Scsi_Cmnd *command) 

{
    #if defined BABYSIT_ALPHA

        /*  
            On numerous occasions, I've encountered the mid-level driver
            trying to abort because my A10 was spinning up and it timed
            out.  If I ignore the abort request, everything seems to
            work though...

	    SCSI_ABORT_SNOOZE tells the mid-level driver to wait some more.
	    See scsi.h, line 339 or so for more details
	*/

	if(memchr(alpha_ids, cur_cmnd->target, sizeof(alpha_ids)) != NULL)
	{
	    printk("iomega_pc2: ignored abort request for Axx\n") ;
	    return SCSI_ABORT_SNOOZE ;
	}

	else

    #endif

    /*
        Um, there's no way to abort the command and be 100% that the 
	SCSI bus will live through it, so I'm not doing anything.
    */

    {
	if (command != NULL)
	{
	    return SCSI_ABORT_NOT_RUNNING ;
	}
	else
	{
	    return SCSI_ABORT_BUSY ;
	}
    }
}

/*
    FUNCTION:	iomega_pc2_reset
    PURPOSE:	Resets the scsi bus
*/

int iomega_pc2_reset(Scsi_Cmnd *command) 

{
    unsigned long mark = 0 ;	/* Timer mark */

    printk("iomega_pc2: resetting with status 0x%02x\n", inb(PC2_STATUS_PORT)) ;

    outb(PC2_CTRL_RESET, PC2_CONTROL_PORT) ;

    for(mark = jiffies; jiffies < mark + 1; ) ;

    outb(0, PC2_CONTROL_PORT) ;

    if(cur_cmnd)
    {
        cur_cmnd->result = make_result(DID_RESET, 0, 0) ;
        finish_command() ;
    }

    return SCSI_RESET_WAKEUP ;
}

/*
    FUNCTION:	iomega_pc2_bios_param
    PURPOSE:	Makes up some numbers for the VFS so it'll mount DOS
    		filesystems on this drive.  I'm not sure what to do here,
    		so I'm going to... well, I don't know what I'm going to do
*/

int iomega_pc2_bios_param(Disk *disk, int dev, int info []) 

{
    info[0] = HEADS ;
    info[1] = SECTOR_SIZE ;
    info[2] = CYLINDERS ;

    return 0 ;
}

#ifdef BABYSIT_ALPHA

    /*
	FUNCTION:   screen_result
	PURPOSE:    Screens the results of the current command.  Since
		    the Alpha drives doen't support full standard SCSI,
		    we have to fix up some stuff to fool the mid-level
		    driver.  Modify as you feel is necessary.
    */

    static void screen_result(void)

    {
	if(memchr(alpha_ids, cur_cmnd->target, sizeof(alpha_ids)) != NULL)
	{

	    /*
		The A10 doesn't really support the INQUIRY command
		to the fullest extent, so we're going to add some
		stuff if it isn't there already.
	    */

	    if(cur_cmnd->cmnd[0] == INQUIRY)
	    {
		if(((unsigned char *)cur_cmnd->request_buffer)[8] == 0)
		{
		    /*
			This is where the vendor goes...
		    */

		    #ifdef PC2_WATCH_RESULT_SCREEN
			printk("fudge: vendor ") ;
		    #endif

		    memcpy(&((unsigned char *)cur_cmnd->request_buffer)[8], 
			your_vendor, 8) ;
		}

		if(((unsigned char *)cur_cmnd->request_buffer)[16] == 0)
		{
		    /*
			This is where the drive model goes
		    */

		    #ifdef PC2_WATCH_RESULT_SCREEN
			printk("fudge: drive ") ;
		    #endif

		    memcpy(&((unsigned char *)cur_cmnd->request_buffer)[16], 
			your_drive, 16) ;
		}

		if(((unsigned char *)cur_cmnd->request_buffer)[32] == 0)
		{
		    /* 
			this is where the revision goes 
		    */

		    #ifdef PC2_WATCH_RESULT_SCREEN
			printk("fudge: revision ") ;
		    #endif

		    memcpy(&((unsigned char *)cur_cmnd->request_buffer)[32],
			your_rev, 4) ;
		}
	    }
	}
    }

#endif

