/*************************************************************************

    p8000_16_wdc.c driver.

   Copyright Matt Knoth 2008 p8000@knothusa.net

   - This source code is released as freeware for non-commercial purposes.
   - You are free to use and redistribute this code in modified or
     unmodified form, provided you list me in the credits.
   - If you modify this source code, you must add a notice to each modified
     source file that it has been changed.
   - The author of this copywritten work reserves the right to change the
     terms of its usage and license at any time, including retroactively
   - This entire notice must remain in the source code.

**************************************************************************/

#include <string.h>
#include "driver.h"
#include "ui.h"
#include "deprecat.h"
#include "cpu/z80/z80.h"
#include "machine/z80pio.h"
#include "cpu/z8001/z8001.h"

static int p8000_wdc_execute( void );

static int   data_ctr;
static UINT8 wdc_status;
static UINT8 cmd_ctr;
static UINT8 cmd_buf[9];
static UINT8 ram_buf[6144];
static UINT8 disk[53000000];
static UINT8 *pAccess;
static emu_timer* wdc_exe_timer;

static UINT8 par_table[200] = {   'W',  'D',  'C',  '_',  '4',  '.',  '2', 0x00,  'R',  'O',  'B',  ' ',  'K',  '5',  '5',  '0',
                                  '4',  '.',  '5',  '0', 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x12, 0x00, 0x04, 0x01, 0x00, 0x00, 
                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x01, 0x00, 0x01, 0x01, 
                                 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

static UINT8 btt_table[200];

static void p8000_wdc_send_status(UINT8 data)
{
   z80pio_p_w(2, 1, data);
}

static TIMER_CALLBACK( wdc_exec_phase_timer_cb )
{
   wdc_status=param;
   p8000_wdc_send_status(param);
}

void p8000_wdc_init( void )
{ FILE* fp;
  char buf[256];

  sprintf(buf,"%s\\%s\\%s",options_get_string(mame_options(),OPTION_FLOPPY_DIRECTORY),options_get_string(mame_options(),OPTION_GAMENAME),"harddisk.wdc");
  if((fp=fopen(buf,"rb"))==NULL) {
      fprintf(stderr,"ERROR: unable to open WDC file %s\n",buf);
      exit(1);
  }
  fread(disk,1,53000000,fp);
  fclose(fp);

  wdc_exe_timer = timer_alloc( wdc_exec_phase_timer_cb, NULL );  // get execution timer
}

void p8000_wdc_reset( void )
{
//  fprintf(stderr,"WDC: p8000_wdc_reset()\n");
  cmd_ctr=0;
  data_ctr=0;
  wdc_status=0xff;
  pAccess=&ram_buf[0];
}

void  p8000_wdc_wr_ctl( UINT8 data )     // set control bits to WDC ////////////////////////////////////////////
{
  if(data & 0x20) {
     p8000_wdc_reset();                  // WDR reset bit set
     wdc_status = data;                  // mask out input data
  } else {
     if(wdc_status & 0x20)
        wdc_status = (data & ~0xa7) | 0x06;
  }
  p8000_wdc_send_status(wdc_status);
}

void  p8000_wdc_wr_data( UINT8 data )    // write data to WDC //////////////////////////////////////////////////
{
  if(cmd_ctr<=8) {                       // receive command
     cmd_buf[cmd_ctr++]=data;
     if(cmd_ctr>8) {
        data_ctr=0;
//      fprintf(stderr,"WDC-CMD: %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",cmd_buf[0],cmd_buf[1],cmd_buf[2],cmd_buf[3],cmd_buf[4],cmd_buf[5],cmd_buf[6],cmd_buf[7],cmd_buf[8]);
        wdc_status=p8000_wdc_execute();
        timer_set(ATTOTIME_IN_USEC(100), NULL, wdc_status, wdc_exec_phase_timer_cb);
        wdc_status=0xff;
        p8000_wdc_send_status(wdc_status);   
     } else {
        p8000_wdc_send_status( 0xff );   // set busy
     }
  } else {                               // receive write data
     if(data_ctr) {
        *pAccess++ = data;
        data_ctr--;
        if(data_ctr==0) {
           timer_set(ATTOTIME_IN_USEC(100), NULL, 0xfe, wdc_exec_phase_timer_cb);
           wdc_status=0xff;
           p8000_wdc_send_status(wdc_status);
           cmd_ctr=0;
//         fprintf(stderr,"WDC: 0x%02x%02x bytes written\n",cmd_buf[7],cmd_buf[6]);
        }
     } 
  }
}

UINT8 p8000_wdc_rd_data( void )          // read data from WDC
{  UINT8 res;

   if(data_ctr) {
      res=*pAccess++;
      data_ctr--;
      if(data_ctr==0) {
         timer_set(ATTOTIME_IN_USEC(100), NULL, 0xfe, wdc_exec_phase_timer_cb);
         wdc_status=0xff;
         p8000_wdc_send_status(wdc_status);
         cmd_ctr=0;
//       fprintf(stderr,"WDC: 0x%02x%02x bytes read\n",cmd_buf[7],cmd_buf[6]);
      }
   } else 
      res=0;
   return res;
}

void  p8000_wdc_disk_save( void )        // write WDC back to disk
{ FILE* fp;
  char buf[256];

  sprintf(buf,"%s\\%s\\%s",options_get_string(mame_options(),OPTION_FLOPPY_DIRECTORY),options_get_string(mame_options(),OPTION_GAMENAME),"harddisk.wdc");
  if((fp=fopen(buf,"wb"))==NULL) {
      fprintf(stderr,"ERROR: unable to open WDC file %s for saving disk\n",buf);
      exit(1);
  }
  fwrite(disk,1,53000000,fp);
  fclose(fp);
}

static int p8000_wdc_execute( void )
{
     int res;
     UINT32 offset;
 
     res=~0x03;
 
     switch(cmd_buf[0]) {                             // in order of occurence frequency
     case  0x21:  //;HD-Lesen von einer Blocknummer
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=(&disk[0]) + (((cmd_buf[5]<<24) + (cmd_buf[4]<<16) + (cmd_buf[3]<<8) + cmd_buf[2])<<9);
                  res=~0x03;
                  break;
     case  0x22:  //;HD-Schreiben auf eine Blocknummer
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=(&disk[0]) + (((cmd_buf[5]<<24) + (cmd_buf[4]<<16) + (cmd_buf[3]<<8) + cmd_buf[2])<<9);
                  res=0xfd;
                  break;
     case  0x01:  //;HD-Lesen von einer Sektoradresse
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=(&disk[0]) + ((par_table[26]*par_table[25]*((cmd_buf[3]<<8)+cmd_buf[2]))+(par_table[26]*cmd_buf[4])+cmd_buf[4]-1);
                  res=~0x03;
                  break;
//   case  0x81:  //;HD-Testlesen von einer Sektoradresse
//                break;
//   case  0x11:  //;HD-Lesen von einer Sektoradresse mit BTT-Berueck.
//                break;
//   case  0x02:  //;HD-Schreiben auf eine Sektoradresse
//                break;
//   case  0x82:  //;HD-Schreiben auf eine Sektoradresse mit Ruecklesen
//                break;
//   case  0x12:  //;HD-Schreiben auf eine Sektoradresse mit BTT-Berue.
//                break;
//   case  0x92:  //;HD-Schreiben auf eine Sektoradresse mit BTT-Beruecksichtigung und Ruecklesen
//                break;
//   case  0xa1:  //;HD-Kopieren von einer Blocknummer des LW 1 auf LW 0
//                break;
//   case  0xa2:  //;HD-Schreiben auf eine Blocknummer mit Ruecklesen
//                break;
     case  0xc2:  //;HD-Schreiben von PAR und BTT auf das Laufwerk
                  cmd_ctr=0;
                  data_ctr=0;
                  pAccess=&par_table[0];
                  res=0xf6;
                  break;
     case  0x04:  //;HD-Formatieren einer Spur
                  cmd_ctr=0;
                  data_ctr=0;
                  pAccess=&ram_buf[0];
                  memset(pAccess,0,3);
                  offset=((par_table[26]*par_table[25]*((cmd_buf[3]<<8)+cmd_buf[2]))+(par_table[26]*cmd_buf[4]))<<9;  // ((sectors*heads*cyl)+(sectors*head))*512
                  memset(disk+offset,0x00,par_table[26]<<9);  // sectors*512
                  res=0xf6;
                  break;
//   case  0x14:  //;HD-Formatieren einer Spur mit Ruecklesen
//                break;
     case  0x24:  //;HD-Formatieren einer Spur mit Defektspurfeststellung
                  cmd_ctr=0;
                  data_ctr=0; //OR: 3 - if error (cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&ram_buf[0]; 
                  memset(pAccess,0,3);
                  offset=((par_table[26]*par_table[25]*((cmd_buf[3]<<8)+cmd_buf[2]))+(par_table[26]*cmd_buf[4]))<<9;  // ((sectors*heads*cyl)+(sectors*head))*512
                  memset(disk+offset,0x00,par_table[26]<<9);  // sectors*512
                  res=0xf6;  // f6 for OK, f4 for error -- emulator always ok
                  break;
     case  0x44:  //;HD-Ruecklesen einer Spur (Verify)
                  cmd_ctr=0;
                  data_ctr=0;
                  pAccess=&ram_buf[0];
                  memset(pAccess,0,3);
                  res=0xf6;
                  break;
//   case  0x84:  //;HD-Loeschen einer Spur
//                break;
     case  0x08:  //;Lesen WDC-RAM
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=(&ram_buf[0]) + (0x1fff & ((cmd_buf[2]<<8)|cmd_buf[1]));
                  res=~0x03;
                  break;
     case  0x18:  //;Schreiben WDC-RAM
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=(&ram_buf[0]) + (0x1fff & ((cmd_buf[2]<<8)|cmd_buf[1]));
                  res=~0x03;
                  break;
     case  0x28:  //;Lesen des WDC-Parameterblocks
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&par_table[0];
                  res=0xf4;
                  break;
     case  0x38:  //;Lesen der WDC-Fehlerstatistik
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&ram_buf[0];
                  memset(pAccess,0,200);
                  res=0xf4;
                  break;
     case  0x48:  //;Loeschen der BTT im WDC-RAM
                  cmd_ctr=0;
                  data_ctr=0;
                  pAccess=&btt_table[0];
                  memset(pAccess,0,200);
                  res=0xf6;
                  break;
     case  0x58:  //;Lesen der BTT aus dem WDC-RAM in den Host-RAM
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&btt_table[0];
                  res=0xf4;
                  break;
     case  0x68:  //;Schreiben der BTT aus dem Host-RAM in den WDC-RAM
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&btt_table[0];
                  res=0xf5;
                  break;
     case  0x78:  //;Schriben des WDC-Parameterblocks
                  data_ctr=(cmd_buf[7]<<8)|cmd_buf[6];
                  pAccess=&par_table[8];
                  res=0xf5;
                  break;
     default:
                 res=0x01;
                 ram_buf[0]=0x01;
                 pAccess=&ram_buf[0];
                 data_ctr=1;
     }
 
     return res;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Tabelle 6.5-3 WDC-Fehler:
// 
// Code ! Fehlererlaeuterung
// -----!------------------------------------------------------
//   00 ! kein Fehler, Durchfuehrung der Aktion ok.
//   01 ! unerlaubter Kommandocode
//   02 ! Head-Nummer im Kommando zu gross
//   03 ! Drive-Nummer im Kommando zu gross
//   04 ! unerlaubte Zylindernummer
//   05 ! Lesefehler beim Einlesen der Defektspurtabelle
//   06 ! Plausibilitaetsfehler in der eingelesenen Defektspurtabelle
//   07 ! nach RESET nicht Track0 erreicht (kein Track0-Signal)
//   08 ! kein Laufwerk "ready"
//   09 ! Sektor nicht gefunden nach Distanzwiederholung
//   0A ! kein "FB" als Datenfeldkennzeichen
//   0B ! keine Marke erkannt (Zeitueberwachung)
//   0C ! kein Kennfeld gefunden, Fehler im Kennfeld
//   0D ! CRC-Fehler im Kennfeld (noch kein exaktes Kennfeld gefunden)
//   0E ! CRC-Fehler im Kennfeld (exaktes Kennfeld gefunden)
//   0F ! CRC-Fehler im Datenfeld
//   11 ! Track0-Signal bei gefordertem Zylinder 0 nicht aktiv
//   12 ! Sektornummer des zu schreibenden Sektors falsch
//   13 ! "Fault Write"-Signal aktiv
//   14 ! Schreibfehler bei "Read after Write"
//   15 ! Blocknummer im Kommando zu gross
//   20 ! EPROM-Pruefsummenfehler
///////////////////////////////////////////////////////////////////////////////////////////////////
}

