/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
 #define INCL_DOSINFOSEG
 #define INCL_NO_SCB
 #define INCL_INITRP_ONLY
 #include "os2.h"                  // \DRV6\H
 #include "dos.h"                  // \DRV6\H
 #include "sas.h"                  // \DRV6\H
 #include "devcmd.h"               // \DRV6\H

 #include "iorb.h"                 // \DRV6\SRC\DEV\DASD\DISKH
 #include "reqpkt.h"               // \DRV6\SRC\DEV\DASD\DISKH
 #include "addcalls.h"             // \DRV6\SRC\DEV\DASD\DISKH
 #include "dskinit.h"

 #include <scsi.h>
 #include <cdbscsi.h>
 #include "cmd.h"
 #include "devhelp.h"              // \DRV6\SRC\DEV\DASD\DISKH
 #include "cdb.h"
 #include "proto.h"
 #include <string.h>
 #include <memory.h>
 #include <rmbase.h>
 #include <rmcalls.h>
 #define max(x,y) (x<=y?y:x)

  BOOL ReadStatusLate=TRUE;

  PUCHAR Read_IOBuffer=NULL;

   UCHAR Mode=Mode1;
   USHORT AdapterBase=0x300;
   USHORT StatusRegister=0x301;
   USHORT DataStatusRegister=0x302;
   USHORT IrqNum=0,IrqMode=NoIrq,OtiChip=Oti12;
   BOOL ReadInProgress=FALSE;
   BOOL ReaderBlocked=FALSE;
   BOOL IrqFired=FALSE;
   BOOL InterruptOccured=FALSE;
   BOOL IrqTried=FALSE;
   ULONG ReadBlockKey=0;
   ULONG Timeouts[32]={0,};
   ULONG Timers[32]={0,};
   ULONG StatusTimeouts=0;
   ULONG Reads[32]={0,};
   UCHAR wDrvVer[4]="";
   BOOL Lu002=FALSE;

   PUCHAR YieldFlag=0;
   PGINFOSEG PGinfo=0;
   #define MaxHoldCPU 3
   extern UCHAR near bInfoFlag;
   USHORT TickCount=0;
   extern USHORT near DrvBlockSize;
   ULONG SpinVar=256;
   SHORT InterruptCount=-1;
   USHORT ReadBlockCount=0;
   PUCHAR ReadBuffer=0;
   USHORT ReadBufferLen=0;
   UCHAR SavePortArray[4];                                           /*@V89549*/

//
//
//      Timer tick routine, used in Polling mode
//
//

VOID _saveregs far TimerTick(void)
{
        if(TickCount)
          {
          TickCount--;
          } /* end if */
}

//
//
//      Read Sector from drive into buffer, note that we will disable on the last byte of the
//      data transfer to close a timing window, but only if in interrupt mode
//
//      There is also a small spin loop at the end of each sector to handle slow hardware
//      this closes an occasional two byte data loss at the beginning of the next sector
//

VOID ReadData1(PUCHAR Buffer,USHORT Len)
{
        if(IrqNum)
          {
          SHORTWAIT;
          } /* end if */
        _asm {
             push   es
             push   di
             mov    dx,DataStatusRegister
             mov    al,ReleaseData
             out    dx,al
             mov    dx,AdapterBase
             cmp    word ptr [bp+8],2048    // if 2352 read, then read all
             jne    skiprep1
             cmp    Mode,Mode2            // if 2048 read and mode 2
             jne    skiprep1              // no
             mov    cx,8                  // yes, skip subheader
        dorep1:
             in     al,dx
             loop   dorep1
        skiprep1:
             cld                                // clear direction flag just in case
             les    di,[bp+4]                   // buffer pointer
             mov    cx,[bp+8]                   // sector length
             rep    insb                        // read in the sector
             mov    dx,DataStatusRegister       // get status regisetr address
             mov    al,SuspendData              // suspend data flow
             out    dx,al                       // do it now
             mov    dx,DataStatusRegister       // get status regisetr address
             mov    al,SuspendData              // suspend data flow
             out    dx,al                       // do it now
             pop    di                          // done
             pop    es                          // get out of here
             };
}

//
//
//
//      Data interrupt handler, used only in Interrupt mode
//      while the code handles nesting properly, this
//      capability is disabled by the disable for the last
//      byte of each sector. The drive apparently gives
//      data ready interrupts when its not
//

VOID _saveregs far IrqHandler(void)
{
USHORT Status;

        DevHelp_EOI(IrqNum);                    // EOI now
        if(++InterruptCount==0)                 // if first level interrupt
          {
          while(InterruptCount>=0)              // while some interrupt nest level
            {
            Enable;                             // enable interrupts
            IrqFired=TRUE;                      // say the interrupt occured
            InterruptOccured=TRUE;              // a different one
            Status=ReadStatus();                // get the drive status
            DevHelp_RAS( 169 ,(ReadBlockCount<<8)|Status , sizeof(PGinfo->msecs),&PGinfo->msecs);
            if(ReadInProgress)                  // ONLY if there is a read in progress
              {
              if(Status == DTEN)                // If Data Available
                {
                if(ReadBlockCount)              // any data still requested to read?
                  {
                  ReadData1(ReadBuffer,ReadBufferLen);
                  ReadBuffer+=ReadBufferLen;        // count size of xfer
                  if(--ReadBlockCount==0)           // was this the last requested
                    {                               // data block?
                    ReadInProgress=FALSE;           // turn off flag
                    if(ReaderBlocked)               // application waiting?
                      {
                      DevHelp_Run(ReadBlockKey);    // unblock it
                      } /* end if */
                    } /* end if */
                  } /* end else */
                } /* end if */
              else if (Status == STEN)              // status change?
                {
                if(ReaderBlocked)                   // application waiting?
                  {
                  ReadInProgress=FALSE;             // turn off flag
                  DevHelp_Run(ReadBlockKey);        // unblock it
                  } /* end if */
                }
              } /* end if */
            else                                    // no read in progress, so how did we get here?
              {
              if (Status == STEN)                   // status change?
                {
                if(ReaderBlocked)                   // application waiting?
                  {
                  ReadInProgress=FALSE;             // turn off flag
                  DevHelp_Run(ReadBlockKey);        // unblock it
                  } /* end if */
                } /* end if */
              } /* end else */
            Disable;                                // disable interrupts
            --InterruptCount;                       // decrement the nest count
            } /* end while */
          } /* end if */
        _asm  stc ;                                 // say we processed interrupt
}

//
//      Get system running millisecond clock, watch out for lower word rollover
//

ULONG GetClock(void)                                      // get ms clock value
{
   ULONG timer=0;                                         // initial return value is 0

   if(PGinfo)                                             // not set at init time
     {                                                    // only do this at init time
     timer=PGinfo->msecs;                                 // get timer value
     if (HIUSHORT(timer)!=HIUSHORT(PGinfo->msecs))        // if low word forced carry
       timer=PGinfo->msecs;                               // get it again
     }

   return timer;
}

void ScaleClock(UCHAR CpuType)
{
ULONG temp,temp1;
ULONG Counter=0;
UCHAR saveal,saveah;

      temp=PGinfo->msecs;                       // get current clock value
      while(PGinfo->msecs==temp);               // spin til end of tick, to get alligned
      temp=PGinfo->msecs;                       // save current tick setting
      while(PGinfo->msecs==temp)                // spin til clock ticks again
        Counter++;                              // and count while spinning
      SpinVar=Counter/170;
      DevHelp_RAS( 195 ,0 , sizeof(SpinVar), &SpinVar);

}

//
//  check to see if we can get the drive status from user specified address
//

BOOL ChkBaseAddx()
{
  BOOL Flag=FALSE;                      // default is we can't find adapter
  USHORT r;
  CDROMSTAT s;

  r=DriveStatus(SearchCount);           // try to get drive status
  if(!TIMEOUT(r))                       // if it didn't timeout
    {
    memset(wDrvVer,0,sizeof(wDrvVer));
    r=DriveCommand(GetVersion,0,0,&s,sizeof(s.Version));
    if(!TIMEOUT(r))
      {
      switch(s.Version.Code)
        {
        case 'M':
        case 'F':
        case 'D':
          wDrvVer[0]=s.Version.Code;
          wDrvVer[1]=BCD2Bin(s.Version.VerMSN)+48;
          wDrvVer[2]=BCD2Bin(s.Version.VerLSN)+48;
          Flag=TRUE;                            // adapter and drive are found
          if(!memcmp(wDrvVer,"M02",3))          // if M02 this is an LU002s drive
            {
            Lu002=TRUE;                         // say this is an LU002 drive, used in WriteCmd
//            Flag=FALSE;                         // we dont support LU002
            } /* end if */
          break;
        default:
          break;
        } /* end switch */
      } /* end if */
    } /* end if */
  return Flag;
}

BOOL PortValid(void)
{
   UCHAR i;
   BOOL  rc = FALSE;
   for ( i = 0; i < sizeof(SavePortArray); i++ )
   {
      if ( SavePortArray[i] != 0xff )
      {
         rc = TRUE;
      }
   }
   return(rc);
}

//
//      Try to use Specified IRQ, but just once, returns true if it worked, false
//      otherwise, return code not currently used, special global IrqFired is set
//      by the interrupt handler to communicate back to this routine
//
//      This is only done on mount of a data CD, as audio never supports interrupts
//

BOOL TryIrq(BOOL Testing)
{
BOOL Flag=FALSE;
PSZ p;
HRESOURCE IrqResource;
RESOURCESTRUCT Resource;
extern HDRIVER hDriver;
extern HADAPTER hAdapter;

        if(IrqTried==FALSE)                             // if we haven't tried this already
          {
          IrqTried=TRUE;                                // say we have, one time only
          if(IrqNum)                                    // are we trying to get into interrupt mode?
            {
            Resource.ResourceType            = RS_TYPE_IRQ;
            Resource.IRQResource.IRQLevel    = IrqNum;
            Resource.IRQResource.PCIIrqPin   = RS_PCI_INT_NONE;
            Resource.IRQResource.IRQFlags    = RS_IRQ_EXCLUSIVE;
            if(!RMAllocResource( hDriver, &IrqResource, &Resource ))
              {
              p=(PSZ)IrqHandler;
              if(!DevHelp_SetIRQ(OFFSETOF(p),IrqNum,FALSE))               // can we hook the specified irq?
                {
                SetIrqMode((UCHAR)(IrqMode|OtiChip));                     // yes, set chip in interrupt mode
                while(TIMEOUT(DriveCommand( DriveConfig,IRQFLAG ,PreIrq|ErrIrq, 0, 0)))
                   DUMMYWAIT;
                SpecialRead();                                            // issue the special read
                if(IrqFired==FALSE)                                       // if the interrupt handler didn't run
                  {                                                       // irq can't be working
                  IrqNum=0;                                               // clear the flag
                  DevHelp_UnsetIRQ(IrqNum);                               // unset the irq num
                  SetIrqMode((UCHAR)(NoIrq|OtiChip));                     // reset drive to non-interrupt mode
                  while(TIMEOUT(DriveCommand( DriveConfig,IRQFLAG ,0, 0, 0)))
                     DUMMYWAIT;
                  RMDeallocResource(hDriver, IrqResource);
                  } /* end if */
                else                                                      // interrupt handler ran
                  {
                  Flag=TRUE;                                              // set flag to tell caller
                  RMModifyResources(hDriver, hAdapter,RM_MODIFY_ADD,IrqResource);
                  }
                }
              else
                {
                IrqNum=0;                                                 // clear the flag
                RMDeallocResource(hDriver, IrqResource);
                }
              }
            } /* end if */
          } /* end if */
  return Flag;
}

void ResetDrive(void)
{

        while(TIMEOUT(DriveCommand(Reset,0,0,0,0)))
          DUMMYWAIT;
        SetDataMode(SuspendData);
        DUMMYWAIT;
        while(TIMEOUT(DriveCommand( DriveConfig,MODEFLAG ,0, 0, 0)))
          DUMMYWAIT;
        if(DrvBlockSize==2352)
          {
          DriveCommand( SetDriveMode,DataLength|EccBit|MuteData ,0, 0, 0);
          } /* end if */
        else
          {
          DriveCommand( SetDriveMode,Mode==Mode2?TestMode|MuteData:MuteData ,0, 0, 0);
          } /* end else */
        while(TIMEOUT(DriveCommand( DriveConfig,BLOCKSIZE ,DrvBlockSize, 0, 0)))
          DUMMYWAIT;
        while(TIMEOUT(DriveCommand( DriveConfig,IRQFLAG ,IrqNum?PreIrq|ErrIrq:0, 0, 0)))
           DUMMYWAIT;
        if (wDrvVer[0]=='D')
          {
          while(TIMEOUT(DriveCommand( DriveConfig,RETRYCOUNT ,3, 0, 0)))
            DUMMYWAIT;
          }
        SpecialRead();
}

BOOL ChkDrive()
{
  BOOL Flag=FALSE;
  USHORT t;
  CDROMSTAT s;
  PSZ p;

#if defined(DEBUG) | defined(TEST)
#else
    p=(PSZ)TimerTick;
    DevHelp_Settimer(OFFSETOF(p));
    DevHelp_AllocGdtSel(&FP_SEG(Read_IOBuffer) ,1);
#endif
    while(TIMEOUT(DriveCommand(Reset,0,0,0,0)))
      DUMMYWAIT;
    OtiChip = (wDrvVer[2]&1)?Oti12:0;
    if(!strcmp(wDrvVer,"M05"))
      {
      OtiChip=0;
      } /* end if */
    else if(!strcmp(wDrvVer,"M06"))
      {
      OtiChip=Oti12;
      } /* end else */
    SetDataMode(SuspendData);
    DUMMYWAIT;
    while(TIMEOUT(DriveCommand( DriveConfig,MODEFLAG ,0, 0, 0)))
      DUMMYWAIT;
    while(TIMEOUT(DriveCommand( SetDriveMode,TestMode|MuteData ,0, 0, 0)))
      DUMMYWAIT;
    while(TIMEOUT(DriveCommand( DriveConfig,BLOCKSIZE ,2048, 0, 0)))
      DUMMYWAIT;
    Flag=TRUE;
    return Flag;
}

UCHAR Bin2BCD( UCHAR bBin )
{
      UCHAR bBCD ;

   bBCD = (UCHAR)( (((bBin/10)<<4)&0xf0) | ((bBin%10)&0x0f) ) ;

   return( bBCD ) ;
}

UCHAR BCD2Bin( UCHAR bBin )
{
      UCHAR bBCD ;

   bBCD = (UCHAR)( ((bBin>>4)*10) + (bBin&0x0f)) ;

   return( bBCD ) ;
}

ULONG MayYield(ULONG LastTime)                  // yield if we've held CPU for MaxHoldCPU ms
{
ULONG Now;

   Now=GetClock();                              // get current ms clock
   if(LastTime)                                 // if last time was specified (not on first time)
     {
     if ((Now>=LastTime+MaxHoldCPU))            // held CPU long enough?
       {                                        //
       if (YieldFlag && *YieldFlag)             // pointer not set at init time, need to yield?
         DevHelp_Yield();                       // yes, yield now
       LastTime=GetClock();                     // get clock after yield
       }
     else if(Now<65536L && LastTime>65537L)     // watch out for clock rollover
       LastTime=GetClock();                     // get a new clock value cause of rollover
     }
   else                                         // no last time specified
     LastTime=GetClock();                       // get first clock value

   return LastTime;                             // return clock value to caller for next cycle
}

USHORT ReadDataBytes(PUCHAR CmdPtr, USHORT Len, PULONG Count,ULONG tries,BOOL PostStatus)
{
ULONG Clock=0L,i,x,SaveCount;
UCHAR Status=0,b;
USHORT Timeout=0,l,t;
PUCHAR pVirtAddx;
BOOL FirstRecord=TRUE;

        SaveCount=*Count;               // save incoming block count
        if(IrqNum)                      // handle status in interrupt mode
          {
          t=ReadBlockCount;             // get residual block count, always 0?
          b=ReadStatus();               // get current status,
                                        // sb !STEN in late mode,
                                        // STEN| DTEN in Early, but Early has a bug

          if(b == STEN)                 // is it status that dropped?
            {
            if(ReadStatusLate==FALSE)   // oops, shouldn't get this unless
              {                         // read failed for some reason
              Status=ReadError;         // say read error so Drivecommand can get
              } /* end if */            // status on our return
            }
          } /* end if */
        else
          {
          DPRINTF(("Read Length=%d Count=%d prestatus=%02X\n",Len,Count,ReadStatus()));

          Clock=GetClock();                         // get starting clock tick count

          for(l=1;*Count;l++)                       // loop til done
            {
#ifdef TRACE
            t=(USHORT)*Count;
            DevHelp_RAS( 168 ,((t<<8) | 0) , sizeof(PGinfo->msecs),&PGinfo->msecs);
#endif

            DPRINTF(("Waiting for !DTEN or !STEN\n"));
            Disable;
            // watch out for potential yield being gone MAXWAIT seconds
            // (maxwait seconds * 1000) + 2 ticks worth / (msec/tick) = ticks
            TickCount=((PGinfo->csecMaxWait*1000)+62)/31;
            Enable;
            x=tries;
            while(TickCount)                       // while some ticks left
              {
              b=ReadStatus();                      // get the current status
              if(b)                                // if either status changed status
                {
                break;
                } /* end if */
              // only yield if first record
              // and in polled mode
              if(IrqNum==0 && FirstRecord)
                {
                Clock=MayYield(Clock);          // yield if possible
                FirstRecord=FALSE;              // we have at least one record
                } /* end if */
              for(i=SpinVar; i--; ){};          // don't spin too fast

              }

            DPRINTF(("Wait over status = %02X\n",b));

            if(b==DTEN)                         // is it Data status that dropped?
              {                                 // yes Data is available
              DPRINTF(("Reading data into pre %p Buffer=%d %02X\n",CmdPtr,l,b));
              DPRINTF(("Reading data into post %p\n",pVirtAddx));
              ReadData1(CmdPtr,Len);            // transfer one sector
#ifdef TRACE
              b=ReadStatus();
              t=*Count;
              DevHelp_RAS( 190 ,((t<<8) | b ) , sizeof(PGinfo->msecs), &PGinfo->msecs);
#endif
              DPRINTF(("Reading data into post rep %p %02X\n",CmdPtr,b));
              CmdPtr+=Len;                      // adjust the buffer pointer
              (*Count)--;                       // reduce coutn of sectors to read
              } /* end if */
            else if(b == STEN)                  // is it Data status that dropped?
              {
              if(ReadStatusLate==FALSE)         // oops, shouldn't get this unless
                {                               // read failed for some reason
                Status=ReadError;               // say read error so Drivecommand can get
                } /* end if */                  // status on our return
              else
                {
                if(*Count)
                  {
//                  Status=CommandCheck;          // command failed
                  Status=ReadError;             // command failed
//                  Timeout=TimeOut<<8;           // must have timed out waiting
                  } /* end if */
                }
              break;
              }
            else
              {
              StatusTimeouts++;
              Timeout=TimeOut<<8;               // must have timed out waiting
              break;
              }
            } /* end while */
#ifdef TRACE
          t=*Count;
#endif
          } /* end else */
        DevHelp_RAS( 168 ,((t<<8) | Status) , sizeof(PGinfo->msecs),&PGinfo->msecs);
        return Timeout|Status;                  // return read status to caller
}

UCHAR WriteData(UCHAR Offset, UCHAR Data)
{
USHORT Addr=AdapterBase+Offset;
   _asm {
        mov DX,Addr
        mov AL,Data
        Out Dx,Al
        }
}

UCHAR ReadData(void)
{
   _asm {
        mov DX,AdapterBase
        In al,DX
        mov ah,0
        }
}

void WriteCmd(PUCHAR CmdPtr, USHORT Len)
{
#ifdef DEBUG
USHORT i;
#endif
USHORT j;
        DPRINTF(("Write Length=%d\n",Len));
        DPRINTF(("Writing data %04X\n",AdapterBase+DataReg));
        _asm { pushf };                 // save callers flags
#ifdef DEBUG
        for(i=0; i<Len;i++ )
          {
          DPRINTF(("%02X ",CmdPtr[i]));
          } /* end for */
        DPRINTF(("\n"));
#endif
        if(Lu002)                       // if this is an LU002 drive
          {
          if((j=ReadStatus()) & DTEN)       // if DTEN is on
            {
            DevHelp_RAS( 170 ,j , sizeof(PGinfo->msecs),&PGinfo->msecs);
            Enable;                     // make sure to enable interrupts here
            TickCount=3;                // three clock tick timeout (96 ms max)
            while(TickCount)            // spin
              {
              if(!((j=ReadStatus()) & DTEN)) // if DTEN is off
                {
                DevHelp_RAS( 171 ,j , sizeof(PGinfo->msecs),&PGinfo->msecs);
                break;                  // break loop
                } /* end if */
              } /* end while */
            DevHelp_RAS( 172 ,j , sizeof(PGinfo->msecs),&PGinfo->msecs);
            } /* end if */
          } /* end if */
        Disable;                        // disable interrupts
        for(;Len--;CmdPtr++)            // send the command bytes
          {
          WriteData(DataReg,*CmdPtr);   // one at a time
          } /* end while */
        _asm { popf };                  // restore callers flags
}

USHORT ReadStatus(void)
{
  _asm {
        pushf                           // save callers state
        cli                             // disable
        mov     dx,StatusRegister       // get the register address
        in      al,dx                   // get status bits
        xor     al,255                  // flip them
        and     al, DTEN | STEN         // just these two
        mov     ah,0                    // make a word
        popf                            // restore callers flags
        };

}

USHORT ReadStatusBytes(PUCHAR CmdPtr, UCHAR Len,ULONG tries)
{
ULONG Clock=0L,i,x;
UCHAR Status=0,b;
USHORT Timeout=0;

        DPRINTF(("Read Length=%d\n",Len));

        for(;Len--;CmdPtr++)                            // get status command returned data
          {
          for(x=tries;x ; x-- )                         // spin loop
            {
            b=ReadStatus();                             // read status
            if(b==STEN)                                 // a status byte ready?
              {
              break;
              } /* end if */
            for(i=SpinVar; i--; );                      // don't spin too fast if status not ready
            }

          if(x)                                         // if status ready before loop expires
            {
            *CmdPtr=ReadData();                         // get the status byte now
            } /* end if */
          else                                          // oops timed out
            {
            StatusTimeouts++;
            Timeout=TimeOut<<8;                         // set return code
            Status=CommandCheck;                        // and command check state
            break;
            } /* end else */
          } /* end while */
        return Timeout|Status;                          // return to caller
}

//
//      Get driver status on demand
//

USHORT DriveStatus(ULONG tries)
{
ULONG i;
USHORT Timeout=0;
UCHAR data;

        DPRINTF(("Requesting status\n"));

        WriteData(DataReg,RequestDriveStatus);          // send status command to drive

        return GetStatus(tries);                        // get status and return it

}

//
//      Get the drive status now, this routine is used after each command that returns status (all)
//

USHORT GetStatus(ULONG tries)
{
ULONG i;
USHORT Timeout=0;
UCHAR data=0;

        DPRINTF(("Requesting status\n"));

        for(;tries ;tries-- )                           // spin loop
          {
          if(ReadStatus() == STEN)                      // if status ready
            {
            break;                                      // break loop
            } /* end if */
          for(i=SpinVar; i--; );                        // don't spin too fast if status not ready
          }
        if(tries)                                       // if we didn't timeout
          {
          data=ReadData();                              // get the status byte now, only one available
          DPRINTF(("Returning status %02X\n",data));
          if(!COMMANDCHECK(data))
            {
            if(DISCCHANGE(data))                          // did the disc change states?
              {
              bInfoFlag &=  CLR_VS;                       // clear the toc cached flag, this state is only available
              } /* end if */                              // during a SINGLE IN from the status port
            } /* end if */
//        if(data!=0xff && data!=0x7f && DISCCHANGE(data)) // did the disc change states?
//        if(data!=0xff && DISCCHANGE(data))            // did the disc change states?
          } /* end if */
        else                                            // oops, timed out
          {
          DPRINTF(("Returning timeout\n"));
          StatusTimeouts++;
          Timeout=TimeOut<<8;                           // set timeout flag
          data=CommandCheck;                            // and command check just in case
          } /* end else */
        DPRINTF(("Status returning\n"));
        return Timeout | data;                          // return to caller
}


//
//      Command processor
//
//      Note: some commands are fake to differentiate from others
//            Seek, Read, & play are the same to the drive, but we have
//            three different ones to handle them differently
//

USHORT DriveCommand(UCHAR OpCode, ULONG Addr1, ULONG Addr2, PCDROMSTAT Buffer, USHORT BufferLen)
{
union  OutputCommands c;
USHORT len=1,rc=0,x;
USHORT Status,ReadStatusbyte;
UCHAR OldOpCode;
ULONG tries=Spincount;
ULONG ReadStart,ReadStop;

        memset(&c,0,sizeof(c));                         // clear the command packet

        switch(OldOpCode=OpCode)                        // process based on the pseudo opcode
          {
          case ReadUPC:

            DriveCommand(DriveConfig,UPCFLAG,GetUpc,0,0);
            Status=DriveCommand(ReadSubQ,NULL,NULL,Buffer,BufferLen);
            DriveCommand(DriveConfig,UPCFLAG,GetSubq,0,0);
            DriveCommand(Stop,0,0,0,0);
            return Status;
            break;

          case ReadSubQ:

            OpCode=ReadSubQ;
            tries=ReadSpinCount;
            break;

          case Stop:

            tries=ReadSpinCount;
            break;

          case LockUnlock:

            c.LockDrawer.Option=LOBYTE(LOUSHORT(Addr1));
            len=sizeof(c.LockDrawer);
            break;

          case Seek:

            DPRINTF(("Seeking to %d %d %d size=%d\n",
               LOBYTE(HIUSHORT(Addr1)),
               HIBYTE(LOUSHORT(Addr1)),
               LOBYTE(LOUSHORT(Addr1)),
               len=sizeof(c.SeekCmd)));
            c.SeekCmd.Min=Bin2BCD(LOBYTE(HIUSHORT(Addr1)));
            c.SeekCmd.Sec=Bin2BCD(HIBYTE(LOUSHORT(Addr1)));
            c.SeekCmd.Frame=Bin2BCD(LOBYTE(LOUSHORT(Addr1)));
            tries=ReadSpinCount;
            len=sizeof(c.SeekCmd);
            break;

          case Read:

            DPRINTF(("Reading at %d %d %d size=%d for %ld blocks\n",
               LOBYTE(HIUSHORT(Addr1)),
               HIBYTE(LOUSHORT(Addr1)),
               LOBYTE(LOUSHORT(Addr1)),
               len=sizeof(c.SeekCmd),
               Addr2));
            DevHelp_RAS( 168 ,1, sizeof(PGinfo->msecs),&PGinfo->msecs);
            c.ReadCmd.Min=Bin2BCD(LOBYTE(HIUSHORT(Addr1)));
            c.ReadCmd.Sec=Bin2BCD(HIBYTE(LOUSHORT(Addr1)));
            c.ReadCmd.Frame=Bin2BCD(LOBYTE(LOUSHORT(Addr1)));
            c.ReadCmd.Length1=LOBYTE(HIUSHORT(Addr2)) | ReadStatusLate? 0 : 0xF0; // read status early/late flag
            c.ReadCmd.Length2=HIBYTE(LOUSHORT(Addr2));
            c.ReadCmd.Length3=LOBYTE(LOUSHORT(Addr2));
            len=sizeof(c.ReadCmd);
            if(wDrvVer[0]=='D')                 // if double speed drive
              {
              OpCode=ReadFast;                  // switch to fast read command
              } /* end if */
            else
              {
              OpCode=Seek;
              } /* end else */
            tries=ReadSpinCount;
            Disable;                            // disable interrupts
            ReadStart=GetClock();
            InterruptOccured=FALSE;
            ReadBlockCount=LOUSHORT(Addr2);
            ReadBuffer=(PUCHAR)Buffer;
            ReadBufferLen=BufferLen;
            ReadInProgress=TRUE;
            break;

          case PlayAudioMSF:

            DPRINTF(("Reading from $d %d %d size=%d\n",
               LOBYTE(HIUSHORT(Addr1)),
               HIBYTE(LOUSHORT(Addr1)),
               LOBYTE(LOUSHORT(Addr1)),
               len=sizeof(c.SeekCmd)));
            c.PlayAudio.StartMin=Bin2BCD(LOBYTE(HIUSHORT(Addr1)));
            c.PlayAudio.StartSec=Bin2BCD(HIBYTE(LOUSHORT(Addr1)));
            c.PlayAudio.StartFrame=Bin2BCD(LOBYTE(LOUSHORT(Addr1)));
            c.PlayAudio.StopFrame =Bin2BCD(LOBYTE(HIUSHORT(Addr2)));
            c.PlayAudio.StopMin   =Bin2BCD(HIBYTE(LOUSHORT(Addr2)));
            c.PlayAudio.StopSec   =Bin2BCD(LOBYTE(LOUSHORT(Addr2)));
            tries=ReadSpinCount;
            len=sizeof(c.ReadCmd);
            OpCode=Seek;
            break;

          case DriveConfig:

            DPRINTF(("Mode =%02x size=%d a1=%08LX a2=%08Lx\n",LOBYTE(LOUSHORT(Addr1)), sizeof(c.DriveConfigCmd), Addr1, Addr2));
            c.DriveConfigCmd.Byte1.Byte= LOBYTE(LOUSHORT(Addr1));
            if(c.DriveConfigCmd.Byte1.Bits.ByteLength)
              {
              DPRINTF(("Blocksize mode set\n"));
              len=sizeof(c.DriveConfigCmd);                     // all the data bytes
              if(Addr2<2352)            // AND its not read-raw
                {
                if(Mode==Mode2)             // if a mode 2 disc
                  {
                  Addr2+=8;                 // add 8 for the subheader size
                  } /* end if */
                else
                  {
//                Addr2+=2;     we don't do this anymore, see bit 7 & 6 in Drive mode settings  // mode 1, add two dummy bytes
                  }
                }
              Addr2--;
              c.DriveConfigCmd.Byte2= HIBYTE(LOUSHORT(Addr2));
              c.DriveConfigCmd.Byte3= LOBYTE(LOUSHORT(Addr2));
              } /* end if */
            else
              {
              DPRINTF(("other mode set\n"));
              c.DriveConfigCmd.Byte2= LOBYTE(LOUSHORT(Addr2));
              len=sizeof(c.DriveConfigCmd)-sizeof(UCHAR);       // one data byte
              }
            break;

          case SetDriveMode:

            c.DriveMode.Mode=LOBYTE(LOUSHORT(Addr1));
            DPRINTF(("Mode=%02X\n",LOBYTE(LOUSHORT(Addr1))));
            len=sizeof(c.DriveMode);
            break;

          case HoldTime:

            c.HoldCmd.UnitsofTenSeconds=LOBYTE(LOUSHORT(Addr1));
            len=sizeof(c.HoldCmd);
            break;

          case SetAudioVolume:

            c.AudioLevel.ATT0=LOBYTE(LOUSHORT(Addr1));
            c.AudioLevel.ATT1=HIBYTE(LOUSHORT(Addr1));
            c.AudioLevel.ATT2=LOBYTE(LOUSHORT(Addr2));
            c.AudioLevel.ATT3=HIBYTE(LOUSHORT(Addr2));
            len=sizeof(c.AudioLevel);
            break;

          case ModeSet:

            c.DataModeSet.ModeType=LOBYTE(LOUSHORT(Addr1));
            len=sizeof(c.DataModeSet);
            Mode=c.DataModeSet.ModeType;
            break;

          case Eject:
          case Close:

            tries=ReadSpinCount*2;
            break;

          default:
            break;
          } /* end switch */

        c.Command=OpCode;                                       // set the REAL opecode here

        WriteCmd((PUCHAR)&c,len);                               // send the command to the drive

        if(OldOpCode!=Read)                                     // if not read then get command status
          {
          Status=GetStatus(tries);                              // now
          } /* end if */
        else                                                    // read comnand handled different ways
          {
          DPRINTF(("ReadStatus Late =%s\n",ReadStatusLate? "Yes": "No"));
          Enable;                                               // we disabled before command issued
          if(ReadStatusLate==FALSE)                             // are we supposed to read status before data appears?
            {
            while(TIMEOUT(Status=GetStatus(tries)));            // get it now, timeouts not allowed, could take LONG time
                                                                // if a seek and spinup are involved, keep trying
            if(READERROR(Status) || COMMANDCHECK(Status) )      // did the command fail for some reason?
              {
              rc=3;                                             // say we had a failure, won't block later
              ReadInProgress=FALSE;                             // and kill the read
              } /* end if */
            DevHelp_RAS( 168 ,(LOBYTE(LOUSHORT(Addr2))<<8)|Status , sizeof(PGinfo->msecs),&PGinfo->msecs);
            } /* end if */
          else                                                  // read status after data
            {
            Status=0;                                           // clear the status so next phase will work
            }
          }
//
//      Phase 2 of command processing,
//
//      if the command didn't fail or timeout, then handle the input data for
//      each command that supports it
//

        if((!TIMEOUT(Status) && !COMMANDCHECK(Status) && !READERROR(Status)) || OldOpCode==RequestSense)
          {

          switch(OldOpCode)                                     // on the pseudo opcode again
            {

            case PlayAudioMSF:                                  // play

                DevHelp_RAS( 168 ,3, sizeof(PGinfo->msecs),&PGinfo->msecs);
                Status &= 0xFA;                                 // turn off noise bits
                break;

            case GetVersion:
            case GetAudioVolume:
            case ReadDriveMode:
            case RequestSense:

                  ReadStatusBytes((PUCHAR)Buffer,(UCHAR)BufferLen,tries);
                  break;

            case LockUnlock:

                  if(c.LockDrawer.Option==Query)                // only if we're querying the state
                    {
                    ReadStatusBytes((PUCHAR)Buffer,(UCHAR)BufferLen,tries);
                    } /* end if */
                break;

            case ReadToc:
            case ReadSubQ:
            case ReadUPC:
            case ReadDiscInfo:

                if(DISCIN(Status))                              // if no disc we can't answer these
                  {
                  ReadStatusBytes((PUCHAR)Buffer,(UCHAR)BufferLen,tries);
                  }
                break;

            case Read:                                          // read data

                    DevHelp_RAS( 168 ,2, sizeof(PGinfo->msecs),&PGinfo->msecs);

                    if(IrqNum)                                  // in interrupt mode?
                      {
                      Disable;                                  // disable
                      if(ReadInProgress)                        // a read still in progress
                        {
                        ReaderBlocked=TRUE;                     // say we're blocked
                        ReadBlockKey=(ULONG)Buffer;             // our block key, PHYSICAL address
                        rc=DevHelp_Block(ReadBlockKey,3000L,1); // block
                        Disable;                                // on return from block we're enabled
                        ReaderBlocked=FALSE;                    // say we're not blocked (we aren't)
                        ReadBlockKey=0;                         // clear the block key value
                        } /* end if */
                      ReadInProgress=FALSE;                     // turn off read in progress
                      Enable;                                   // we can enable again
                      ReadStop=GetClock();                      // get current clock value
                      if(Addr2<32)                              // was this a <32 sector read?
                        {
                        Reads[Addr2]++;                         // count reads of this sector size
                        if(!rc)                                 // did the read timeout?
                          {
                          Timers[Addr2]=max(Timers[Addr2],ReadStop-ReadStart); // no set max wait time for this size
                          } /* end if */
                        else
                          {
                          Timeouts[Addr2]++;                    // oops, timed out, count it for this size read
                          Timeouts[0]=AdrRed2Hsg(Addr1);        // sector addr of read request (debugging)
                          } /* end else */
                        } /* end if */
                      } /* end if */

                    DevHelp_RAS( 168 ,(rc<<8) +3, sizeof(PGinfo->msecs),&PGinfo->msecs);

                    // if in polling mode
                    // OR
                    // in interrupt mode AND we we're unblocked, or an interrupt has occured

                    if(IrqNum==0 || (IrqNum && (rc==0 || InterruptOccured)) )
                      {
                      // get the data (polled mode) or
                      // just status (interrupt, post status mode)
                      // use the GLOBAL GDT virtual pointer for data buffer address, NOT the
                      // PHYSICAL address passed

                      ReadStatusbyte=ReadDataBytes((PUCHAR)Buffer,BufferLen, &Addr2,tries*2,ReadStatusLate);

                      // did the read timeout (polled mode)

                      if(!TIMEOUT(ReadStatusbyte))      // if no timeout
                        {
                        if(ReadStatusLate)              // are we supposed to read the status now?
                          {
                          Status=GetStatus(tries);      // yes, get the read status
                          } /* end if */
                        else
                          {
                          if(READERROR(ReadStatusbyte)) // oops, Read pre-status lied
                            {
                            Status=GetStatus(tries);    // get REAL status now
                            } /* end if */
                          } /* end else */
                        } /* end if */
                      else
                        {
                        Status=ReadStatusbyte;          // set the timeout return code
                        } /* end else */

                      DevHelp_RAS( 168 ,((LOUSHORT(Addr2)<<8) | Status) , sizeof(PGinfo->msecs),&PGinfo->msecs);

                      } /* end if */
                    else
                      {

                      // Must be in interrupt mode, but check just in case

                      if(IrqNum)
                        {
//#ifdef TRACE
                        while((x=ReadStatus())==DTEN)
                          {
                          ReadData();
                          } /* end if */
                        DevHelp_RAS( 168 ,(x<<8) +4, sizeof(PGinfo->msecs),&PGinfo->msecs);
                        if(x == STEN)
                          {
                          Status=GetStatus(tries);
                          if(ReadBlockCount)            // not all sectors read
                            {
                            Status=CommandCheck;        // command failed
                            } /* end if */
                          DevHelp_RAS( 168 ,Status, sizeof(PGinfo->msecs),&PGinfo->msecs);
                          } /* end if */
                        else
//#endif
                          Status= CommandCheck;         // we either timed out, or had a command failure
                                                        // can't tell which, say command failure (worst case)
                        } /* end if */
                      } /* end else */
                break;
            default:
                break;
            } /* end switch */
          } /* end if */
        return Status;
}

void SetDataMode(UCHAR data)
{
USHORT addr=AdapterBase+2;
   DPRINTF(("Data Mode set %02X\n",data));
   _asm {
          mov dx,addr
          mov al,data
          out dx,al
          };
}

void SetIrqMode(UCHAR mode)
{
USHORT addr=AdapterBase+3;
   DPRINTF(("Irq Mode set %02X\n",mode));
   _asm {
          mov dx,addr
          mov al,mode
          out dx,al
          };
}
// -------------------------------------------------------------- //
//                                                                  //
// This routine will change an HSG address to Red book address          //
//                                                                  //
//                                                                  //

ULONG AdrHsg2Red( ULONG ulHsg )
{
      ULONG ulRedBook ;

   ulHsg    += 150 ;

   ulRedBook = MAKEULONG
               (
                  MAKEUSHORT
                  (
                     (UCHAR)(ulHsg%75),
                     (UCHAR)((ulHsg%4500)/75)
                  ),
                  (UCHAR)(ulHsg/4500)
               ) ;

   return( ulRedBook ) ;
}
ULONG AdrRed2Hsg( ULONG ulRedBook )
{
      ULONG ulHsg ;

   ulHsg = ((((ULONG)(LOBYTE(HIUSHORT(ulRedBook)))*60)+
              (ULONG)(HIBYTE(LOUSHORT(ulRedBook)))     )*75)+
              (ULONG)(LOBYTE(LOUSHORT(ulRedBook)))          - 150 ;

   return( ulHsg ) ;
}

