//----------------------------------------------------------------------------
// IDE                                                     19960417 CHG
// Small and simple interface for an "standard" IDE Harddisk connected
// to a 8051 cpu, in this case a DS5000, but all other 8051 compatible
// cpu could be used, as long as they have some external RAM, 1KByte or so
//
// This code is provided as is, with NO responsibility by the author !
// ie. this is use on your own risk !!!!!
//
// Code is free to use, the only request i have is, that if you do some
// enhancements, discoveries I would appreciate to get a copy/info on what
// YOU have done !
// That way, we can all help each other with code etc etc etc.
//
// Made by 
//   Carsten Groen, OZ9AAR
//   Kaervaenget 46, Gl. Sole
//   DK-8722 Hedensted
//   Denmark
//   email: cgroen@image.dk
//
//----------------------------------------------------------------------------
#include <reg5000.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

//----------------------------------------------------------------------------
// Constants...
//----------------------------------------------------------------------------
#define TRUE     1 
#define FALSE    0 
#define CTRL     0
#define CMD      1
#define DRIVE0   0

//----------------------------------------------------------------------------
// Types
//----------------------------------------------------------------------------
typedef struct {
  unsigned char Heads; 
  unsigned int Tracks;
  unsigned int SectorsPerTrack;
  char Model[41];
} tdefDriveInfo;
 
//----------------------------------------------------------------------------
// I/O definitions...
// The IDE harddisk is just connected DIRECTLY to these I/O ports on the 8051
// See pin numbers on IDC connecter on IDE drive in one of the attached files.
// REMEMBER to make a pullup on the P0 port (ex 4.7 K to vcc from each of the 
// 8 P0 pins
//----------------------------------------------------------------------------
#define nCS1FX  P00
#define nCS3FX  P01 
#define DA0     P02
#define DA1     P03
#define DA2     P04
#define nDASP   P05
#define LSBDATA P1   
#define MSBDATA P2
#define nDIOR   P06
#define nDIOW   P07
#define INTRQ   P32
#define IORDY   P33 
#define RESET   P34
 
#define ALLINPUT 0xFF
 
//----------------------------------------------------------------------------
// Variables...
//----------------------------------------------------------------------------
unsigned int Timer10mSec=0;    // General timer, tick is 10 mSec
unsigned char SectorBuffer[512];
 
 
//----------------------------------------------------------------------------
// Wait for a specific time in 10 mSec
// Based on a 11.0592 Mhz crystal
//----------------------------------------------------------------------------
void Delay(unsigned char t) {
  unsigned int i;
  if (t==0) return;
  while (t--) for(i=0;i<774; i++);
}
 
//----------------------------------------------------------------------------
// Dump a buffer as a hex listing
//---------------------------------------------------------------------------- 
void HexDump( void *Dataa, int Len ) {  
  unsigned char  *Data=Dataa; 
  unsigned char  Line[80], *CurLine, *CurData;
  int   linelen, j;
  unsigned short  rc;
  unsigned char tmp;

  printf( "***************  HEX-DUMP  ***************\n" );
  while( Len ){
    linelen = Len < 16 ? Len : 16;
    *Line='\0';
    CurLine = Line;

    for( CurData=Data, j=0; j<linelen; j++ ) {
      tmp=*CurData;
      sprintf( CurLine, "%02BX ", tmp);
      CurLine+=3; CurData++;
    }           
 
    for( j=linelen; j<17; j++ ) {
      *CurLine  =' ';
      CurLine[1]=' ';
      CurLine[2]=' ';
      CurLine+=3;
    }
 
    for( j=0; j<linelen; j++ ) {
      sprintf( CurLine, "%c ", *Data > ' ' ? *Data : '_' );
      Data++;CurLine+=1;
    }
    printf( Line );
    printf("\n");
    Len-=linelen;
  }
  rc = printf( "***************   HEXEND   ***************\n" );
}
 
//----------------------------------------------------------------------------
// Timer 0 interrupt service function
// executes each 10 mSec @ 11.0592 MHz Crystal Clock
//----------------------------------------------------------------------------
timer0() interrupt 1 using 1 {
  TH0  = 0xDC;         // reload timer again
  TL0  = 0x00;         // to 10 mSec timeout
  TR0  = 1;
  if (Timer10mSec) Timer10mSec--;
}

//----------------------------------------------------------------------------
// Show state of nDASP, INTRQ and IORDY signals
//----------------------------------------------------------------------------
void ShowInputs(void) {
  printf("Signals from Drive: nDASP=%s, INTRQ=%s, IORDY=%s\n",
         nDASP ? "FALSE":"TRUE", INTRQ ? "TRUE":"FALSE", IORDY ? "TRUE":"FALSE");
}         
 
//----------------------------------------------------------------------------
// Select address and CS signals
//----------------------------------------------------------------------------
void SetAddress(unsigned char cs, unsigned char adr) { 
  DA0=((adr & 0x01)==0x01);
  DA1=((adr & 0x02)==0x02);
  DA2=((adr & 0x04)==0x04);
  if (cs==CTRL) { 
    nCS1FX=1;
    nCS3FX=0;
  } else {
    nCS1FX=0;
    nCS3FX=1;
  }
}

//----------------------------------------------------------------------------
// Read data WORD from Drive
//----------------------------------------------------------------------------
unsigned int ReadWORD(unsigned char cs, unsigned char adr) { 
  unsigned int tmp; 
  MSBDATA=ALLINPUT;
  LSBDATA=ALLINPUT;
  SetAddress(cs,adr); 
  nDIOR=0;
  tmp=(MSBDATA<<8)+LSBDATA;
  nDIOR=1;
  nCS1FX=1;
  nCS3FX=1;
  return tmp;
}
 
//----------------------------------------------------------------------------
// Read data BYTE from Drive
//----------------------------------------------------------------------------
unsigned char ReadBYTE(unsigned char cs, unsigned char adr) { 
  unsigned char tmp; 
  MSBDATA=ALLINPUT;
  LSBDATA=ALLINPUT;
  SetAddress(cs,adr); 
  nDIOR=0;
  tmp=LSBDATA;
  nDIOR=1;
  nCS1FX=1;
  nCS3FX=1;
  return tmp;
}
 
//----------------------------------------------------------------------------
// Write data WORD to Drive
//----------------------------------------------------------------------------
void WriteWORD(unsigned char cs, unsigned char adr, unsigned int dat) { 
  SetAddress(cs,adr); 
  // OBS MSB/LSB is swapped (see writeSector function)
  MSBDATA=dat;
  LSBDATA=(dat>>8);
  nDIOW=0;
  nDIOW=1;
  nCS1FX=1;
  nCS3FX=1;
}
 
//----------------------------------------------------------------------------
// Write data BYTE to Drive
//----------------------------------------------------------------------------
void WriteBYTE(unsigned char cs, unsigned char adr, unsigned char dat) { 
  SetAddress(cs,adr); 
  MSBDATA=0;
  LSBDATA=dat;
  nDIOW=0;
  nDIOW=1;
  nCS1FX=1;
  nCS3FX=1;
}
 
//----------------------------------------------------------------------------
// Send Identify Command to Drive, and fetch resulting data
//---------------------------------------------------------------------------- 
unsigned char IdentifyDrive(bit DriveNo,  unsigned char *Buffer, 
                            tdefDriveInfo *DriveInfo) {
  unsigned int i; 
  unsigned char Tmp;
  WriteBYTE(CMD, 6, 0xA0 + (DriveNo ? 0x10:0x00)); // Select drive
  WriteBYTE(CMD, 1, 0);  
  WriteBYTE(CMD, 2, 1);  
  WriteBYTE(CMD, 3, 1);  
  WriteBYTE(CMD, 4, 0);  
  WriteBYTE(CMD, 5, 0);  
  WriteBYTE(CMD, 7, 0xEC);  
  Timer10mSec=10000;
  while ((ReadBYTE(CMD,7) & 0x08)!=0x08 && Timer10mSec); // Wait for DRQ or timeout
  if (Timer10mSec==0) return 0xFF;
 
  // Fetch the data...
  MSBDATA=ALLINPUT;
  LSBDATA=ALLINPUT;
  // Select address and activate CS
  SetAddress(CMD, 0); 
  // Two bytes at a time
  for (i=0; i<512; i+=2) {
    nDIOR=0;
    *(Buffer+i)=LSBDATA;
    *(Buffer+i+1)=MSBDATA;
    nDIOR=1;
  }
  // Disable CS
  nCS1FX=1;
  nCS3FX=1;
  // Extract drive info
  DriveInfo->Heads           = (*(Buffer+7)<<8)  + *(Buffer+6);  
  DriveInfo->Tracks          = (*(Buffer+3)<<8)  + *(Buffer+2);
  DriveInfo->SectorsPerTrack = (*(Buffer+13)<<8) + *(Buffer+12);
  // Swap bytes, because of 16 bit transfer...
  for (i=0; i<40; i+=2) {
    Tmp=*(Buffer+54+i);
    *(Buffer+54+i)=*(Buffer+55+i);
    *(Buffer+55+i)=Tmp;
  }
  memcpy(DriveInfo->Model, Buffer+54, 80);
  // Terminate string...
  DriveInfo->Model[40]='\0';
  // Return the error register...
  return ReadBYTE(CMD, 1);
}
 
//----------------------------------------------------------------------------
// Read one sector, identified by drive, head, track and sector
// Returns contents of the Error Register (0x00 is no error detected)
//---------------------------------------------------------------------------- 
unsigned char ReadSector(unsigned char Drive, 
                unsigned char Head, unsigned int Track, unsigned char Sector,
                unsigned char *Buffer) {
  unsigned int i;
  // Prepare parameters...
  WriteBYTE(CMD,6, 0xA0+(Drive ? 0x10:00)+Head); // CHS mode/Drive/Head
  WriteBYTE(CMD,5, Track>>8);  // MSB of track
  WriteBYTE(CMD,4, Track);     // LSB of track
  WriteBYTE(CMD,3, Sector);    // sector
  WriteBYTE(CMD,2, 0x01);      // 1 sector
  // Issue read sector command...
  WriteBYTE(CMD,7, 0x20);      // Read sector(s) command
  Timer10mSec=10000;
  while ((ReadBYTE(CMD,7) & 0x08)!=0x08 && Timer10mSec); // Wait for DRQ or timeout
  if (Timer10mSec==0) return 0xFF;
 
  // Fetch the sector...
  MSBDATA=ALLINPUT;
  LSBDATA=ALLINPUT;
  // Select address and activate CS
  SetAddress(CMD, 0); 
  // Two bytes at a time
  for (i=0; i<512; i+=2) {
    nDIOR=0;
    *(Buffer+i)=LSBDATA;
    *(Buffer+i+1)=MSBDATA;
    nDIOR=1;
  }
  // Disable CS
  nCS1FX=1;
  nCS3FX=1;
  // Return the error register...
  return ReadBYTE(CMD, 1);
}
 
//----------------------------------------------------------------------------
// Write one sector, identified by drive, head, track and sector
// Returns contents of the Error Register (0x00 is no error detected)
//---------------------------------------------------------------------------- 
unsigned char WriteSector(unsigned char Drive, 
                unsigned char Head, unsigned int Track, unsigned char Sector,
                unsigned char *Buffer) {
  unsigned int i;
  // Prepare parameters...
  WriteBYTE(CMD,6, 0xA0+(Drive ? 0x10:00)+Head); // CHS mode/Drive/Head
  WriteBYTE(CMD,5, Track>>8);  // MSB of track
  WriteBYTE(CMD,4, Track);     // LSB of track
  WriteBYTE(CMD,3, Sector);    // sector
  WriteBYTE(CMD,2, 0x01);      // 1 sector
  // Issue write sector command...
  WriteBYTE(CMD,7, 0x30);      // Write sector(s) command
  Timer10mSec=10000;
  while ((ReadBYTE(CMD,7) & 0x08)!=0x08 && Timer10mSec); // Wait for DRQ or timeout
  if (Timer10mSec==0) return 0xFF;
 
  // Select address and activate CS
  SetAddress(CMD, 0); 
  // write sector data...
  for (i=0; i<512; i+=2) {
    LSBDATA=*(Buffer+i);
    MSBDATA=*(Buffer+i+1);
    nDIOW=0;
    nDIOW=1;
  }
  // Disable CS
  nCS1FX=1;
  nCS3FX=1;
  Timer10mSec=10000;
  while ((ReadBYTE(CMD,7) & 0xC0)!=0x40 && Timer10mSec); // Wait for DRDY and NOT BUSY
  if (Timer10mSec==0) return 0xFF;                       //   or timeout
 
  // Return the error register...
  return ReadBYTE(CMD, 1);
}

//----------------------------------------------------------------------------
// Set drive mode (STANDBY, IDLE)
//----------------------------------------------------------------------------
#define STANDBY 0
#define IDLE    1
#define SLEEP   2 
 
unsigned char SetMode(bit DriveNo, unsigned char Mode, bit PwrDown) {
  WriteBYTE(CMD, 6, 0xA0 + (DriveNo ? 0x10:0x00)); // Select drive
  WriteBYTE(CMD, 2, (PwrDown ? 0x01:0x00)); // Enable automatic power down
  switch (Mode) {
    case STANDBY: WriteBYTE(CMD,7, 0xE2); break;
    case IDLE:    WriteBYTE(CMD,7, 0xE3); break;
    // NOTE: To recover from sleep, either issue a soft or hardware reset !
    // (But not on all drives, f.ex seagate ST3655A it's not nessecary to reset
    // but only to go in Idle mode, But on a Conner CFA170A it's nessecary with
    // a reset)
    case SLEEP:   WriteBYTE(CMD,7, 0xE6); break;
  }
  Timer10mSec=10000;
  while ((ReadBYTE(CMD,7) & 0xC0)!=0x40 && Timer10mSec); // Wait for DRDY & NOT BUSY 
  if (Timer10mSec==0) return 0xFF;                       //   or timeout
 
  // Return the error register...
  return ReadBYTE(CMD, 1);
}
 
//----------------------------------------------------------------------------
// Show all IDE registers
//---------------------------------------------------------------------------- 
void ShowRegisters(bit DriveNo) { 
  WriteBYTE(CMD,6, 0xA0 + (DriveNo ? 0x10:0x00)); // Select drive
  printf("Reg 0=%02BX 1=%02BX 2=%02BX 3=%02BX 4=%02BX 5=%02BX 6=%02BX 7=%02BX \n",
    ReadBYTE(CMD, 0), ReadBYTE(CMD, 1), ReadBYTE(CMD, 2), ReadBYTE(CMD, 3),
    ReadBYTE(CMD, 4), ReadBYTE(CMD, 5), ReadBYTE(CMD, 6), ReadBYTE(CMD, 7));
} 
 
//----------------------------------------------------------------------------
// Main part
//----------------------------------------------------------------------------
void main() {
  unsigned int  i;
  char cmdbuf[15];
  tdefDriveInfo DriveInfo;
  int Head=0, Track=0, Sector=1;
 
 //------------------------------
 // Initialize outputs...
 //------------------------------
  nCS1FX=1;
  nCS3FX=1;  
  DA0   =0;  
  DA1   =0;  
  DA2   =0;  
  nDIOR =1;  
  nDIOW =1;  
  RESET =0; // Reset drive  
 //------------------------------
 // configure inputs...
 //------------------------------
  nDASP   =1;
  LSBDATA =ALLINPUT;
  MSBDATA =ALLINPUT;
  INTRQ   =1;
  IORDY   =1;
 //------------------------------
 // Configure onchip resources...
 //------------------------------
  TMOD |= 0x01;               // TMOD: timer 0, mode 1, 16 timer
  TH0   = 0xDC;               // Set timeout period for Timer 0 
  TL0   = 0x00;               // to 10 mSec timeout
  ET0   = 1;                  // Enable Timer 0 interrupts
  TR0   = 1;                  // Start timer 0
 
  SCON  = 0x50;               // SCON: mode 1, 8-bit UART, enable rcvr    */
  TMOD |= 0x20;               // TMOD: timer 1, mode 2, 8-bit reload      */
  TH1   = 0xFD;               // TH1:  reload value for 9.6 Kbaud         */
  PCON  = 0x00;               // PCON: Double baudrate, 19.2 Kbaud        */
  TR1   = 1;                  // TR1:  timer 1 run                        */
//ES    = 1;                  // ES:   Enable serial port int             */
  TI    = 1;                  // TI:   set TI to send first char of UART  */
  EA    = 1;                  // Global int enable
 
 //----------------------------------------------------------------------------
 // Let things settle, and after that, remove RESET from drive
 //----------------------------------------------------------------------------                        
  Delay(10);
  RESET=1;    // Remove reset to drive
 //----------------------------------------------------------------------------
 // ?
 //----------------------------------------------------------------------------
  printf("--------------------------------------------\n");
  printf("---------IDE EXERCISER (c) CHG 1996---------\n");
  printf("--------------------------------------------\n");
  ShowInputs();
 
  printf("Waiting for Initial DRDY & NOT BUSY from drive\n"); 
  WriteBYTE(CMD,6, 0xA0); // Set drive/Head register, ie. select drive 0
  while ((ReadBYTE(CMD,7) & 0xC0)!=0x40); // Wait for DRDY and NOT BUSY
  printf("Drive is ready!\n");
  
  printf("Spinning drive up (Going to IDLE mode)\n");
  printf("SetMode=%02bX\n", SetMode(DRIVE0, IDLE, TRUE));
  printf("Drive is ready!\n");
 
  printf("ReadSector=%02bX\n", ReadSector(DRIVE0, 0, 0, 1, SectorBuffer));
  HexDump(SectorBuffer, 512);
  for (i=0; i<512; i++) SectorBuffer[i]=i;
 
  printf("WriteSector=%02bX\n", WriteSector(DRIVE0, 0, 0, 1, SectorBuffer));
  printf("ReadSector=%02bX\n", ReadSector(DRIVE0, 0, 0, 1, SectorBuffer));
  HexDump(SectorBuffer, 512);
 
  while (1) {
    printf ("\nCommand: ");
    gets(cmdbuf, sizeof(cmdbuf));
    for (i=0; cmdbuf[i]!=0; i++) cmdbuf[i] = toupper(cmdbuf[i]);
    for (i = 0; cmdbuf[i] == ' '; i++);        /* skip blanks                 */
 
    switch (cmdbuf[i]) {
      case 'S': SetMode(DRIVE0, STANDBY, TRUE); break;
      case 'I': SetMode(DRIVE0, IDLE, TRUE);    break;
      case 'D': IdentifyDrive(DRIVE0, SectorBuffer, &DriveInfo);
                HexDump(SectorBuffer, 256);
                printf("Model         = %s\n", DriveInfo.Model);
                printf("Heads         = %bu\n",DriveInfo.Heads);
                printf("Tracks        = %u\n", DriveInfo.Tracks);
                printf("Sectors/Track = %u\n", DriveInfo.SectorsPerTrack);
 
        break;
      case 'R': ShowRegisters(DRIVE0); break;
      case '?':
                printf("--------------------------------------------\n");
                printf("---------IDE EXERCISER (c) CHG 1996---------\n");
                printf("--------------------------------------------\n");
                printf("Commands:\n");
                printf("  S    go to Standby mode\n");
                printf("  I    go to Idle mode\n");
                printf("  D    Identify drive\n");
                printf("  R    Show all registers\n");
                printf("  G <Head>,<Track>,<Sector> Read sector\n");
                printf("--------------------------------------------\n");
        break;
      case 'G':
        sscanf (&cmdbuf[i+1], "%d,%d,%d",
                 &Head, &Track, &Sector);
        printf("ReadSector=%02bX\n", ReadSector(DRIVE0, Head, Track, Sector, SectorBuffer));
        HexDump(SectorBuffer, 512);
        printf("Head=%d, Track=%d, Sector=%d\n", Head, Track, Sector);
        break;
    }
  }    
}
