/* ps2esdi driver based on assembler code by Arindam Banerji,
   written by Peter De Schrijver */
/* Reassuring note to IBM : This driver was NOT developed by vice-versa
   engineering the PS/2's BIOS */
/* Dedicated to Wannes, Tofke, Ykke, Godot, Killroy and all those other lovely
   fish out there... */
/* This code was written during the long and boring WINA elections 1994 */
/* Thanks to Arindam Banerij for giving me the source of his driver */
/* This code may be freely distributed and modified in any way, as long as these
   notes remain intact */

/*  Revised: 05/07/94 by Arindam Banerji (axb@cse.nd.edu) */


/* TODO : 
 + Timeouts
 + Get disk parameters
 + DMA above 16MB
 + reset after read/write error
*/
#include <linux/config.h>
#ifdef CONFIG_BLK_DEV_PS2

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/genhd.h>
#include <linux/ps2esdi.h>

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

#define MAJOR_NR 31
#define PS2ESDI_IRQ 14
#define MAX_HD 2
#define MAX_RETRIES 5
#define MAX_16BIT 65536
#define ESDI_TIMEOUT   0xf000
#define ESDI_STAT_TIMEOUT 4

#define CMD_BLK_LENGTH 4
#include "blk.h"

static inline unsigned char CMOS_READ(unsigned char addr)
{
	outb_p(addr,0x70);
	return inb_p(0x71);
}

static void reset_ctrl(void);

u_long ps2esdi_init(u_long mem_start, u_long mem_end);

static void ps2esdi_geninit(void);

static void do_ps2esdi_request(void);

static int ps2esdi_readwrite(int cmd,u_char drive, char *buffer,
						  u_int block,u_int count);

static void ps2esdi_fill_cmd_block(u_short *cmd_blk,u_short cmd,
       u_short cyl,u_short head,u_short sector,u_short length,u_char drive);

static int ps2esdi_out_cmd_blk(u_short *cmd_blk);

static int ps2esdi_wait_xfer(void);

static void ps2esdi_prep_dma(char *buffer,u_short length,u_char dma_xmode);

static int ps2esdi_do_dma(void);

static void ps2esdi_interrupt_handler(int unused);

static int ps2esdi_open(struct inode *inode,struct file *file);

static void ps2esdi_release (struct inode *inode, struct file *file);

static int ps2esdi_ioctl (struct inode *inode,struct file *file,
						     u_int cmd,u_long arg);

static int ps2esdi_reread_partitions(int dev);

u_int dma_arb_level;			/* DMA arbitration level */

static struct wait_queue *ps2esdi_int = NULL, *ps2esdi_wait_open=NULL;
static u_int int_ret_code=0xffff;
static int access_count[MAX_HD]={0,};
static char ps2esdi_valid[MAX_HD]={0,};
static int ps2esdi_sizes[MAX_HD<<6]={0,};
static int ps2esdi_blocksizes[MAX_HD<<6]={0,};
static int ps2esdi_drives=0;
static struct hd_struct ps2esdi[MAX_HD<<6];

struct ps2esdi_i_struct {
   unsigned int head,sect,cyl,wpcom,lzone,ctl;
   };
struct ps2esdi_i_struct ps2esdi_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };

static struct file_operations ps2esdi_fops = {
   NULL,                   /* lseek - default */
   block_read,             /* read - general block-dev read */
   block_write,            /* write - general block-dev write */
   NULL,                   /* readdir - bad */
   NULL,                   /* select */
   ps2esdi_ioctl,          /* ioctl */
   NULL,                   /* mmap */
   ps2esdi_open,           /* open */
   ps2esdi_release,        /* release */
   block_fsync             /* fsync */
};

static struct gendisk ps2esdi_gendisk = {
   MAJOR_NR,              /* Major number */
   "ps2esdi",             /* Major name */
   6,                     /* Bits to shift to get real from partition */
   1 << 6,                /* Number of partitions per real */
   MAX_HD,                /* maximum number of real */
   ps2esdi_geninit,       /* init function */
   ps2esdi,               /* hd struct */
   ps2esdi_sizes,         /* block sizes */
   0,                     /* number */
   (void *) ps2esdi_info, /* internal */
   NULL                   /* next */
};

/* initialization routine called by ll_rw_blk.c   */
u_long ps2esdi_init(u_long mem_start, u_long mem_end)
{

  /* register the device - pass the name, major number and operations
     vector .                                                 */
  if(register_blkdev(MAJOR_NR,"ps2esdi",&ps2esdi_fops)) {
      printk("%s: Unable to get major number %d\n",DEVICE_NAME,MAJOR_NR);
      return mem_start;
    }

   /* set up some global information - indicating device specific info */
   blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
   read_ahead[MAJOR_NR] = 8;  /* 8 sector (4kB) read ahead */

   /* some minor housekeeping - setup the global gendisk structure */
   ps2esdi_gendisk.next = gendisk_head;
   gendisk_head = &ps2esdi_gendisk;

    return mem_start;
}  /* ps2esdi_init */

/* handles boot time command line parameters */
void ed_setup(char *str, int *ints)
{
 int hdind = 0;

 /* handles 3 parameters only - corresponding to
    1. Number of cylinders
    2. Number of heads
    3. Sectors/track
  */

 if (ints[0] != 3)
     return;

  /* print out the information - seen at boot time */
  printk("%s: ints[0]=%d ints[1]=%d ints[2]=%d ints[3]=%d\n",
		       DEVICE_NAME,ints[0],ints[1],ints[2],ints[3]);

  /* set the index into device specific information table */
  if (ps2esdi_info[0].head != 0)
      hdind=1;

   /* set up all the device information */
   ps2esdi_info[hdind].head = ints[2];
   ps2esdi_info[hdind].sect = ints[3];
   ps2esdi_info[hdind].cyl = ints[1];
   ps2esdi_info[hdind].wpcom = 0;
   ps2esdi_info[hdind].lzone = ints[1];
   ps2esdi_info[hdind].ctl = (ints[2] > 8 ? 8 : 0);
   ps2esdi_drives = hdind+1;     /* increment index for the next time */
} /* ed_setup */

/* ps2 esdi specific initialization - called thru the gendisk chain */
static void ps2esdi_geninit(void)
{
  /*

	  The first part contains the initialization code
	  for the ESDI disk subsystem.  All we really do
	  is search for the POS registers of the controller
	  to do some simple setup operations.  First, we
	  must ensure that the controller is installed,
	  enabled, and configured as PRIMARY.  Then we must
	  determine the DMA arbitration level being used by
	  the controller so we can handle data transfer
	  operations properly.  If all of this works, then
	  we will set the INIT_FLAG to a non-zero value.
  */

    int drive, i, cmos_disks;
    extern struct drive_info drive_info;
    u_char *BIOS = (unsigned char *) &drive_info,status;
    u_int id;

    /* Search for the cadr which contains the ESDI adapter */
	for(i=0;i<NUMBER_OF_SLOTS;i++) {
		outb_p(i | SELECT_BIT,PORT_POS_SEL);
		id=((inb_p(PORT_POS_REG_1)<<8) + inb_p(PORT_POS_REG_0));
		if(id!=0xFFFF)
			printk("%s: found device %04X in slot %d\n",
							DEVICE_NAME,id,i);
		/* Is this a normal ESDI adapter or an integrated one ? */
		if((id==NRML_ESDI_ID) || (id==INTG_ESDI_ID))
			break;
	}  /* search for slots */

      /* Slot could not be found !!! */
	if(i==NUMBER_OF_SLOTS) {
		printk("%s: ESDI adapter not found\n",DEVICE_NAME);
		outb(0,PORT_POS_SEL);
		return;
	} /* slot not found - error */

     /* if we get this far - slot has been found - carry on */
		
      printk("%s: %s ESDI adapter found in slot %d\n",
	       DEVICE_NAME,(id==NRML_ESDI_ID) ? "normal" : "integrated",i);

     /* Found the slot - read the POS register 2 to get the necessary
	configuration and status information.  POS register 2 has the
	following information :
	 Bit           Function
	  7             reserved = 0
	  6             arbitration method
			 0 - fairness enabled
			 1 - fairness disabled, linear priority assignment
	  5-2            arbitration level
	  1              alternate address
			  0 - use addresses 0x3510 - 0x3517
			  1 - use addresses 0x3518 - 0x351f
	  0              adapter enable
      */
      status=inb_p(PORT_POS_REG_2);
      outb(0,PORT_POS_SEL);
      /* is it enabled ? */
      if(!(status & STATUS_ENABLED)) {
		printk("%s: ESDI adapter disabled\n",DEVICE_NAME);
		return;
	}	

	/* check for alternate addresses - not supported at present */
	if(status & STATUS_ALTERNATE) {
	   printk("%s: alternate ESDI adapter not supported\n",DEVICE_NAME);
	   return;
	}	
       /* get the dma arbitration level */
       dma_arb_level=  (status>>2) & 0xf;

       printk("%s: DMA arbitration level : %d\n",DEVICE_NAME,dma_arb_level);

       LITE_ON;
	reset_ctrl();
       LITE_OFF;

/*  The following part gets geometry from cmos... */

printk("%s: ps2esdi_drives : %d\n",DEVICE_NAME,ps2esdi_drives);
	if (!ps2esdi_drives) {	   
	     for (drive=0 ; drive<2 ; drive++) {
		   ps2esdi_info[drive].cyl = *(unsigned short *) BIOS;
		   ps2esdi_info[drive].head = *(2+BIOS);
		   ps2esdi_info[drive].wpcom = *(unsigned short *) (5+BIOS);
		   ps2esdi_info[drive].ctl = *(8+BIOS);
		   ps2esdi_info[drive].lzone = *(unsigned short *) (12+BIOS);
		   ps2esdi_info[drive].sect = *(14+BIOS);
		   BIOS += 16;
		}

	/*
		We querry CMOS about hard disks : it could be that 
		we have a SCSI/ESDI/etc controller that is BIOS
		compatable with ST-506, and thus showing up in our
		BIOS table, but not register compatable, and therefore
		not present in CMOS.

		Furthurmore, we will assume that our ST-506 drives
		<if any> are the primary drives in the system, and 
		the ones reflected as drive 1 or 2.

		The first drive is stored in the high nibble of CMOS
		byte 0x12, the second in the low nibble.  This will be
		either a 4 bit drive type or 0xf indicating use byte 0x19 
		for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.

		Needless to say, a non-zero value means we have 
		an AT controller hard disk for that drive.

		
	*/

		if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
			if (cmos_disks & 0x0f)
				ps2esdi_drives = 2;
			else
				ps2esdi_drives = 1;
printk("%s: cmos said ps2esdi_drives : %d. I assume 1 drive\n",
					      DEVICE_NAME,ps2esdi_drives);
ps2esdi_info[0].cyl=115;
ps2esdi_info[0].head=64;
ps2esdi_info[0].sect=32;
printk("%s: Drive's parameters : cyl : %d sect : %d head : %d\n",
DEVICE_NAME,ps2esdi_info[0].cyl,ps2esdi_info[0].sect,ps2esdi_info[0].head);
	ps2esdi_drives=1;
       }  /* if !ps2esdi_drives */

   i = ps2esdi_drives;
   while (i-- > 0) {
   ps2esdi[i<<6].nr_sects = 0;
   if (ps2esdi_info[i].head > 64) {
     printk("ps2esdi.c: ps/2 esdi interface disk with more than 16 heads detected,\n");
     printk("  probably due to non-standard sector translation. Giving up.\n");
     printk("  (disk %d: cyl=%d, sect=%d, head=%d)\n", i,
				ps2esdi_info[i].cyl,
				ps2esdi_info[i].sect,
				ps2esdi_info[i].head);
			if (i+1 == ps2esdi_drives)
				ps2esdi_drives--;
			continue;
	   } /* more than 64 heads ?? */
	   ps2esdi[i<<6].nr_sects = ps2esdi_info[i].head*
			    ps2esdi_info[i].sect*ps2esdi_info[i].cyl;
	    ps2esdi_valid[i]=1;
	} /* while ( i-- > 0 ) */

/* end of cmos code...*/

  /* finally this part sets up some global data structures etc. */
	if(request_irq(PS2ESDI_IRQ,ps2esdi_interrupt_handler,SA_INTERRUPT,"PS/2 ESDI")) {
	   printk("%s: Unable to get IRQ %d\n",DEVICE_NAME,PS2ESDI_IRQ);
	   return;
	}

	ps2esdi_gendisk.nr_real = ps2esdi_drives;

	for(i=0;i<(MAX_HD << 6);i++) ps2esdi_blocksizes[i] = 1024;
	blksize_size[MAJOR_NR] = ps2esdi_blocksizes;
}    /* ps2esdi_geninit */

/* strategy routine that handles most of the IO requests */
static void do_ps2esdi_request(void)
{

	u_int block,count,retry;
	int result;
  /* since, this routine is called with interrupts cleared - they must be
     before it finishes                                          */
	sti();
	/* loop thru and handle all requests */
	while(result=FAIL, CURRENT) {
	  /* standard macro that ensures that requests are really on the
	     list + sanity checks.                     */
	INIT_REQUEST;
		
	if((u_int)CURRENT->buffer+CURRENT->nr_sectors*512 > 16*MB) {
	       printk("%s: DMA above 16MB not supported\n",DEVICE_NAME);
	       end_request(FAIL);
	} /* check for above 16Mb dmas */

 if((CURRENT_DEV < ps2esdi_drives) &&
    (CURRENT->sector + CURRENT->nr_sectors <=
				     ps2esdi[MINOR(CURRENT->dev)].nr_sects))
  {
#if 0
printk("%s:got request. device : %d minor : %d command : %d  sector : %ld count : %ld\n",
DEVICE_NAME,CURRENT_DEV,MINOR(CURRENT->dev),CURRENT->cmd,CURRENT->sector,
		     CURRENT->nr_sectors);
#endif

     block=CURRENT->sector + ps2esdi[MINOR(CURRENT->dev)].start_sect;

#if 0
printk("%s: blocknumber : %d\n",DEVICE_NAME,block);
#endif
    count=CURRENT->nr_sectors;
    switch(CURRENT->cmd) {
      case READ:
	   for(retry=0;retry < MAX_RETRIES && !result;retry++) {
	      result=ps2esdi_readwrite(READ,CURRENT_DEV,CURRENT->buffer,
							    block,count);
	      if(!result) {
		printk("resetting...\n");
		reset_ctrl();
	      }
	   }

	   break;


      case WRITE:
	   for(retry=0;retry < MAX_RETRIES && !result;retry++) {
	      result=ps2esdi_readwrite(WRITE,CURRENT_DEV,CURRENT->buffer,
			       block,count);
	      if(!result) {
		printk("resetting...\n");
		reset_ctrl();
	      }
	   }
	   break;
				       break;
      default:
	    printk("%s: Unknown command\n",DEVICE_NAME);
	    break;
	   } /* handle different commands */
	} /* is reques is valid */

	 end_request(result);
    }  /* loop */
}  /* main strategy routine */

/* resets the ESDI adapter */
static void reset_ctrl(void)
{

     u_char status;
     u_long expire;

     /* read the ESDI status port - if the controller is not busy,
	simply do a soft reset (fast) - otherwise we'll have to do a
	hard (slow) reset.                                    */
     if(!(inb(ESDI_STATUS) & STATUS_BUSY)) {
		printk("%s: soft reset...\n",DEVICE_NAME);
		outb_p(CTRL_SOFT_RESET,ESDI_ATTN);
	}  /* soft reset */
	else {
		printk("%s: hard reset...\n",DEVICE_NAME);
		outb_p(CTRL_HARD_RESET,ESDI_CONTROL);
		expire=jiffies + 10;
		while(jiffies < expire)
			;
		outb_p(0,ESDI_CONTROL);
	      } /* hard reset */

	/* wait for an interrupt condition to be indicated by controller */
	expire= jiffies + 40*100;
	while(((inb(ESDI_STATUS) & STATUS_INTR)) && (jiffies < expire))
		;

	/* did the controller time out ?? */
	if(jiffies>=expire) {
		printk("%s: controller reset timed out\n",DEVICE_NAME);
		return;
	}

	/* read interrupt status */
	status=inb(ESDI_INTRPT);
	/* indicate EOI on attention register of controller */
	outb_p(CTRL_EOI,ESDI_ATTN);
	/* what do we want to do with reset failures ?? */
	if((status!=STATUS_RESET_FAIL) && (status!=0x01)) {
		printk("%s: Warning: controller reset failed status=%02X\n",DEVICE_NAME,status);
	}
	/* enable interrupts on the controller */
	outb_p(CTRL_ENABLE_INTR,ESDI_CONTROL);

} /* reset the controller */

/* called by the strategy routine to handle read and write requests */
static int ps2esdi_readwrite(int cmd,u_char drive, char *buffer,
					     u_int block,   u_int count)
{
	
      u_short temp,track,head,cylinder,sector;
      u_short cmd_blk[CMD_BLK_LENGTH];

   /* loop as many times as required to get all the data in  */
   while(count)
     {
      /* do some relevant arithmatic */
      temp=
	(count <  (2*MAX_16BIT/SECT_SIZE)) ? count : (2*MAX_16BIT/SECT_SIZE);
      track=block / ps2esdi_info[drive].sect;
      head=track % ps2esdi_info[drive].head;
      cylinder=track / ps2esdi_info[drive].head;
      sector=block % ps2esdi_info[drive].sect;
	
#if 0
printk("%s: cyl=%d head=%d sect=%d\n",DEVICE_NAME,cylinder,head,sector);
#endif
     /* call the routine that actually fills out a command block */
     ps2esdi_fill_cmd_block(cmd_blk,
       (cmd==READ) ? CMD_READ : CMD_WRITE,cylinder,head,sector,temp,drive);
		
     int_ret_code=0xffff;

     /* send the command block to the controller */
     if(ps2esdi_out_cmd_blk(cmd_blk)) {
	     printk("%s: Controller failed\n",DEVICE_NAME);
	     return FAIL;
       } /* check for failure to put out the command block */

#if 0
printk("%s: waiting for xfer\n",DEVICE_NAME);
#endif
     /* turn disk lights on */
       LITE_ON;

      /* wait for the transfer of the command blocks to finish */
	  if(!ps2esdi_wait_xfer()) {
	       printk("%s: Controller failed in send_cmd\n",DEVICE_NAME);
	       return FAIL;
	   }
       int_ret_code=0xffff;
			
#if 0
printk("%s: preparing DMA for %s\n",DEVICE_NAME,(cmd==READ) ? "read" : "write");
#endif
     ps2esdi_prep_dma(buffer,temp,(cmd==READ) ? DMA_READ_16 : DMA_WRITE_16);

#if 0
printk("%s: doing dma...\n",DEVICE_NAME);
#endif
      if(!ps2esdi_do_dma()) {
		 printk("%s: Controller failed in DMA\n",DEVICE_NAME);
		 return FAIL;
	 }

      /* turn the disk lights off */
      LITE_OFF;
	      int_ret_code=0xffff;
	      count-=temp; buffer+=temp*SECT_SIZE; block+=temp;
     } /* loop as required */

      return SUCCES;
} /* ps2esdi_readwrite */
				
/* fill out the command block */
static void ps2esdi_fill_cmd_block(u_short *cmd_blk,u_short cmd,
 u_short cyl,u_short head,u_short sector,u_short length,u_char drive)
{

	cmd_blk[0]=(drive<<5) | cmd;
	cmd_blk[1]=length;
	cmd_blk[2]=((cyl & 0x1f) << 11) | (head << 5) | sector;
	cmd_blk[3]=(cyl & 0x3E0) >> 5;

} /* fill out the command block */
		
/* write a command block to the controller */
static int ps2esdi_out_cmd_blk(u_short *cmd_blk)
{

   int i,j;
   u_char status;

   /* enable interrupts */
   outb(CTRL_ENABLE_INTR,ESDI_CONTROL);

   /* do not write to the controller, if it is busy */
#if 0
   for(i=70000;i && (inb(ESDI_STATUS) & STATUS_BUSY);i--);
#endif
   for(i=jiffies+ESDI_STAT_TIMEOUT;(i>jiffies) && (inb(ESDI_STATUS) &
                                   STATUS_BUSY););

#if 0
printk("%s: i(1)=%d\n",DEVICE_NAME,i);
#endif
   /* if device is still busy - then just time out */
   if(inb(ESDI_STATUS) & STATUS_BUSY) {
	    printk("%s: ps2esdi_out_cmd timed out (1)\n",DEVICE_NAME);
	    return ERROR;
     } /* timeout ??? */

   /* Set up the attention register in the controller */
   outb(((*cmd_blk) & 0xE0) | 1,ESDI_ATTN);
	
#if 0 
printk("%s: sending %d words to controller\n",DEVICE_NAME,(((*cmd_blk)>>14)+1)<<1);
#endif

  /* one by one send each word out */
   for(i=(((*cmd_blk)>>14)+1)<<1;i;i--)
    {
      status=inb(ESDI_STATUS);
#if 0
      for(j=70000;j && (status & STATUS_BUSY) && (status & STATUS_CMD_INF);
					       j--,status=inb(ESDI_STATUS));
#endif
      for(j=jiffies+ESDI_STAT_TIMEOUT;(j>jiffies) && (status & STATUS_BUSY) &&
                         (status & STATUS_CMD_INF);status=inb(ESDI_STATUS));
      if((status & (STATUS_BUSY | STATUS_CMD_INF))==STATUS_BUSY) {
#if 0
			printk("%s: sending %04X\n",DEVICE_NAME,*cmd_blk);
#endif
		 outw(*cmd_blk++,ESDI_CMD_INT);
       }
      else {
	printk("%s: ps2esdi_out_cmd timed out while sending command (status=%02X)\n",
						      DEVICE_NAME,status);
	return ERROR;
       }
     } /* send all words out */
   return OK;
}  /* send out the commands */

/* wait for a transfer to finish */
static int ps2esdi_wait_xfer(void)
{
#if 0
printk("%s: int_ret_code %02X\n",DEVICE_NAME,int_ret_code);
printk("%s: wait_xfer sleeping ...\n",DEVICE_NAME);
#endif

/* check on saved interrupt code */
 if(int_ret_code==0xFFff) {
       sleep_on(&ps2esdi_int);
  } /* wait for interrupt */

#if 0
printk("%s: int_ret_code %02X\n",DEVICE_NAME,int_ret_code);
#endif
 /* is this a transfer finish return code ? */
 if((int_ret_code & 0x1f) != INT_TRANSFER_REQ)
     printk("%s: wait_xfer: unexpected int_ret_code : %02X\n",
						 DEVICE_NAME,int_ret_code);

 return (int_ret_code & 0x1f) == INT_TRANSFER_REQ;
} /* wait for transfer to complete */
	
/* prepare for dma - do all the necessary setup */
static void ps2esdi_prep_dma(char *buffer,u_short length,u_char dma_xmode)
{

	u_int tc;

	cli();

	outb(dma_arb_level | DMA_MASK_CHAN,PORT_DMA_FN);

	outb(dma_arb_level | DMA_WRITE_ADDR,PORT_DMA_FN);
	outb((u_int) buffer & (u_int) 0xff,PORT_DMA_EX);
	outb(((u_int) buffer >> 8) & (u_int) 0xff,PORT_DMA_EX); 
	outb(((u_int) buffer >> 16) & (u_int) 0xff,PORT_DMA_EX); 

	outb(dma_arb_level | DMA_WRITE_TC,PORT_DMA_FN);
	tc=(length*SECT_SIZE/2)-1;
	outb(tc & 0xff,PORT_DMA_EX);
	outb((tc>>8) & 0xff,PORT_DMA_EX);

	outb(dma_arb_level | DMA_WRITE_MODE,PORT_DMA_FN);
	outb(dma_xmode,PORT_DMA_EX);

	outb(dma_arb_level | DMA_UNMASK_CHAN,PORT_DMA_FN);
 	
	sti();

} /* prepare for dma */

/* do the actual dma */
static int ps2esdi_do_dma(void)
{

      outb(CTRL_ENABLE_DMA | CTRL_ENABLE_INTR,ESDI_CONTROL);
#if 0
printk("%s: do_dma int_ret_code %02X\n",DEVICE_NAME,int_ret_code);
#endif
	if(int_ret_code==0xFFff) {
		sleep_on(&ps2esdi_int);
	}
	outb(CTRL_ENABLE_INTR,ESDI_CONTROL);
	
	if((int_ret_code & 0x1f) != INT_CMD_COMPLETE) 
		printk("%s: do_dma: unexpected int_ret_code : %02X\n",DEVICE_NAME,int_ret_code);

	return (int_ret_code & 0x1f) == INT_CMD_COMPLETE; 
}  /* start up the dma */
	
static void ps2esdi_interrupt_handler(int unused)
{

  int i,j;
  if(inb(ESDI_STATUS) & STATUS_INTR) {
	 int_ret_code=inb(ESDI_INTRPT);
#if 0
     printk("%s: esdi interrupt code : %02X\n",DEVICE_NAME,int_ret_code);
#endif

   if((int_ret_code & 0x0f) == INT_TRANSFER_REQ)  {
#if 0 
printk("%s: waking up...\n",DEVICE_NAME);
		  outb(0x20,0x20);
		  outb(0x20,0xa0);
#endif
		   wake_up(&ps2esdi_int);
      }
   else if((int_ret_code & 0x0f) == INT_ATTN_ERROR) {
 	 printk("%s: ATTN_ERROR interrupt: %02X\n",DEVICE_NAME,int_ret_code); 
#if 0 
			outb(0x20,0x20);
			outb(0x20,0xa0);
#endif
			wake_up(&ps2esdi_int);
	       }
   else {
	 if((int_ret_code & 0x0f) != INT_CMD_COMPLETE) {
	for(i=ESDI_TIMEOUT;i && (inb(ESDI_STATUS) & STATUS_BUSY);i--);
	    if(inb(ESDI_STATUS) & STATUS_BUSY) {
	     printk("%s: ps2esdi_out_cmd timed out while getting status",
							       DEVICE_NAME);
	     }
	     j=inw(ESDI_STT_INT)>>8;
	     printk("%s: %d status words follow :",DEVICE_NAME,j);
	     for(;j;j--) {
	      for(i=ESDI_TIMEOUT;i && (inb(ESDI_STATUS) & STATUS_BUSY);i--);
		  if(inb(ESDI_STATUS) & STATUS_BUSY) {
		   printk("%s: ps2esdi_out_cmd timed out while reading status",DEVICE_NAME);
		   return;
		  }
	      printk("%04X ",inw(ESDI_STT_INT));
	     }
	   printk("\n");
	 }
	 outb((int_ret_code & 0xe0) | 0x2,ESDI_ATTN);
	 wake_up(&ps2esdi_int);
	}
    }
   else {
		printk("%s: Spurious interrupt ?\n",DEVICE_NAME);
	}
} /* handle interrupts */

static int ps2esdi_open(struct inode *inode,struct file *file) {

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

#if 0
printk("%s: dev= %d\n",DEVICE_NAME,dev);
#endif

   if (dev < ps2esdi_drives) {
      while (!ps2esdi_valid[dev])
         sleep_on(&ps2esdi_wait_open);

      access_count[dev]++;

      return (0);
   }
   else
      return (-ENODEV);
}

static void ps2esdi_release (struct inode *inode, struct file *file) {

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

   if (dev < ps2esdi_drives) {
      sync_dev(dev);
      access_count[dev]--;
   }
}
	
static int ps2esdi_ioctl (struct inode *inode,struct file *file,u_int cmd,u_long arg) {

	struct ps2esdi_geometry *geometry = (struct ps2esdi_geometry *) arg;
	int dev = DEVICE_NR(MINOR(inode->i_rdev)),err;

	if (inode && (dev < ps2esdi_drives))
		switch (cmd) {
			case HDIO_GETGEO:
				if (arg) {
					if ((err = verify_area(VERIFY_WRITE,geometry,sizeof(*geometry))))
						return (err);
					put_fs_byte(ps2esdi_info[dev].head,(char *) &geometry->heads);
					put_fs_byte(ps2esdi_info[dev].sect,(char *) &geometry->sectors);
					put_fs_word(ps2esdi_info[dev].cyl,(short *) &geometry->cylinders);
					put_fs_long(ps2esdi[MINOR(inode->i_rdev)].start_sect,(long *) &geometry->start);

					return (0);
				}
				break;
			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 BLKGETSIZE:
				if (arg) {
					if ((err = verify_area(VERIFY_WRITE,(long *) arg,sizeof(long))))
						return (err);
					put_fs_long(ps2esdi[MINOR(inode->i_rdev)].nr_sects,(long *) arg);

					return (0);
				}
				break;
			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 (ps2esdi_reread_partitions(inode->i_rdev));
			RO_IOCTLS(inode->i_rdev,arg);
		}
	return (-EINVAL);
}

static int ps2esdi_reread_partitions(int dev) {

	int target = DEVICE_NR(MINOR(dev)),start = target << ps2esdi_gendisk.minor_shift,partition;

	cli(); ps2esdi_valid[target] = (access_count[target] != 1); sti();
	if (ps2esdi_valid[target])
		return (-EBUSY);

	for (partition = ps2esdi_gendisk.max_p - 1; partition >= 0; partition--) {
		sync_dev(MAJOR_NR << 8 | start | partition);
		invalidate_inodes(MAJOR_NR << 8 | start | partition);
		invalidate_buffers(MAJOR_NR << 8 | start | partition);
		ps2esdi_gendisk.part[start + partition].start_sect = 0;
		ps2esdi_gendisk.part[start + partition].nr_sects = 0;
	};

	ps2esdi_gendisk.part[start].nr_sects = ps2esdi_info[target].head * ps2esdi_info[target].cyl * ps2esdi_info[target].sect;
	resetup_one_dev(&ps2esdi_gendisk,target);

	ps2esdi_valid[target] = 1;
	wake_up(&ps2esdi_wait_open);

	return (0);
}
#endif
