/************************************************************
 *                                                          *
 *                  Linux EATA SCSI driver                  *
 *                                                          *
 *  based on the EATA proposal rev 2.0b, DPT DOS driver kit *
 *  some proprietary source (DPT Unix driver), several      *
 *  other linux scsi drivers and kernel documentation       *
 *                                                          *
 *  The driver currently:                                   *
 *      -has ISA detection routines                         *
 *      -has EISA detection routines (new)                  *
 *      -doesn't support more than one HA and one channel   *
 *      -gets sometimes timeouts when accessing more than   *
 *       one device "simultaniously"                        *
 *                                                          *
 *  ECS_emulation_sense() doesn't work and I don't know why * 
 *  Until this is working, the drive geometry has to be     * 
 *  hardcoded into the driver :-( (Any ideas ?)             *
 *                                                          *
 *  (c)1993,94 Michael Neuffer                              *
 *             Michael_Neuffer@wi2.maus.de (mails <16KB!)   *
 *             neuffer@goofy.zdv.uni-mainz.de               *
 *                                                          *
 *  This program is free software; you can redistribute it  * 
 *  and/or modify it under the terms of the GNU General     *
 *  Public License as published by the Free Software        *
 *  Foundation; either version 2 of the License, or         *
 *  (at your option) any later version.                     *
 *                                                          *
 *  This program is distributed in the hope that it will be *
 *  useful, but WITHOUT ANY WARRANTY; without even the      *
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A    *
 *  PARTICULAR PURPOSE.  See the GNU General Public License *
 *  for more details.                                       *
 *                                                          *
 *  You should have received a copy of the GNU General      * 
 *  Public License along with this kernel; if not, write to *
 *  the Free Software Foundation, Inc., 675 Mass Ave,       *
 *  Cambridge, MA 02139, USA.                               *
 *                                                          *
 *                                                          *
 * I have to thank DPT for their excellent support. I took  *
 * me almost a year and a stopover at their HQ on my first  *
 * trip to the USA to get it, but now they are very helpful *
 * and try to give me all the infos and support I need....  *
 *                                                          *
 ************************************************************
 *  last change: 02.10.94                                   *
 ************************************************************/

/* Look in eata.h for configuration information */

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/dma.h>
#include "eata.h"
#include "scsi.h"
#include "sd.h"

#define MAXISA  4
#define MAXEISA 16  

static unsigned int ISAbases[]={0x1F0,0x170,0x330,0x230};
static unsigned int EISAbases[]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
static struct eata_register *EATA_base;

static unsigned char dma_channel;
static struct geom_emul geometry;       /* Drive 1 & 2 geometry              */

static int eata_int_happened, eata_status, expecting_ints;

struct eata_cmd_queue outstanding[64];  /* We should allocate this dynamicly */

int cont_errs[]=
{
  DID_OK,         /* No error                    */
  DID_NO_CONNECT, /* Selection Timeout           */
  DID_TIME_OUT,   /* Command Timeout             */
  DID_RESET,      /* SCSI Bus Reset Received     */
  DID_ERROR,      /* Initial Controller Power up */
  DID_ERROR,      /* Unexpected Bus Phase        */
  DID_ERROR,      /* Unexpected Bus Free         */
  DID_PARITY,     /* Bus Parity Error            */
  DID_ERROR,      /* SCSI Hung                   */
  DID_ERROR,      /* Unexpected Message Rejected */
  DID_ERROR,      /* SCSI Bus Reset STUCK        */            
  DID_ERROR,      /* Auto Request-Sense Failed   */
  DID_PARITY,     /* Controller Ram Parity Error */
};

inline void end_of_command(void)
{
  Scsi_Cmnd *cmd;
  struct eata_ccb *cp;
  struct eata_sp  *sp;
  int i, result;
        /* Find out which CP caused the interrupt */
  for(i=0; i<64; i++)
    {
      if(outstanding[i].used && outstanding[i].sp.cont_stat & 0x80)
	break;
    }
  if(i==64)
    {
      printk("Got interrupt but no finished packet found!\n");
      return;
    }
  DBG(DBG_AHMON,outb(i, 0x80));                   /* Ahmon's special */     
  cmd=outstanding[i].cmd;
  cp=&outstanding[i].cp;
  sp=&outstanding[i].sp;
  outstanding[i].used=0;
  if(cmd->use_sg) scsi_free(cmd->host_scribble, 512);
  result=sp->scsi_stat;                             
  
  DBG(DBG_INTR,printk("end_of_command: queuenr: %d message bytes: %x,%x,%x\n",
		      i,sp->msg[0],sp->msg[1],sp->msg[2]));
 
  result|=COMMAND_COMPLETE<<8; 
  result|=cont_errs[sp->cont_stat&0x7f] << 16; /* Fixed the error array */
  cmd->result=result;
  
  cmd->scsi_done(cmd);
}

void eata_int_handler(int irq)
{
  eata_status=inb(((int)EATA_base)+HA_RSTATUS); /* Acknowledge interrupt */
  DBG(DBG_INTR, printk(" Interrupt %d received, expected: %d,Status: %x\n",irq,
		       expecting_ints,eata_status));
  if(expecting_ints==EXP_NOTHING)
    {
      printk("eata_int_handler: Unexpected interrupt!\n");
      return;
    }
  if(expecting_ints==EXP_NORMAL)
    {
      eata_int_happened=1;
      return;
    }
  end_of_command();
}

inline void eata_set_dma_address(int add)
{
  outb(add & 0x000000ff, ((int)EATA_base)+HA_WDMAADDR);
  outb((add & 0x0000ff00) >> 8, ((int)EATA_base)+HA_WDMAADDR+1);
  outb((add & 0x00ff0000) >> 16, ((int)EATA_base)+HA_WDMAADDR+2);
  outb((add & 0xff000000) >> 24, ((int)EATA_base)+HA_WDMAADDR+3);
}

const char *eata_info(void)
{
  static char *information="EATA SCSI Controller Driver\n";
  return information;
}

inline int eata_reverse(int arg)
{
  int result;
  result=(arg & 0xff000000) >> 24;
  result|=((arg & 0x00ff0000) >> 8);
  result|=((arg & 0x0000ff00) << 8);
  result|=((arg & 0x000000ff) << 24);
  return result;
}

int eata_queue(Scsi_Cmnd *cmd, void *(done)(struct scsi_cmnd *))
{
  int i=64,status;
  struct eata_sp  *sp;
  struct eata_ccb *cp;
  struct eata_sg_list *sglist;
  struct scatterlist *sl;

        /* Search for an open command slot.  If i==64, then
	   there must be none free.  If this is the case, keep
	   trying because an interrupt will eventually happen
	   that will free up a slot. 
	
	   This is assuming that interrupts are enabled when this
	   routine is called.  Is that true? 
        */

  i=64;
  while(i==64) for(i=0; i<64 && outstanding[i].used; i++);
  cli();
  outstanding[i].used=1; /* Claim it    */
  cmd->scsi_done=done;
  sp=&outstanding[i].sp;
  cp=&outstanding[i].cp;
  sp->cont_stat=0; /* Clear this out because the interrupt handler
		      checks it. Note that I clear it BEFORE I set the
		      pointer in outstanding[] */
  outstanding[i].cmd=cmd;
  sti();
  if(cmd->cmnd[0]==WRITE_6 || cmd->cmnd[0]==WRITE_10)
    cp->cp_option.Byte=HA_DATA_OUT;                       /* Output mode */
  else
    cp->cp_option.Byte=HA_DATA_IN;                        /* Input mode  */
  if(cmd->use_sg) {
    cp->cp_option.Byte|=HA_SC_GA;                         /* SG mode     */
    sglist=(struct eata_sg_list *)scsi_malloc(512);       /* Max needed 64 */
    if(!sglist) 
      {
	panic("eata_queue: scsi_malloc failed. No memory for SG list\n");
      }
    cmd->host_scribble=(unsigned char *)sglist;
    sl=(struct scatterlist *)cmd->request_buffer;
    for(i=0; i<cmd->use_sg; i++, sglist++, sl++)
      {
	sglist->data=(void *)eata_reverse((int )sl->address);
	sglist->len=eata_reverse((int )sl->length);
      }
    sglist=(struct eata_sg_list *)cmd->host_scribble;
  }
  cp->reqlen=38;
  cp->cp_option2.Byte=0x00;
  cp->cp_id=cmd->target;
  cp->cp_msg0=cmd->lun+HA_IDENTIFY_MSG+HA_G_DISCO_RECO; /*Those bits should always be set*/
  cp->cp_msg1=cp->cp_msg2=cp->cp_msg3=0;
  memset(cp->cp_cdb, 0, 12);
  memcpy(cp->cp_cdb, cmd->cmnd, COMMAND_SIZE(*cmd->cmnd));
  if(cmd->use_sg)
    {
      *((int *)cp->cp_dataDMA)=eata_reverse((int )sglist);
      *((int *)cp->cp_datalen)=eata_reverse(cmd->use_sg*8);
    }
  else
    {
      *((int *)cp->cp_datalen)=eata_reverse(cmd->request_bufflen);
      *((int *)cp->cp_dataDMA)=eata_reverse((int )cmd->request_buffer);
    }
  *((int *)cp->cp_statDMA)=eata_reverse((int )sp);
  *((int *)cp->cp_viraddr)=i;      
  DBG(DBG_QUEUE,printk("EATA_QUEUE pos: %d, com: %x, SG: %d\n",i,cmd->cmnd[0],
		       cmd->use_sg));
  status=inb(((int)EATA_base)+HA_RSTATUS);
  while(status & HA_SBSY) status=inb(((int)EATA_base)+HA_RSTATUS);
  expecting_ints=EXP_RETURN;
  eata_set_dma_address((int )cp);
  outb(EATA_CMD_DMA_SEND_CP, ((int)EATA_base)+HA_WCOMMAND); /* Let 'er 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 eata_command(Scsi_Cmnd *SCpnt)
{

  DBG(DBG_FUNC,printk("eata_command: calling eata_queue\n"));

  eata_queue(SCpnt, internal_done);
  
  while (!internal_done_flag);
  internal_done_flag = 0;
  return internal_done_errcode;
}

int eata_abort(Scsi_Cmnd *cmd)
{
  int i;

  /* Just act like we did something */
  DBG(DBG_ABNORM,printk("eata_abort()\n"));
  DELAY(200);
  /*
  eata_reset(cmd);
  */
  
  for(i=0; i<64; i++) 
    if((outstanding[i].cmd==cmd)&& outstanding[i].used)
      return(SCSI_ABORT_BUSY);
  return(SCSI_ABORT_NOT_RUNNING); /*SNOOZE*/
}

int eata_reset(Scsi_Cmnd *cmd)
{
  int i;
  long time;
  DBG(DBG_ABNORM,printk("eata_reset()\n"));
  DELAY(200);
  outb(EATA_CMD_RESET, ((int)EATA_base)+HA_WCOMMAND);
/*  if(cmd)
    cmd->flags|=NEEDS_JUMPSTART;                                         
*/  
  time=jiffies+75;
  while(time>jiffies);
  for(i=0; i<64; i++) outstanding[i].used=0;
  return(SCSI_RESET_WAKEUP);
}

int eata_biosparam(Scsi_Disk * disk, int dev, int geo[])
{
  int id;
  int size = disk->capacity;
  
  id=MINOR(dev);
  if (id) id>>=4;

  DBG(DBG_PROBE,printk("\nSize: %d, Device: %x, id: %x\n\n",size,dev,id));
  
  if(geometry.drv[0].id==id){
    geo[0]=geometry.drv[0].heads;
    geo[1]=geometry.drv[0].sectors;
    geo[2]=geometry.drv[0].cylinder;
  }else if(geometry.drv[1].id==id){
    geo[0]=geometry.drv[1].heads;
    geo[1]=geometry.drv[1].sectors;
    geo[2]=geometry.drv[1].cylinder;
  }else{
    if(size<0x200000){
      geo[0]=64;
      geo[1]=32;
    }else if(size<0x400000){
      geo[0]=65;
      geo[1]=63;
    }else if(size<0x800000){
      geo[0]=128;
      geo[1]=63;
    }else{
      geo[0]=255;
      geo[1]=63;
    }    
    geo[2]=size/(geo[0]*geo[1]);
  }
  return(0);
}

int get_conf_DMA(struct eata_register *base,struct get_conf *buf)
{
   long i;
  
  inb(((int)base)+HA_RSTATUS);               /* Clear any pending conditions */
 
  i=jiffies;
  expecting_ints=EXP_NORMAL;
  eata_int_happened=0;

  outb((unchar)((long)(buf)>>24),(int)base+HA_WDMAADDR+3);/*get config of    */
  outb((unchar)((long)(buf)>>16),(int)base+HA_WDMAADDR+2);/*poss. controllers*/
  outb((unchar)((long)(buf)>>8), (int)base+HA_WDMAADDR+1);
  outb((unchar)((long)(buf)),    (int)base+HA_WDMAADDR); 
  outb(EATA_CMD_DMA_READ_CONFIG, (int)base+HA_WCOMMAND);  /* set opcode      */

  while(!eata_int_happened && jiffies<i+200);             /* wait for 2 sec. */
  expecting_ints=EXP_NOTHING;

  if(!eata_int_happened){
    printk("eata.c get_conf_DMA: Controller timeout\n");
    return (0);
  }
  
  DBG(DBG_PROBE,printk("\nSignature: %c %c %c %c \n",(char)buf->gco_sig[0],
           (char)buf->gco_sig[1],(char)buf->gco_sig[2],(char)buf->gco_sig[3]));
  if((buf->gco_sig[0]=='E')&&(buf->gco_sig[1]== 'A')
       &&(buf->gco_sig[2]== 'T')&&(buf->gco_sig[3]== 'A')) { 
    DBG(DBG_PROBE,printk("EATA Controller found at %x EATA Level: %x\n",
		    (unsigned int) base,(unsigned int)(buf->gco_version>>4)));
    return(1);
  }
  return(0);
}

long find_EISA(int flag, struct get_conf *buf)
{
/* 
   Is there anyone using more than 2 controllers
   at the same time ? ie. more than one secondary ?
   I don't think so.
*/
  struct eata_register *base;
  int i;
  unsigned char pal1,pal2,pal3,*p;

  for(i=0; i<MAXEISA;i++){
    if(EISAbases[i]){                      /* Still a possibility ?          */
      base=(void *)0x1c88+(i*0x1000);
      p=(char*)base;
      pal1=*(p-8);
      pal2=*(p-7);
      pal3=*(p-6);
      if(((pal1==0x12)&&(pal2==0x14))||    /* Check for id tags              */
	 ((pal1==0x38)&&(pal2==0xa3)&&(pal3==0x82))||
	 ((pal1==0x5d)&&(pal2==0xc3)&&(pal3==0x90))||        /* SCIII PM2022 */
	 ((pal1==0x06)&&(pal2==0x94)&&(pal3==0x24)))
	if(get_conf_DMA(base,buf)){
	  if((!buf->bit2.SECOND)&&(buf->bit2.IRQ)&&(flag==-1)){
	                                   /* We just found a primary EISA   */
	    ISAbases[0]=0;                 /* so there is no prim. ISA cont. */
	    EISAbases[i]=0;                /* and we don't need to look for  */
	    return((long)base);            /* a sec. EISA controller here    */
	  }
	  else if((buf->bit2.SECOND)&&(buf->bit2.IRQ)&&(!flag)){ 
	                                   /* We've found a EISA secondary   */
	    ISAbases[1]=0;                 /* so there is no sec. ISA cont.  */
	    EISAbases[i]=0;
	    return((long)base);
	  }
	  else EISAbases[i]=0;
	}
    }
  }
  return(0l);                              /* Nothing found  :-(             */
}

long find_ISA(struct eata_register *base, struct get_conf *buf)
{
  int i,l;
  long ret;

  ret=0;
  if(!base){
    for (l=1;l<=MAXISA;l++){
      if(ISAbases[l]){
	i=get_conf_DMA(base,buf);
	if (!i) ISAbases[l]=0;
	else{
	  ret=(int)base;
	  break;
	}
      }
    }
  }else{
    i=get_conf_DMA(base, buf);
    if((i==1)&&((((long)base==0x1F0l)&&!buf->bit2.SECOND)
             ||(((long)base!=0x1F0l)&&buf->bit2.SECOND))) 
      ret=(long)base;
    else ret=0;
  }
  return(ret);
}

int ECS_emulation_sense(long base,int drive)
{
 /*
     I'm not sure what's going wrong here, because now I have 
     documentation of the ECS command set but still I don't 
     get it to work, I don't get a reponse from the 
     controller (Ready + Seek complete...) :-(
 */
  int x,ret;
  struct emul_sense *p;
  char buff[256];
 /* long time;*/

  ret=0;

  while(inb_p(base+HA_RSTATUS)&HA_SBSY);      /* Waiting to get not busy */

  expecting_ints=EXP_NORMAL;
  eata_int_happened=0;

  x=drive<<5;
  outb(base+1,0);
  outb(base+2,0);
  outb(base+3,255);
  outb(base+4,0);
  outb(base+5,0);
  outb(base+HA_WCOMMAND-1,x);
  outb(base+HA_WCOMMAND,ECS_EMULATE_SENSE);

  DBG(DBG_WINTR,printk("ECS: Waiting for interrupt\n"));

/* while(!eata_int_happened); */                /* wait for interrupt  */

  expecting_ints=EXP_NOTHING;
  while(!((x=inb_p(base+HA_RSTATUS))&HA_SDRQ)){
    DBG(DBG_WINTR,printk("Controller status %x\n",x));
    DELAY(50);
  }
  insw(base+HA_RDATA,buff,256);             /* Get the Data and    */
  while(inb(base+HA_RSTATUS)&HA_SDRQ)       /* throw away the rest */
    inb(base+HA_RDATA);
  p=(struct emul_sense *)buff;
  geometry.drv[drive].heads=p->Heads;
  geometry.drv[drive].sectors=p->Sectors;
  geometry.drv[drive].cylinder=(p->Cyls[0]<<8)+p->Cyls[1];
  if(p->lunmap[drive]&0x80)
    geometry.drv[drive].trans=0;
  if(drive==0){
    if(p->lunmap[2]&0x80) ret=0;
    else ret=1;
    geometry.drv[drive].id=p->lunmap[0];
    geometry.drv[drive].lun=p->lunmap[1];
  } else if(drive==1){
    ret=1;
    geometry.drv[drive].id=p->lunmap[2];
    geometry.drv[drive].lun=p->lunmap[3];
  }
  return(ret);
}

void get_geo_trans(long base)
{ 
  outb(0x70,0x12);
  if(inb(0x71)){
    geometry.drv[0].trans=1;
    if(ECS_emulation_sense(base,0)){
      outb(0x70,0x12);
      if(inb(0x71)&0xF){
	geometry.drv[1].trans=1;
	ECS_emulation_sense(base,1);
      }
    }
  }
}

/* int eata_detect(int index) */
int eata_detect(Scsi_Host_Template * tpnt)
{
  struct get_conf gc;
  int i;
  int err;
  long base;
  long time;
  unsigned int irqlist[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

  geometry.drv[0].trans=geometry.drv[1].trans=0; 
  base=0;
  err=0;

  
  for(i=0;i<16;i++)                        /* Here we grab every available IRQ    */
    if(!request_irq(i,eata_int_handler,SA_INTERRUPT,"EATA"))/* until we know which one we  */
      irqlist[i]=1;                        /* will need                   */


  base=find_EISA(-1,&gc);                  /* Check for primary EISA HA */
  if(base) ISAbases[0]=0; /* If found, there can't be an ISA prim. here */
  else base=find_ISA((void *)0x1F0,&gc);   /* Check for primary ISA HA  */
  if (!base) {                             /* no primary found          */
    base=find_EISA(0,&gc);                 /* Check for secondary EISA  */
    if (!base) base=find_ISA((void *)0,&gc); 
    if (!base) {                           /* No controller fould :-(   */
      for(i=0;i<16;i++)                    /* release all interrupts    */ 
	if(irqlist[i]){
	  free_irq(i);
	  irqlist[i]=0;
	}
      return(0);
    }
  }
/*  else get_geo_trans(base); */           /* set emulation parameters  */

  else if(IDE_EMULATION){                  /* Unfortionally,we have to  */
    geometry.drv[0].heads=HEADS0;          /* hardcode the drive geometry */
    geometry.drv[0].sectors=SECTORS0;      /* into the driver for now   */
    geometry.drv[0].cylinder=CYLINDER0;
    geometry.drv[0].id=ID0;
    geometry.drv[1].heads=HEADS1;
    geometry.drv[1].sectors=SECTORS1;
    geometry.drv[1].cylinder=CYLINDER1;
    geometry.drv[1].id=ID1;
  } else {
    geometry.drv[0].id=-1;
    geometry.drv[1].id=-1;
  }

  dma_channel=(8-gc.bit2.DRQX)&7;          /* Which DMA channel ?      */
  if (!gc.bit1.DRQ_valid) {
    DBG(DBG_PROBE,printk("EISA EATA controller found.\n"));
    err=0;
  }else if (request_dma(dma_channel,"DPT_EATA")) {
    printk("Unable to allocate DMA channel %d for EATA controller.\n",
	   dma_channel);
    err=1;
  }

  for(i=0;i<16;i++)                         /* Now we can release all  */
    if(irqlist[i] && (i!=gc.bit2.IRQ)){     /* unused interrupts again */
      free_irq(i);
      irqlist[i]=0;
    }
  if (err) {
    free_irq((unsigned int)gc.bit2.IRQ);
    return(0);
  }
  if(!irqlist[(unsigned int) gc.bit2.IRQ]){
    printk("eata_detect: Couldn't alloc. IRQ%d!\n",(unsigned int)gc.bit2.IRQ);
    return(0);
  }

  tpnt->can_queue=((int)(gc.gco_queuesiz[0])<<8) + ((int)(gc.gco_queuesiz[1]));
  DBG(DBG_PROBE, printk("Can queue %d commands \n",tpnt->can_queue));
  if(tpnt->can_queue>64)                 /* This is only temporarily in here */
    tpnt->can_queue=64;                   /* We'll allocate the needed space */

  tpnt->this_id=(int)(xscsi2int(gc.gco_HAaddress));
  tpnt->sg_tablesize=((short unsigned int)(gc.gco_SGsiz[0])<<8)
                                      +((short unsigned int)(gc.gco_SGsiz[1]));
  tpnt->cmd_per_lun=1;                    
                               
  if (!gc.bit1.DRQ_valid) tpnt->unchecked_isa_dma=0; /* This is an EISA contr.*/
  else tpnt->unchecked_isa_dma=1;                    /* Here we have only ISA */

  snarf_region(base, 9);
  scsi_register(tpnt,0);
  printk("EATA - DMA driver version: %d.%d%s\n",VER_MAJOR,VER_MINOR,VER_SUB);
  printk("EATA compliant controller detected. EATA Level %x, SCSI ID is %d \n",
	        (unsigned int)(gc.gco_version>>4),tpnt->this_id);
  printk("Using IRQ %d, DMA channel %d, at %lx\n",(unsigned int)gc.bit2.IRQ,
	        dma_channel,base); 
  time=jiffies;
  while(jiffies<time+300);                               /* Let 'em read 8-) */
  for(i=0; i<64; i++) outstanding[i].used=0;             /* Do some setup    */
  EATA_base=(void *)base;
  return(1);

}  
  
