/*-----------------------------------------------------------------------
  The ECB is a rack controller from Risoe based on a Z80 processor. 
  This module provides some functions for communicating with such a
  device. This is the implementation file. 

  WARNING: This contains code which may be endian dependent!

  copyright: see file COPYRIGHT

  Mark Koennecke, January 2002, with some bits taken from the original 
  tascom code.
  -------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <tcl.h>
#include <unistd.h>
#include "fortify.h"
#include "sics.h"
#include "ecb.h"
#include "ecb.i"
/*------------- private defines and error codes ------------------------*/
#define ACKN ('\6')             /* Acknowledge character */
#define READ_BYTES  3
#define WRITE_BYTES 4
#define DMAREAD     5
#define ECB_BYTES 65535

typedef union {                 /* Used to swap bytes in 'address' and 'byte_count' */
  unsigned short word;
  struct {
    unsigned char msb;          /* Most significant byte */
    unsigned char lsb;          /* Least significant byte */
  } b;
} Swap;
/* ------- error codes */
#define ECBILLEGALFUNC -100
#define ECBOVERFLOW    -101

/*----------------------------------------------------------------------*/
static int ecbSendFunc(pECB self, int func)
{
  unsigned char function, response;
  int count, status;

  /*
     send function code
   */
  function = (unsigned char) func;
  count = 1;
  status = GPIBsend(self->gpib, self->ecbDeviceID, &function, count);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  /*
     read acknowledge byte
   */
  status = GPIBread(self->gpib, self->ecbDeviceID, &response, count);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }
  if (response != ACKN) {
    self->lastError = ECBILLEGALFUNC;
    return 0;
  }
  return 1;
}

/*-----------------------------------------------------------------------*/
int ecbExecute(pECB self, int func, Z80_reg in, Z80_reg * out)
{
  int count, status;

  assert(self != NULL);
  assert(self->gpib != NULL);
  self->lastError = 0;

  /*
     send function code
   */
  status = ecbSendFunc(self, func);
  if (status <= 0) {
    return status;
  }

  /*
     send input register 
   */
  count = 4;
  status = GPIBsend(self->gpib, self->ecbDeviceID, &in, count);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  /*
     read result register
   */
  status = GPIBread(self->gpib, self->ecbDeviceID, out, count);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  return 1;
}

/*----------------------------------------------------------------------*/
static int ecbPrepareIO(pECB self, int func, unsigned short address,
                        unsigned short byteCount)
{
  Swap save, adr, count;
  int status, bytes;

  if (byteCount >= ECB_BYTES) {
    self->lastError = ECBOVERFLOW;
    return 0;
  }

  /*
     Swap address and byteCount?? This may be a portability issue!
     This may not be necessary on some platforms
   */
  save.word = address;          /* Swap address bytes */
  adr.b.lsb = save.b.msb;
  adr.b.msb = save.b.lsb;
  save.word = byteCount;        /* Swap byte count bytes */
  count.b.lsb = save.b.msb;
  count.b.msb = save.b.lsb;

  status = ecbSendFunc(self, func);
  if (status <= 0) {
    return status;
  }

  /*
     send address 
   */
  bytes = 2;
  status = GPIBsend(self->gpib, self->ecbDeviceID, &adr, bytes);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  /*
     send byte count
   */
  status = GPIBsend(self->gpib, self->ecbDeviceID, &count, bytes);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  return 1;
}

/*-----------------------------------------------------------------------*/
int ecbRead(pECB self, unsigned short address, void *buffer, int byteCount)
{

  int status, count;

  assert(self != NULL);
  assert(self->gpib != NULL);
  self->lastError = 0;

  status =
      ecbPrepareIO(self, READ_BYTES, address, (unsigned short) byteCount);
  if (status <= 0) {
    return 0;
  }

  /*
     actual read
   */
  status = GPIBread(self->gpib, self->ecbDeviceID, buffer, byteCount);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  return 1;
}

/*----------------------------------------------------------------------*/
int ecbDMARead(pECB self, unsigned short address, void *buffer,
               unsigned short byteCount)
{
  int status, count;

  assert(self != NULL);
  assert(self->gpib != NULL);
  self->lastError = 0;

  status =
      ecbPrepareIO(self, DMAREAD, address, (unsigned short) byteCount);
  if (status <= 0) {
    return 0;
  }

  usleep(20 * 1000);

  /*
     actual read
   */
  status = GPIBread(self->gpib, self->ecbDeviceID, buffer, byteCount);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  return 1;
}

/*----------------------------------------------------------------------*/
int ecbWrite(pECB self, unsigned short address,
             void *buffer, int byteCount)
{

  int status, count;

  assert(self != NULL);
  assert(self->gpib != NULL);
  self->lastError = 0;

  status =
      ecbPrepareIO(self, WRITE_BYTES, address, (unsigned short) byteCount);
  if (status <= 0) {
    return 0;
  }

  /*
     actual read
   */
  status = GPIBsend(self->gpib, self->ecbDeviceID, buffer, byteCount);
  if (status < 0) {
    self->lastError = status;
    return 0;
  }

  return 1;
}

/*-----------------------------------------------------------------------*/
void ecbErrorDescription(pECB self, char *buffer, int maxBuffer)
{
  int positive;

  switch (self->lastError) {
  case ECBILLEGALFUNC:
    strlcpy(buffer, "Illegal ECB function called", maxBuffer);
    return;
  case ECBOVERFLOW:
    strlcpy(buffer,
            "You tried to copy more then 64K onto the poor ECB, REFUSED!",
            maxBuffer);
    return;
  }

  /*
     GPIB error codes
   */
  GPIBerrorDescription(self->gpib, self->lastError, buffer, maxBuffer);
}

/*----------------------------------------------------------------------*/
void ecbClear(pECB self)
{
  GPIBclear(self->gpib, self->ecbDeviceID);
}

/*-----------------------------------------------------------------------*/
int fixECBError(pECB self)
{
  int pos;

  switch (self->lastError) {
  case ECBILLEGALFUNC:
  case ECBOVERFLOW:
    return HWFault;
  }

  /*
     GPIB error
   */
  pos = -self->lastError;
  switch (pos) {
  case GPIBEABO:
    return HWRedo;
  default:
    return HWFault;
  }
}

/*------------------------------------------------------------------------*/
int ECBAction(SConnection * pCon, SicsInterp * pSics, void *pData,
              int argc, char *argv[])
{
  pECB self = (pECB) pData;
  Z80_reg in, out;
  char pBuffer[80], pError[132];
  int status, iVal, func = 0;

  assert(self != NULL);

  /*
     Only managers will be allowed to wrestle directly with ECB
     controllers.
   */
  if (!SCinMacro(pCon)) {
    if (!SCMatchRights(pCon, usMugger)) {
      return 0;
    }
  }

  if (argc < 2) {
    SCWrite(pCon, "ERROR: keyword required for ECB", eError);
    return 0;
  }

  strtolower(argv[1]);
  if (strcmp(argv[1], "func") == 0) {
    if (argc < 7) {
      SCWrite(pCon,
              "ERROR: require function code and four register values",
              eError);
      return 0;
    }
    status = Tcl_GetInt(pSics->pTcl, argv[2], &func);
    if (status != TCL_OK) {
      SCWrite(pCon, "ERROR: failed to convert argument to int", eError);
      return 0;
    }
    status = Tcl_GetInt(pSics->pTcl, argv[3], &iVal);
    if (status != TCL_OK) {
      SCWrite(pCon, "ERROR: failed to convert argument to int", eError);
      return 0;
    }
    in.d = (unsigned char) iVal;
    status = Tcl_GetInt(pSics->pTcl, argv[4], &iVal);
    if (status != TCL_OK) {
      SCWrite(pCon, "ERROR: failed to convert argument to int", eError);
      return 0;
    }
    in.e = (unsigned char) iVal;
    status = Tcl_GetInt(pSics->pTcl, argv[5], &iVal);
    if (status != TCL_OK) {
      SCWrite(pCon, "ERROR: failed to convert argument to int", eError);
      return 0;
    }
    in.b = (unsigned char) iVal;
    status = Tcl_GetInt(pSics->pTcl, argv[6], &iVal);
    if (status != TCL_OK) {
      SCWrite(pCon, "ERROR: failed to convert argument to int", eError);
      return 0;
    }
    in.c = (unsigned char) iVal;

    status = ecbExecute(self, func, in, &out);
    if (status != 1) {
      ecbErrorDescription(self, pBuffer, 79);
      sprintf(pError, "ERROR: %s", pBuffer);
      SCWrite(pCon, pError, eError);
      return 0;
    }
    sprintf(pBuffer, "%d %d %d %d", out.d, out.e, out.b, out.c);
    SCWrite(pCon, pBuffer, eValue);
    return 1;
  } else if (strcmp(argv[1], "clear") == 0) {
    ecbClear(self);
    SCSendOK(pCon);
    return 1;
  } else if (strcmp(argv[1], "toint") == 0) {
    sprintf(pBuffer, "%d", argv[2][0]);
    SCWrite(pCon, pBuffer, eValue);
    return 1;
  } else {
    SCWrite(pCon, "ERROR: ECB does not understand keyword", eError);
    return 0;
  }
}

/*---------------------------------------------------------------------*/
int ecbAssignEncoder(pECB self, int encoder, int motorNumber)
{

  if (encoder <= 0 || encoder > 3) {
    return 0;
  }

  self->encoder[encoder - 1] = motorNumber;
  self->encoderDirty = 1;
  return 1;
}

/*----------------------------------------------------------------------*/
int ecbLoadEncoder(pECB self)
{
  Z80_reg in, out;
  int status;

  if (self->encoderDirty != 1) {
    /*
       no need to do it if no change 
     */
    return 1;
  }

  if (self->encoder[0] != 0) {
    in.d = self->encoder[0];
  } else {
    in.d = 0;
  }
  if (self->encoder[1] != 0) {
    in.e = self->encoder[1];
  } else {
    in.e = 0;
  }
  if (self->encoder[2] != 0) {
    in.b = self->encoder[2];
  } else {
    in.b = 0;
  }
  in.c = 1;

  status = ecbExecute(self, 152, in, &out);
  return status;
}

/*-----------------------------------------------------------------------*/
void ECBKill(void *pData)
{
  pECB self = (pECB) pData;

  if (self == NULL) {
    return;
  }

  /*
     Detaching here may be dangerous: If the GPIB has been deleted first, 
     this makes a core dump. Best is the GPIB keeps a list of attached
     things and cleans them itself.

     GPIBdetach(self->gpib,self->ecbDeviceID);
   */
  if (self->pDes) {
    DeleteDescriptor(self->pDes);
  }
  free(self);
}

/*----------------------------------------------------------------------
MakeECB name gpibcontroller boardNo gpib-address
  -----------------------------------------------------------------------*/
int MakeECB(SConnection * pCon, SicsInterp * pSics, void *pData,
            int argc, char *argv[])
{
  pECB self = NULL;
  int address, status, boardNo;
  pGPIB gpib = NULL;
  char pError[132];

  /*
     we need a name, the GPIB controller and an address on the GPIB bus for 
     the ECB  as arguments
   */
  if (argc < 5) {
    SCWrite(pCon, "ERROR: insufficient arguments to MakeECB", eError);
    return 0;
  }
  gpib = FindCommandData(pSics, argv[2], "GPIB");
  if (gpib == NULL) {
    sprintf(pError, "ERROR: no GPIB controller %s found", argv[2]);
    SCWrite(pCon, pError, eError);
    return 0;
  }
  status = Tcl_GetInt(pSics->pTcl, argv[3], &boardNo);
  if (status != TCL_OK) {
    sprintf(pError, "ERROR: failed to convert %s to integer", argv[3]);
    SCWrite(pCon, pError, eError);
    return 0;
  }
  status = Tcl_GetInt(pSics->pTcl, argv[4], &address);
  if (status != TCL_OK) {
    sprintf(pError, "ERROR: failed to convert %s to integer", argv[4]);
    SCWrite(pCon, pError, eError);
    return 0;
  }
  if (address < 0 || address > 30) {
    SCWrite(pCon, "ERROR: invalid GPIB address specified", eError);
    return 0;
  }

  self = (pECB) malloc(sizeof(ECB));
  if (self == NULL) {
    SCWrite(pCon, "ERROR: no memory to allocate ECB", eError);
    return 0;
  }
  memset(self, 0, sizeof(ECB));
  self->pDes = CreateDescriptor("ECB");
  if (self->pDes == NULL) {
    SCWrite(pCon, "ERROR: no memory to allocate ECB", eError);
    return 0;
  }
  self->gpib = gpib;
  self->boardNumber = boardNo;
  self->ecbAddress = address;
  self->ecbDeviceID = GPIBattach(self->gpib, self->boardNumber,
                                 self->ecbAddress, 0, 13, 0, 1);
  if (self->ecbDeviceID <= 0) {
    SCWrite(pCon, "ERROR: failed to initialize ECB connection", eError);
    ECBKill(self);
    return 0;
  }
  AddCommand(pSics, argv[1], ECBAction, ECBKill, self);
  return 1;
}
