/*******************************************************************************
*       This file contains the TASCOM ECB driver functions.
*
*
*       Project: TASCOM
*   File name: ecbdriv_els.c
*
*  Author: J. Bundgaard                    Nov. 1991.
*
*
*
* Copyright (C) Risoe National Laboratory, 1991-1994, All rights reserved
*******************************************************************************/
/*******************************************************************************
*&   modified by:
*&
*&
*&   V003.101   P.S.    26-APR-2004
*&                      For the ecb_load program only.
*&
*& 
*&*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>

#include "ecb_load.h"
#include GPIB_H

/*******************************************************************************
* Static variables
*******************************************************************************/
static repete = 1;
static int look_up[] = {10, 11, 12, 13, 14 ,15};

/*******************************************************************************
* External variables defined in this file.
*******************************************************************************/
int x_ecb1 = -1;               /* Unit descriptor of ECB system #1 */
Z80_reg x_inreg;          /* Z80 registers b,c,d and e before call */
Z80_reg x_outreg;            /* Z80 registers b,c,d and e after call */
unsigned char x_ecb_data[ECB_BYTES]; /* Array to hold data blocks for ECB */
int x_ecb_error_count = 0;           /* Counter for ECB call errors */
int x_cpu_typ = 0;                /* =0: WP-CPU, =1: P2701, =2: P2701 + P2501 */
Ecb_data x_ecba;                     /* DMA data from P2701a ECB-CPU */

/*******************************************************************************
* Prototypes for functions defined in this file.
*******************************************************************************/
extern void Ecb_init(void);
extern int Ecb_error(void);
extern void Ecb_unit (void);                   /* Establish unit No. for ECB */
extern int Ecb_func (int , unsigned char);     /* Call ECB function */
extern void Ecb_read (int, unsigned short, unsigned short); /* Read n bytes */
extern void Ecb_write (int, unsigned short, unsigned short); /* Write n bytes */

static int Ecb_prepare (int, unsigned char, unsigned short, unsigned short);
static int Ecb_send_func (int, unsigned char);              /* Send function code */
static void Ecb_error_func (char *, unsigned char, int);
static void Restart_ecb_func (int unit, unsigned char function);
static int Restart_ecb_prepare(int,unsigned char,unsigned short,unsigned short);
static void Do_reset (void);
extern void Ecb_down_load (FILE *stream);
static unsigned long Hex_to_bin(char c);
static char Bin_to_hex (int i);
extern int Ecb_type (int);

/*******************************************************************************
* ECB system initialisation
*******************************************************************************/

extern void
Ecb_init(void)
{

  x_cpu_typ = 1;                       /* New CPU module, P2701 */

  x_ecb1 = -1;
  if (x_gpib0 >= 0)
  {
    x_ecb1 = ibfind (ECB1);         /* Now see if the ECB system is avaiable */
    if (x_ecb1 <= 0)
    {
      x_ecb1 = -1;
      x_error_text =(char *) ECB1;
      x_tas_error = GPIB_FIND_ERROR;
      Ecb_load_error();
    }
    else
    {
      ibsic (x_gpib0);                   /* Send interface clear */
      usleep (300000);                   /* Wait 0.3 sec. */
      ibtmo (x_ecb1, T10s);              /* Time out 10 sec. */
    }
  }

  x_tas_error = FALSE;

  return;
}
/*******************************************************************************
* Function to call a Z80 function (subroutine) without interrupt.
* This means that the function No. must be greater than or equal to
* 128 (function 0) and less than or equal to 191 (function 63).
* 'Unit' is the address of the ECB system on the GPIB bus. Four Z80 regs.
* are transferred upon the call from the host to the ECB system, and
* again read back to the host upon exit from the ECB function. This
* mechanismen is used to tranfer of arguments and results. Two global
* structures are used for the purpose, x_inreg and x_outreg.
*******************************************************************************/

extern int
Ecb_func (int unit, unsigned char function)
{
  int byte_cnt;
  short status;
  char *ecb_error_text;
  void TC_FAR *point_input = &x_inreg;
  void TC_FAR *point_output = &x_outreg;

  if (Ecb_send_func (unit, function) == FALSE)
  {
    if (repete == 1)
      Restart_ecb_func (unit, function); /* GPIB error: recall Ecb_func() once */
    return (FALSE);
  }

  /* Function accepted. Send the 4 Z80 register values */
  byte_cnt = 4;
  status =  ibwrt (unit, point_input, byte_cnt);
  if (status < 0)
  {
    if (repete ==1)
    {
      Restart_ecb_func (unit, function); /* GPIB error: recall Ecb_func() once */
      return;
    }
    if (status | TIMO)
      x_error_text = "Timeout";
    else
       x_error_text = '\0';
    x_tas_error = GPIB_WRITE_ERROR;
    ecb_error_text = "during write of data bytes at ECB function";
    Ecb_error_func(ecb_error_text, function, unit);
    return(FALSE);
  }
  /* Wait for the function to complete and then receive the 4 Z80 regs. values */
  status = ibrd (unit, point_output, byte_cnt);
  if (status < 0)
  {
    if (repete ==1)
    {
      Restart_ecb_func (unit, function); /* GPIB error: recall Ecb_func() once */
      return(FALSE);
    }
    if (status | TIMO)
      x_error_text = "Timeout";
    else
      x_error_text = '\0';
    x_tas_error = GPIB_READ_ERROR;
    ecb_error_text = "during read of data bytes at ECB function";
    Ecb_error_func(ecb_error_text, function, unit);
    return(FALSE);
  }
  return(TRUE);
}


/*******************************************************************************
* Function to re-call Ecb_func() in case of a GPIB bus error.
*******************************************************************************/

static void
Restart_ecb_func (int unit, unsigned char function)
{

  Do_reset();
  Ecb_func (unit, function);
  repete = 1;                              /* Reset repete to allow re-calls */
  return; 
}




/*******************************************************************************
* Function to re-call Ecb_send_func() in case of a GPIB bus error.
*******************************************************************************/

static int
Restart_ecb_prepare (int unit, unsigned char function,
           unsigned short address, unsigned short byte_count)
{
  short status;

  Do_reset();
  status = Ecb_prepare (unit, function, address, byte_count);
  repete = 1;                              /* Reset repete to allow re-calls */
  return(status); 
}




/*******************************************************************************
* Send Interface clear and print a message.
*******************************************************************************/

static void
Do_reset (void)
{
  ibsic (x_gpib0);                        /* Send interface clear */
  usleep (300000);                        /* Wait 0.3 sec. */
  ibsic (x_gpib0);                        /* Send interface clear */
  usleep (300000);                        /* Wait 0.3 sec. */
  repete = 0;                             /* No more re-calls */
  return; 
}



/*******************************************************************************
* Function that transfers 'byte_count' bytes from the ECB system to
* the host. 'Address' specifies source start address in the Z80 me-
* mory space. Destination is the global unsigned character array
* x_ecb_data.
*******************************************************************************/

extern void
Ecb_read (int unit, unsigned short address, unsigned short byte_count)
{
  short status;
  void TC_FAR *point_data = &x_ecb_data;

  if (Ecb_prepare (unit, READ_N_BYTES, address, byte_count) == FALSE)
    return;

  /* Read the requested No. of bytes */
  status = ibrd (unit, point_data, (int) byte_count);
  if (status < 0)
    {
      if (status | TIMO)
    x_error_text = "Timeout";
      else
     x_error_text = '\0';
      x_tas_error = GPIB_READ_ERROR;
      return;
    }
  return;
}


/*******************************************************************************
* Function that transfers 'byte_count' bytes from the host to the ECB
* system. 'Address' specifies destination start address in the Z80 me-
* mory space. Source is the global unsigned character array x_ecb_data.
*******************************************************************************/

extern void
Ecb_write (int unit, unsigned short address, unsigned short byte_count)
{
  short status;
  char *ecb_error_text;
  void TC_FAR *point_data = &x_ecb_data;

  if (Ecb_prepare (unit, WRITE_N_BYTES, address, byte_count) == FALSE)
    return;

  /* Write the requested No. of bytes */
  status = ibwrt (unit, point_data, (int) byte_count);
  if (status < 0)
    {
      if (status | TIMO)
     x_error_text = "Timeout";
      else
     x_error_text = '\0';
      x_tas_error = GPIB_WRITE_ERROR;
      ecb_error_text = "";
      Ecb_error_func(ecb_error_text, 0, unit);
      return;
    }

  /* Receive a dummy byte */
  byte_count = 1;
  status = ibrd (unit, point_data, (int) byte_count);
  if (status < 0)
    {
      if (status | TIMO)
   x_error_text = "Timeout";
      else
     x_error_text = '\0';
      x_tas_error = GPIB_READ_ERROR;
      return;
    }

  return;
}


/*******************************************************************************
* Prepare an ECB read or write n bytes. See Ecb_read() and Ecb_write().
*******************************************************************************/
static int
Ecb_prepare (int unit, unsigned char function,
         unsigned short address, unsigned short byte_count)
{
  int byte_cnt;
  short status;
  Swap save, adr, count;
  char *ecb_error_text;
  void TC_FAR *point_adress = (Swap *) &adr.word;
  void TC_FAR *point_count = (Swap *) &count.word;

  if (byte_count > ECB_BYTES)
    {
      x_error_int = byte_count;
      x_tas_error = ECB_OVERFLOW;
      ecb_error_text = "";
      Ecb_error_func(ecb_error_text, 0, unit);
      return (FALSE);
    }

  save.word = address;            /* Swap address bytes */
  adr.b.lsb = save.b.msb;
  adr.b.msb = save.b.lsb;
  save.word = byte_count;     /* Swap byte count bytes */
  count.b.lsb = save.b.msb;
  count.b.msb = save.b.lsb;

  if (Ecb_send_func (unit, function) == FALSE)
  {
    if (repete ==1)
    {                            /* GPIB error, Restart Ecb_prepare() once */
      status = Restart_ecb_prepare (unit, function, address, byte_count);
      return(status);
    }
    ecb_error_text = "during write of ECB function code ";
    Ecb_error_func(ecb_error_text, function, unit);
    return (FALSE);
  }

  byte_cnt = 2;      /* Function accepted, send the two byte address value */
  status = ibwrt (unit, point_adress, byte_cnt);
  if (status < 0)
  {
    if (repete ==1)
    {                           /* GPIB error, Restart Ecb_prepare() once */
      Restart_ecb_prepare (unit, function, address, byte_count);
      return(status);
    }
      if (status | TIMO)
   x_error_text = "Timeout";
      else
     x_error_text = '\0';
      x_tas_error = GPIB_WRITE_ERROR;
      ecb_error_text = "";
      Ecb_error_func(ecb_error_text, 0, unit);
      return (FALSE);
  }
  /* Send the two byte byte_count value */
  status = ibwrt (unit, point_count, byte_cnt);
  if (status < 0)
  {
    if (repete ==1)
    {                          /* GPIB error, Restart Ecb_prepare() once */
      Restart_ecb_prepare (unit, function, address, byte_count);
      return(status);
    }
      if (status | TIMO)
        x_error_text = "Timeout";
      else
     x_error_text = '\0';
      x_tas_error = GPIB_WRITE_ERROR;
      ecb_error_text = "";
      Ecb_error_func(ecb_error_text, 0, unit);
      return (FALSE);
  }
  return (TRUE);
}

/*******************************************************************************
* Function to send ECB function code and receive an acknowledge
* character as response.
*******************************************************************************/

static int
Ecb_send_func (int unit, unsigned char function)
{
  unsigned char response;
  unsigned long byte_cnt;
  int error_type;
  short status;
  char *ecb_error_text;
  void TC_FAR *point_func = &function;
  void TC_FAR *point_resp = &response;

  byte_cnt = 1;                         /* Send function code */
  status = ibwrt (unit, point_func, byte_cnt);
  error_type = iberr;
  if (status < 0)
  {
  if (repete == 1)
    return(FALSE);
  if (status | TIMO)
  {
      if ((error_type == ENOL) || (error_type == EBUS))
      {
        x_error_text = ECBTEXT;
        x_tas_error = GPIB_NO_LISNER_ERROR;
        Ecb_load_error();
        return (FALSE);
      }
      x_error_text = "Timeout";
  }
  else
    x_error_text = '\0';
  x_tas_error = GPIB_WRITE_ERROR;
  Ecb_load_error();
  return (FALSE);
  }
  
  /* Get acknowledge byte back */
  status = ibrd (unit, point_resp, byte_cnt);
  if (status < 0)
  {
    if (repete == 1)
      return(FALSE);
    if (status | TIMO)
      x_error_text = "Timeout";
    else
      x_error_text = '\0';
    x_tas_error = GPIB_READ_ERROR;
    ecb_error_text = "during read of ack-byte at ECB function ";
    Ecb_error_func(ecb_error_text, function, unit);
    return (FALSE);
  }
  if (response != ACKN)           /* Acknowledge ? */
    {
      x_error_int = function;
      x_tas_error = ECB_ILLEGAL_FUNC;
      ecb_error_text = " ECB function code ";
      Ecb_error_func(ecb_error_text, function, unit);
      return (FALSE);
    }
  return (TRUE);
}



/*******************************************************************************
* Errors at calls to the ECB system. After two ECB errors TASCOM
* returns to DOS.
*******************************************************************************/

static void
Ecb_error_func (char *ecb_error_text, unsigned char function, int unit)
{

  Ecb_load_error();
  if (function != 0)
  {
    printf ("%s %d\n",ecb_error_text, function);
    fflush (stdout);
    exit(0);
  }

  return;
}

/*******************************************************************************
* Errors at calls to the ECB system. After two ECB errors TASCOM
* returns to DOS.
*******************************************************************************/

static void
Ecb_error_func1 (char *ecb_error_text, unsigned char function, int unit)
{

  Ecb_load_error();
  if (function != 0)
  {
    printf ("%s %d\n",ecb_error_text, function);
    fflush (stdout);
    exit(0);
  }

  return;
}



/******************************************************************************
* Down load the ECB program.
* Reads lines from the HEX file and add up to 20 data lines together before
* sending them to the ECB system.
******************************************************************************/

extern void 
Ecb_down_load (FILE *stream)
{
  int ecb_type, unit, function, load_count = -1;
  char *point;
  unsigned short byte_count, address;
  char line[F_BUFSIZ];
  unsigned long type, no_bytes, adrs, total_bytes, old_adrs, total1, total0, total2;
  int line_no = 0;
  void TC_FAR *point_func = &function;
  int line_not_output = 0;   /* if =1: data waiting in line[] */
  void TC_FAR *line9 = &line[9];


/*  Stop_display(); */
  for (;;)
  {
    if (line_not_output == 1)
      line_not_output = 0;                       /* Start with old line */
    else
    {                                            /* Read a line from file */
      point = fgets (line, F_BUFSIZ, stream);
      if (point != NULL)
      {
        byte_count = strlen(line);
        byte_count -= 4;            /* Remove checksum and \n in the       */
        line[byte_count+1] = NUL;   /* line, i.e. the four last characters */
        line[0] = '0';              /* Replace : in line[0] with 0 */
      }
      else
      {
        if (!ferror(stream))
        byte_count = 0;
        else
        {
        fclose (stream);
          stream = NULL;
          x_tas_error = FILE_READ;
         Ecb_load_error ();
        printf (" during download of the ECB program\n");
/*          Start_display();  */
    return;
        }
      }
    }
    line_no += 1;
    if (byte_count != 0)
    {
      if (line_no == 1)
        strcpy (x_ecb_data, line);
      type = (Hex_to_bin(line[7]) << 4) + Hex_to_bin(line[8]);
      if (type != 0)
      {
        if (line_no > 1)           /* Output buffer and mark that */
          line_not_output = 1;     /* last input line has not been output */
      }
      else
      {
        no_bytes = (Hex_to_bin(line[1]) << 4) + Hex_to_bin(line[2]);
        adrs = (Hex_to_bin(line[3]) << 12) + (Hex_to_bin(line[4]) << 8) +
            (Hex_to_bin(line[5]) << 4) + Hex_to_bin(line[6]);
        if (line_no == 1)
        {
          total_bytes = no_bytes;
          old_adrs = adrs;
          continue;         /* Get next input line */
        }
        else
        {
          if (old_adrs + no_bytes == adrs)
          {                                    /* Contigious addresses */
            total_bytes += no_bytes;
            old_adrs = adrs;
            strcat (x_ecb_data, line9);       /* Add the new bytes to buffer */
            total0 = total_bytes/256;         /* and adjust number of bytes  */
            x_ecb_data[0] = Bin_to_hex(total0);
            total1 = total_bytes - 256*total0;
            total2 = total1/16;
            x_ecb_data[1] = Bin_to_hex(total2);
            x_ecb_data[2] = Bin_to_hex(total1 - 16*total2);
            if (line_no < 20)                        /* Max 20 lines together */
              continue;       /* Get next input line */
          }
        }
        line_not_output = 0;   /* Output buffer, nothing waiting in line[] */
      }

      unit = x_ecb1;
      address = 1;
      byte_count = strlen (x_ecb_data);
      Ecb_write (unit, address, byte_count);          /* Write a line to ECB */
      line_no = 0;

/*      if (Test_ctrl_c())
/*      {
/*        Start_display();
/*        return;                 /* Control(c) hit */
/*      }
*/

      if (load_count == -1)     /* Type Loading followed by dots while   */
      {                         /*  down loading the file                */
        printf ("\nLoading");
        fflush (stdout);
        load_count = 0;
      }
      load_count += 1;

      if (load_count > 15)
      {
        printf (".");
        fflush (stdout);
        load_count = 0;
      }


      function = ECB_DOWN_LOAD;
      Ecb_func (unit, function);               /* Call ECB download function */
      if (x_outreg.b != LOAD_NEXT)                /* Wait for reply from ECB */
        break;                 /* If LOAD_NEXT, get next line, else finished */
    }
  }
  printf ("\nLoading complete\n");
  fflush (stdout);

/*  Start_display(); */
  return;
}

/*******************************************************************************
* Convert HEX to bin
*******************************************************************************/

static unsigned long
Hex_to_bin(char c)
{

  if(isdigit((int) c))
    return (c - '0');
  else
    return look_up[c - 'A'];
}


/*******************************************************************************
* Convert Bin to HEX
*******************************************************************************/

static char
Bin_to_hex (int i)
{

  if(i <= 9)
    return ( (char)(i + (int)('0')) );
  else
    return ( (char)( (i - 10) + (int)('A')) );
}


/******************************************************************************
* Get type and version of the ECB program. If return_value is TYPE_OUT, the
* type is returned as 1 for RAM and 0 for ROM. If return_value is VERS_OUT,
* the version number * 100 is returned.
* If return_value = 0, type and version are printed.
******************************************************************************/

extern int
Ecb_type (int return_value)
{
  int unit, function;
  int version, status;


  /* P2701a CPU: Get ROM/RAM type */
  unit = x_ecb1;
  function = ECB_TYPE;
  status = Ecb_func (unit, function);
  if (status == 0)
    exit(0);    


  version = 100*(int)x_outreg.b + 10*(int)x_outreg.c + (int)x_outreg.e;

  if (return_value == TYPE_OUT)
    return((int) x_outreg.d);

  if (return_value == VERS_OUT)
    return(version);

  printf ("ECB program: ");
  if (x_outreg.d == 1)
    printf ("RAM");
  else if (x_outreg.d == 0)
    printf ("ROM");
  printf (" mode, version %4.2f\n", ((float) version)/100);
  fflush (stdout);

  return((int) x_outreg.d);
}


/*******************************************************************************
* Ecb option error routine.
*******************************************************************************/

extern int
Ecb_error(void)
{

  switch (x_tas_error)
  {
    case ECB_OVERFLOW:
      printf (" Byte count %d overflows ECB data buffer.", x_error_int);
      break;

    case ECB_ILLEGAL_FUNC:
      printf (" No such ECB function: %d", x_error_int);
      break;

    default:
      return(NOT_FOUND);
  }

  printf ("\n");
  fflush (stdout);
  return(FOUND);

}
