/* 
	ez.c	(c) 1996  Grant R. Guenther <grant@torque.net>
		          Under the terms of the GNU public license.

	This is a driver for the parallel port versions of SyQuest's 
        EZ135 and EZ230 removable media disk drives.  
        
        Special thanks go to Pedro Soria-Rodriguez for his help testing 
	the EZFlyer 230 support.

	The drive is actually SyQuest's IDE product with a
        ShuttleTech IDE <-> parallel converter chip built in.

	To compile the driver, ensure that /usr/include/linux and
        /usr/include/asm are links to the correct include files for 
        the target system. Then compile the driver with 

		cc -D__KERNEL__ -DMODULE -O2 -c ez.c

        If you are using MODVERSIONS, add the following to the cc command:

		-DMODVERSIONS -I /usr/include/linux/modversions.h

	You must then load it with insmod.

	Before attempting to access the new driver, you will need to
        create some device special files.  The following commands will
	do that for you:

		mknod /dev/eza  b 40 0
		mknod /dev/eza1 b 40 1
		mknod /dev/eza2 b 40 2
		mknod /dev/eza3 b 40 3
		mknod /dev/eza4 b 40 4
		chown root:disk /dev/ez*
		chmod 660 /dev/ez*

	You can make devices for more partitions (up to 15) if you need to.

	You can alter the port used by the driver in two ways:  either
        change the definition of EZ_BASE or modify the ez_base variable
        on the insmod command line, for example:

		insmod ez ez_base=0x3bc

	The driver can detect if the parallel port supports 8-bit
        transfers.  If so, it will use them.  You can force it to use
        4-bit (nybble) mode by setting the variable ez_nybble to 1.

	The driver can be used with or without interrupts.  If an IRQ
        is specified in the variable ez_irq, the driver will use it.
        If ez_irq is set to 0, an alternative, polling-based, strategy 
	will be used.

	If you experience timeout errors while using this driver - and
        you have enabled interrupts - try disabling the interrupt.  I
        have heard reports of some parallel ports having exceptionally
        unreliable interrupts.  This could happen on misconfigured 
        systems in which an inactive sound card shares the same IRQ with 
        the parallel port. (Remember that most people do not use the
        parallel port interrupt for printing.)

	It would be advantageous to use multiple mode transfers,
        but ShuttleTech's driver does not appear to use them, so I'm not
        sure that the converter can handle it.

	It is not currently possible to connect a printer to the chained
        port on the EZ135p and expect Linux to use both devices at once.

	When the EZ230 powers on, the "standby timer" is set to about 6
        minutes:  if the drive is idle for that length of time, it will
        put itself into a low power standby mode.  It takes a couple of
        seconds for the drive to come out of standby mode.  So, if you
        load this driver while it is in standby mode, you will notice
        a "freeze" of a second or two as the driver waits for the EZ230
        to come back to life.  Once loaded, this driver disables the
        standby timer (until you next power up the EZ230 ...)

	Keep an eye on http://www.torque.net/ez135.html for news and
        other information about the driver.  If you have any problems
        with this driver, please send me, grant@torque.net, some mail 
        directly before posting into the newsgroups or mailing lists.

*/

#define	EZ_VERSION	"0.11"

#define	EZ_BASE		0x378
#define EZ_IRQ		7
#define EZ_REP		4

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/tqueue.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define EZ_BITS	   4			/* compatible with SCSI version */
#define EZ_MAJOR   40			/* as assigned by hpa */

#define MAJOR_NR EZ_MAJOR

/* set up defines for blk.h,  why don't all drivers do it this way ? */

#define DEVICE_NAME "ez"
#define DEVICE_REQUEST do_ez_request
#define DEVICE_NR(device) (MINOR(device)>>EZ_BITS)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)

#include <linux/blk.h>

#define EZ_PARTNS  (1<<EZ_BITS)

#define EZ_LOG_HEADS	64		
#define EZ_LOG_SECTS	32		/* SCSI compatible logical geometry */

#define EZ_SIGOFF	54
#define EZ_SIG		"ySuQse tZE"
#define EZ_SIGLEN	10
#define EZ_ID_LEN	14

#define EZ_TMO  	250		/* interrupt timeout in jiffies */

#define EZ_SPIN_DEL     50		/* spin delay in micro-seconds  */

#define EZ_SPIN		(10000/EZ_SPIN_DEL)*EZ_TMO  
#define EZ_ISPIN	(10000/EZ_SPIN_DEL)*20
#define EZ_DELAY        udelay(EZ_SPIN_DEL)

#define STAT_ERR	0x00001
#define STAT_INDEX	0x00002
#define STAT_ECC	0x00004
#define STAT_DRQ	0x00008
#define STAT_SEEK	0x00010
#define STAT_WRERR	0x00020
#define STAT_READY	0x00040
#define STAT_BUSY	0x00080

#define ERR_AMNF	0x00100
#define ERR_TK0NF	0x00200
#define ERR_ABRT	0x00400
#define ERR_MCR		0x00800
#define ERR_IDNF	0x01000
#define ERR_MC		0x02000
#define ERR_UNC		0x04000
#define ERR_TMO		0x10000

#define IDE_READ	0x20
#define IDE_WRITE	0x30
#define IDE_STANDBY     0x96
#define IDE_DOORLOCK	0xde
#define IDE_DOORUNLOCK  0xdf
#define IDE_ACKCHANGE   0xdb
#define IDE_IDENTIFY    0xec

int ez_init(void);
void ez_setup(char * str, int * ints);
#ifdef MODULE
void cleanup_module( void );
#endif
static void ez_geninit(struct gendisk *ignored);
static int ez_open(struct inode *inode, struct file *file);
static void do_ez_request(void);
static int ez_ioctl(struct inode *inode,struct file *file,
                    unsigned int cmd, unsigned long arg);
static void ez_release (struct inode *inode, struct file *file);
static int ez_revalidate(kdev_t dev);
static int ez_check_media(kdev_t dev);
static void ez_get_capacity( void );
static int ez_detect(void);
static void do_ez_read(void);
static void do_ez_write(void);
static void ez_media_check(void);
static void ez_doorlock(int func);
static void ez_interrupt( int irq, void * dev_id, struct pt_regs * regs);
static void ez_pseudo( void *data);
static void ez_timer_int( unsigned long data);
static void do_ez_read_drq( void );
static void do_ez_write_done( void );

static struct hd_struct ez[EZ_PARTNS];
static int ez_sizes[EZ_PARTNS];
static int ez_blocksizes[EZ_PARTNS];

static int	ez_base = EZ_BASE;
static int      ez_irq = EZ_IRQ;
static int	ez_rep = EZ_REP;
static int	ez_nybble = 0;		/* force 4-bit mode ? */

static int ez_valid = 0;		/* OK to open */
static int ez_access = 0;		/* count of active opens ... */
static int ez_changed = 0;		/* Did we see new media on open ? */
static int ez_capacity = 512*16*32;     /* Size of this volume in sectors */
static int ez_heads = 16;		/* physical geometry */
static int ez_sectors = 32;
static int ez_mode = 1;			/* 4- or 8-bit mode */
static int ez_loops = 0;		/* counter for pseudo-interrupts */
static int ez_timeout = 0;		/* did the interrupt time out ? */
static int ez_int_seen = 0;		/* have we ever seen an interrupt ? */
static int ez_busy = 0;			/* request being processed ? */
static int ez_block;			/* address of next requested block */
static int ez_count;			/* number of blocks still to do */
static char * ez_buf;			/* buffer for request in progress */
static char ez_scratch[512];		/* scratch block buffer */
static void (*ez_continuation)(void);	/* i/o completion handler */

char	*ez_errs[17] = { "ERR","INDEX","ECC","DRQ","SEEK","WRERR",
			 "READY","BUSY","AMNF","TK0NF","ABRT","MCR",
			 "IDNF","MC","UNC","???","TMO"};

static struct tq_struct ez_tq = {0,0,ez_pseudo,NULL};
static struct timer_list ez_timer = {0,0,0,0,ez_timer_int};
static struct wait_queue *ez_wait_open = NULL;

/* kernel glue structures */

static struct gendisk ez_gendisk = {
	MAJOR_NR,	/* Major number */
	"ez",		/* Major name */
	EZ_BITS,	/* Bits to shift to get real from partition */
	EZ_PARTNS,      /* Number of partitions per real */
	1,		/* maximum number of real */
	ez_geninit,	/* init function */
	ez,		/* hd struct */
	ez_sizes,	/* block sizes */
	0,		/* number */
        NULL,		/* internal */
	NULL		/* next */
};

static struct file_operations ez_fops = {
	NULL,			/* lseek - default */
	block_read,		/* read - general block-dev read */
	block_write,		/* write - general block-dev write */
	NULL,			/* readdir - bad */
	NULL,			/* select */
	ez_ioctl,		/* ioctl */
	NULL,			/* mmap */
	ez_open,		/* open */
	ez_release,		/* release */
	block_fsync,		/* fsync */
	NULL,			/* fasync */
	ez_check_media,         /* media change ? */
	ez_revalidate		/* revalidate new media */
};

__initfunc(int ez_init (void))	/* preliminary initialisation */

{	
	if (register_blkdev(MAJOR_NR,"ez",&ez_fops)) {
		printk("ez_init: unable to get major number %d\n",MAJOR_NR);
		return -1;
	}
	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
	read_ahead[MAJOR_NR] = 8;	/* 8 sector (4kB) read ahead */
	ez_gendisk.next = gendisk_head;
	gendisk_head = &ez_gendisk;

	return 0;
}

__initfunc(static void ez_geninit (struct gendisk *ignored))    /* real init */

{	int i;

	ez_gendisk.nr_real = 0;
	
	if (ez_detect()) {
		ez_busy = 0;
		ez_valid = 1;
		ez_gendisk.nr_real = 1;
		ez[0].nr_sects = ez_capacity;
		for(i=0;i<EZ_PARTNS;i++) ez_blocksizes[i] = 1024;
		blksize_size[MAJOR_NR] = ez_blocksizes;
	} 
#ifdef MODULE
	  else cleanup_module();
#endif
}

static int ez_open (struct inode *inode, struct file *file)

{	int dev = DEVICE_NR(inode->i_rdev);

	if (dev >= ez_gendisk.nr_real) return -ENODEV;

	MOD_INC_USE_COUNT;

	while (!ez_valid) sleep_on(&ez_wait_open);
	ez_access++;
	ez_media_check();
	ez_doorlock(IDE_DOORLOCK);
	return 0;
}

static void do_ez_request (void)

{       int	dev;

	if (ez_busy) return;
repeat:
	if ((!CURRENT) || (CURRENT->rq_status == RQ_INACTIVE)) return;
	INIT_REQUEST;

	dev = MINOR(CURRENT->rq_dev);
	ez_block = CURRENT->sector;
	ez_count = CURRENT->nr_sectors;

	if ((dev >= EZ_PARTNS) || ((ez_block+ez_count) > ez[dev].nr_sects)) {
		end_request(0);
		goto repeat;
	}

	ez_block += ez[dev].start_sect;
	ez_buf = CURRENT->buffer;

	if (CURRENT->cmd == READ) do_ez_read();
	else if (CURRENT->cmd == WRITE) do_ez_write();
	else {  end_request(0);
		goto repeat;
	}
}

static int ez_ioctl(struct inode *inode,struct file *file,
		    unsigned int cmd, unsigned long arg)

{	struct hd_geometry *geo = (struct hd_geometry *) arg;
	int dev, err;

	if ((!inode) || (!inode->i_rdev)) return -EINVAL;
	dev = MINOR(inode->i_rdev);
	if (dev >= EZ_PARTNS) return -EINVAL;

	switch (cmd) {
	    case HDIO_GETGEO:
		if (!geo) return -EINVAL;
		err = verify_area(VERIFY_WRITE,geo,sizeof(*geo));
		if (err) return err;
		put_user(ez_capacity/(EZ_LOG_HEADS*EZ_LOG_SECTS),
			 (short *) &geo->cylinders);
		put_user(EZ_LOG_HEADS, (char *) &geo->heads);
		put_user(EZ_LOG_SECTS, (char *) &geo->sectors);
	        put_user(ez[dev].start_sect,(long *)&geo->start);
		return 0;
	    case BLKRASET:
		if(!suser()) return -EACCES;
		if(!(inode->i_rdev)) return -EINVAL;
		if(arg > 0xff) return -EINVAL;
		read_ahead[MAJOR(inode->i_rdev)] = arg;
		return 0;
            case BLKRAGET:
		if (!arg) return -EINVAL;
		err = verify_area(VERIFY_WRITE,(long *) arg,sizeof(long));
		if (err) return (err);
		put_user(read_ahead[MAJOR(inode->i_rdev)],(long *) arg);
		return (0);
	    case BLKGETSIZE:
		if (!arg) return -EINVAL;
		err = verify_area(VERIFY_WRITE,(long *) arg,sizeof(long));
		if (err) return (err);
		put_user(ez[dev].nr_sects,(long *) arg);
		return (0);
	    case BLKFLSBUF:
		if(!suser())  return -EACCES;
		if(!(inode->i_rdev)) return -EINVAL;
		fsync_dev(inode->i_rdev);
	        invalidate_buffers(inode->i_rdev);
		return 0;
	    case BLKRRPART:
		return ez_revalidate(inode->i_rdev);
	    RO_IOCTLS(inode->i_rdev,arg);
	    default:
	        return -EINVAL;
	}
}

static void ez_release (struct inode *inode, struct file *file)

{	kdev_t devp;

	devp = inode->i_rdev;
	if (DEVICE_NR(devp) == 0)  {
		fsync_dev(devp);
		invalidate_inodes(devp);
		invalidate_buffers(devp);
		ez_access--;
		if (!ez_access) ez_doorlock(IDE_DOORUNLOCK);
		MOD_DEC_USE_COUNT;
	}
}

static int ez_check_media( kdev_t dev)

{       int	t;

	t = ez_changed;
	ez_changed = 0;
	return t;
}

static int ez_revalidate(kdev_t dev)

{	int p;
	long flags;
	kdev_t devp;

	save_flags(flags);
	cli(); 
	if (ez_access > 1) {
		restore_flags(flags);
		return -EBUSY;
	}
	ez_valid = 0;
	restore_flags(flags);	

	for (p=(EZ_PARTNS-1);p>=0;p--) {
		devp = MKDEV(MAJOR_NR, p);
		fsync_dev(devp);
		invalidate_inodes(devp);
		invalidate_buffers(devp);
		ez[p].start_sect = 0;
		ez[p].nr_sects = 0;
	}

	ez_get_capacity();
	ez[0].nr_sects = ez_capacity;
	resetup_one_dev(&ez_gendisk,0);

	ez_valid = 1;
	wake_up(&ez_wait_open);

	return 0;
}

#ifdef MODULE

/* Glue for modules ... */

void	cleanup_module(void);

int	init_module(void)

{	int	err;
	long    flags;

	save_flags(flags);
	cli();

	err = ez_init();
	if (err) {
	    restore_flags(flags);
	    return err;
	}
	ez_geninit(&ez_gendisk);

	if (!ez_gendisk.nr_real) {
		restore_flags(flags);
		return -1;
	}

	ez_valid = 0;
	resetup_one_dev(&ez_gendisk,0);
	ez_valid = 1;

	restore_flags(flags);
	return 0;
}

void	cleanup_module(void)

{	struct gendisk **gdp;
	long flags;

	save_flags(flags);
	cli();

	unregister_blkdev(MAJOR_NR,"ez");

	for(gdp=&gendisk_head;*gdp;gdp=&((*gdp)->next))
		if (*gdp == &ez_gendisk) break;
	if (*gdp) *gdp = (*gdp)->next;

	if (ez_gendisk.nr_real) {
		release_region(ez_base,3);
		if (ez_irq) free_irq(ez_irq,NULL);
	}

	restore_flags(flags);
}

#else 

/* ez_setup:  process lilo command parameters ...

   syntax:	ez=base[,irq[,rep[,nybble]]]
*/

__initfunc(void ez_setup(char *str, int *ints))

{       if (ints[0] > 0) ez_base = ints[1];
        if (ints[0] > 1) ez_irq = ints[2];
        if (ints[0] > 2) ez_rep = ints[3];
        if (ints[0] > 3) ez_nybble = ints[4];
} 

#endif

/* Now the actual hardware interface to the EZ135p */

static void    out_p( short port, char byte)

{       int i;

	for(i=0;i<ez_rep;i++) outb(byte,ez_base+port);
}

static int     in_p( short port)

{       int i;
	char c;

	c=inb(ez_base+port);
	for(i=1;i<ez_rep;i++) c=inb(ez_base+port);
	return c & 0xff;
}

#define w0(byte)  out_p(0,byte)
#define w2(byte)  out_p(2,byte)
#define r0()      (in_p(0) & 0xff)
#define r1()      (in_p(1) & 0xff)

/*  register access functions */

static int read_regr( char regr )

{	int h, l;

	if (ez_mode == 1) {	/* nybble mode */
	    w0(regr);
	    w2(1); w2(3);
	    l = r1() >> 4;
	    w2(4);
	    h = r1() & 0xf0;
	    return h + l;
	} else {		/* byte mode */
	    w0(regr+0x20);
	    w2(1); w2(0x25);
	    h = r0();
	    w2(4);
	    return h;
	}
}	

static void write_regr( char regr, char val )

{	w0(regr);
	w2(1);
	w0(val);
	w2(4);
}

/* connect / disconnect code */

static void prefix( char byte )

{	w2(4); w0(0x22); w0(0xaa); w0(0x55); w0(0); 
	w0(0xff); w0(0x87); w0(0x78); w0(byte);
        w2(5); w2(4); w0(0xff); 
}

static void connect ( void  )

{	prefix(0x40); prefix(0x50); prefix(0xe0);
        w0(0); w2(1); w2(4);
	read_regr(0xd);
	write_regr(0x6d,0xe8);
	write_regr(0x6c,0x1c);
	write_regr(0x72,0x10);
	write_regr(0x6a,0x38);
	write_regr(0x68,0x10);
	read_regr(0x12);
	write_regr(0x72,0x10);
	read_regr(0xd);
	write_regr(0x6d,0xaa);
	write_regr(0x6d,0xaa);
}

static void disconnect ( void )

{	read_regr(0xd);
	write_regr(0x6d,0xa8);
	prefix(0x30);
} 

/* basic i/o */

static void read_block( char * buf )

/* the nybble mode read has a curious optimisation in it: there are actually
   five bits available on each read.  The extra bit is used to signal that
   the next nybble is identical ...  I wonder how much research went into
   designing this use of the extra bit ?
*/

{	int	j, k, n0, n1, n2, n3;

	read_regr(0xd); write_regr(0x6d,0xe9);

	j = 0;
	if (ez_mode == 1) {		/* nybble mode */

	    w0(7); w2(1); w2(3); w0(0xff);
	    for(k=0;k<256;k++) {
		w2(6); n0 = r1();
		if (n0 & 8) n1 = n0; else { w2(4); n1 = r1(); }
		w2(7); n2 = r1();
		if (n2 & 8) n3 = n2; else { w2(5); n3 = r1(); }
		buf[j++] = (n0 >> 4) + (n1 & 0xf0);
		buf[j++] = (n2 >> 4) + (n3 & 0xf0);
	    }

	} else {			/* byte mode */

	    w0(0x27); w2(1); w2(0x25); w0(0);
	    for(k=0;k<256;k++) {
		w2(0x24); buf[j++] = r0();
       		w2(0x25); buf[j++] = r0();
	    }
	    w2(0x26); w2(0x27); w0(0); w2(0x25); w2(4);

	}
}

static void write_block( char * buf )

{	int	j;

	read_regr(0xd); write_regr(0x6d,0xe9);

	w0(0x67); w2(1); w2(5);
	for(j=0;j<256;j++) {
		w0(buf[2*j]); w2(4);
		w0(buf[2*j+1]); w2(5);
	}
	w2(7); w2(4);
}

/*  ide command interface */

void	ez_print_error( char * msg, int status )

{	char	*e, *p;
        int	i;

	e = ez_scratch;
	for(i=0;i<18;i++) if (status & (1<<i)) {
		p = ez_errs[i];
		while ((*e++=*p++));
		*(e-1) = ' ';
	}
	if (status) e--;
	*e = 0;
	printk("ez: %s: status = 0x%x (%s)\n",msg,status,ez_scratch);
}

static int wait_for( int w, char * msg )    /* polled wait */

{	int	k, r, e;

	k=0;
	while(k < EZ_SPIN) { 
	    r = read_regr(0x1f);
            k++;
	    if (ez_timeout) break;
	    if (((r & w) == w) && !(r & STAT_BUSY)) break;
	    EZ_DELAY;
	}
	e = (read_regr(0x19)<<8) + r;
	if ((k >= EZ_SPIN) || ez_timeout) e |= (ERR_TMO|STAT_ERR);
	if ((e & STAT_ERR) & (msg != NULL)) ez_print_error(msg,e);
	return e;
}

static void send_command( int n, int s, int h, int c0, int c1, int func )

{
	read_regr(0xd); write_regr(0x6d,0xa9);

	write_regr(0x76,0);		
	write_regr(0x79,0);	/* the IDE task file */
	write_regr(0x7a,n);
	write_regr(0x7b,s);
	write_regr(0x7c,c0);
	write_regr(0x7d,c1);
	write_regr(0x7e,0xa0+h);
	write_regr(0x7f,func);

	udelay(1);
}

static void ez_ide_command( int func, int block )

{	int c1, c0, h, s;

	s  = ( block % ez_sectors) + 1;
	h  = ( block / ez_sectors) % ez_heads;
	c0 = ( block / (ez_sectors*ez_heads)) % 256;
	c1 = ( block / (ez_sectors*ez_heads*256));

	send_command(1,s,h,c0,c1,func);
}

static void ez_gate_intr( int flag )

{	if (flag) write_regr(0x6d,0x39);  /* gate interrupt line to bus */
     	if (flag && ez_irq) w2(0x14);	  /* enable IRQ */
	if (!flag) w2(4);	          /* disable IRQ */
}

static int check_int( void )	/* is the interrupt bit set  ?  */

{	return (r1() & 0x40);
}

static void ez_doorlock( int func )

{	connect();
	if (wait_for(STAT_READY,"Lock") & STAT_ERR) {
		disconnect();
		return;
	}
	ez_ide_command(func,0);
	wait_for(STAT_READY,"Lock done");
	disconnect();
}

/* ez_media_check: check for and acknowledge the MC flag */

__initfunc(static void ez_media_check( void ))

{	int r;

	ez_changed = 0;
	connect();
	r = wait_for(STAT_READY,"Media check ready");
	if (!(r & STAT_ERR)) {
		ez_ide_command(IDE_READ,0);  /* try to read block 0 */
		r = wait_for(STAT_DRQ,"Media check");
		if (!(r & STAT_ERR)) read_block(ez_scratch);
	} else ez_changed = 1;   /* say changed if other error */
	if (r & ERR_MC) {
		ez_changed = 1;
		ez_ide_command(IDE_ACKCHANGE,0);
		wait_for(STAT_READY,"Ack. media change");
	}
	disconnect();
}

__initfunc(static int ez_identify( void ))


{	int	k, r;

	connect();
	wait_for(0,NULL);  /* wait until not busy, quietly */
	ez_ide_command(IDE_IDENTIFY,0);

	if (ez_irq) {	  		/* check that the interrupt works */
		ez_gate_intr(1);
		k = 0;
		while ((k++ < EZ_ISPIN) && !ez_int_seen) EZ_DELAY;
		ez_gate_intr(0);
		r = read_regr(0x1f);
		if ((!ez_int_seen) || !(r & STAT_DRQ)) {
			free_irq(ez_irq,NULL);
			ez_irq = 0;
		}
	}

        if (wait_for(STAT_DRQ,NULL) & STAT_ERR) {
		disconnect();
		return 0;
	}
	read_block(ez_scratch);
	disconnect();
	return 1;
}

#define  word_val(n) 	(ez_scratch[2*n]+256*ez_scratch[2*n+1])

__initfunc(static void ez_get_capacity( void ))

{	int	ez_cylinders;

	connect();
	wait_for(0,NULL);
	ez_ide_command(IDE_IDENTIFY,0);
	if (wait_for(STAT_DRQ,"Get capacity") & STAT_ERR) {
		disconnect();
		return;
	}
	read_block(ez_scratch);
	disconnect();
	ez_sectors = word_val(6);
	ez_heads = word_val(3);
	ez_cylinders  = word_val(1);
	ez_capacity = ez_sectors*ez_heads*ez_cylinders;
	printk("ez: Capacity = %d, (%d/%d/%d)\n",ez_capacity,ez_cylinders,
		ez_heads,ez_sectors);
}

__initfunc(static void ez_standby_off( void ))

{	connect();
	wait_for(0,NULL);
	send_command(0,0,0,0,0,IDE_STANDBY);
	wait_for(0,NULL);
	disconnect();
}

__initfunc(static int ez_port_check( void )) 	/* check for 8-bit port */

{	int	r;

        w2(0); 
	w0(0x55); if (r0() != 0x55) return 0;
	w0(0xaa); if (r0() != 0xaa) return 0;
	w2(0x20); w0(0x55); r = r0(); w0(0xaa);
	if (r0() == r) return 2;
	if (r0() == 0xaa) return 1;
	return 0;
}

__initfunc(static int ez_detect( void ))

{	int j, k;
	char sig[EZ_SIGLEN] = EZ_SIG;
	char id[EZ_ID_LEN+1];
	long	flags;

	if (check_region(ez_base,3)) {
		printk("ez: Ports at 0x%x are not available\n",ez_base);
		return 0;
	}

	ez_mode = ez_port_check();
	if (!ez_mode) {
		printk("ez: No parallel port at 0x%x\n",ez_base);
		return 0;
	}

	if (ez_irq && request_irq(ez_irq,ez_interrupt,0,"ez",NULL)) ez_irq = 0;

	if (ez_nybble) ez_mode = 1;
	    
	request_region(ez_base,3,"ez");

	save_flags(flags);
	sti();

	k = 0;
	if (ez_identify()) {
		k = 1;
		for(j=0;j<EZ_SIGLEN;j++) 
		   k &= (ez_scratch[j+EZ_SIGOFF] == sig[j]);
	}
	if (k) { 
	    for(j=0;j<EZ_ID_LEN;j++) id[j^1] = ez_scratch[j+EZ_SIGOFF];
	    id[EZ_ID_LEN] = 0;
	    if (!ez_irq) printk("ez %s: %s at 0x%x, %d-bit mode.\n",
				EZ_VERSION,id,ez_base,4*ez_mode);
	    else printk("ez %s: %s at 0x%x, IRQ %d, %d-bit mode.\n",
                        EZ_VERSION,id,ez_base,ez_irq,4*ez_mode);
	    ez_standby_off();
	    ez_media_check();
	    ez_get_capacity();
	    restore_flags(flags);
	    return 1;
	}
	restore_flags(flags);
	release_region(ez_base,3);
	if (ez_irq) free_irq(ez_irq,NULL);
	printk("ez: Drive not detected\n");
	return 0;
}

/* interrupt management */

static void ez_set_intr( void (*continuation)(void) )

{	ez_continuation = continuation;
	ez_loops = 1;  ez_timeout = 0;
	ez_gate_intr(1);
	if (ez_irq) {
		ez_timer.expires = jiffies + EZ_TMO;
		add_timer(&ez_timer);
	} else queue_task(&ez_tq,&tq_scheduler); 	
}

static void ez_pseudo( void *data )

{	void (*con)(void);

	ez_timeout = (ez_loops >= EZ_TMO);
	if (check_int() || ez_timeout) {
		con = ez_continuation;
		ez_continuation = NULL;
		if (con) con();
	} else {	
		ez_loops++;
	        queue_task(&ez_tq,&tq_scheduler);
	}
}

static void ez_timer_int( unsigned long data)

{	void  (*con)(void);

	con = ez_continuation;
	if (!con) return;
	ez_continuation = NULL;
	ez_gate_intr(0);
	ez_timeout = 1;
	con();
}

static void ez_interrupt( int irq, void * dev_id, struct pt_regs * regs)

{	void  (*con)(void);

	ez_int_seen = 1;
	con = ez_continuation;
	if (!con) return;
	ez_gate_intr(0);
	del_timer(&ez_timer);
	ez_continuation = NULL;
	con();
}

/* The i/o request engine */

#define EZ_DONE(s) { disconnect(); end_request(s); ez_busy = 0;\
		     cli(); do_ez_request(); return; }

static void do_ez_read( void )

{	ez_busy = 1;
	if (!ez_count) {
		ez_busy = 0;
		return;
	}
	sti();
	connect();
	if (wait_for(STAT_READY,"do_ez_read") & STAT_ERR) EZ_DONE(0);
	ez_ide_command(IDE_READ,ez_block);
	ez_set_intr(do_ez_read_drq);
}

static void do_ez_read_drq( void )

{	sti();
	if (wait_for(STAT_DRQ,"do_ez_read_drq") & STAT_ERR) EZ_DONE(0);
	read_block(ez_buf);
	ez_count--;
	if (ez_count) {
		ez_buf += 512;
		ez_block++;
		disconnect();
		do_ez_read();
		return;
	}
	EZ_DONE(1);
}

static void do_ez_write( void )

{	ez_busy = 1;
	if (!ez_count) {
		ez_busy = 0;
		return;
	}
	sti();
	connect();
	if (wait_for(STAT_READY,"do_ez_write") & STAT_ERR) 
	   EZ_DONE(0);
	ez_ide_command(IDE_WRITE,ez_block);
	if (wait_for(STAT_DRQ,"do_ez_write_drq") & STAT_ERR) 
	   EZ_DONE(0);
	write_block(ez_buf);
	ez_set_intr(do_ez_write_done);
}

static void do_ez_write_done( void )

{	sti();
	if (wait_for(STAT_READY,"do_ez_write_done") & STAT_ERR) EZ_DONE(0);
	ez_count--;
	if (ez_count) {
		ez_buf += 512;
		ez_block++;
		disconnect();
		do_ez_write();
		return;
	}
	EZ_DONE(1);
}

/* end of ez.c */
