/* 
 * Copyright (C) 1995 Advanced RISC Machines Limited. All rights reserved.
 * 
 * This software may be freely used, copied, modified, and distributed
 * provided that the above copyright notice is preserved in all copies of the
 * software.
 */

/* -*-C-*-
 *
 * $Revision: 1.4 $
 *     $Date: 1999/01/28 03:50:16 $
 *
 *
 * serpardv.c - Serial/Parallel Driver for Angel.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crc.h"
#include "devices.h"
#include "buffers.h"
#include "rxtx.h"
#include "hostchan.h"
#include "params.h"
#include "logging.h"
#include "hsys.h"

#ifdef COMPILING_ON_WINDOWS
#  undef   ERROR
#  undef   IGNORE
#  include <windows.h>
#  include "angeldll.h"
#  include "comb_api.h"
#else
#  ifdef __hpux
#    define _TERMIOS_INCLUDED
#    include <sys/termio.h>
#    undef _TERMIOS_INCLUDED
#  else
#    include <termios.h>
#  endif
#  include "unixcomm.h"
#endif

#ifndef UNUSED
#  define UNUSED(x) (x = x)     /* Silence compiler warnings */
#endif

#define MAXREADSIZE     512
#define MAXWRITESIZE    512

#define SERPAR_FC_SET  ((1 << serial_XON) | (1 << serial_XOFF))
#define SERPAR_CTL_SET ((1 << serial_STX) | (1 << serial_ETX) | \
                        (1 << serial_ESC))
#define SERPAR_ESC_SET (SERPAR_FC_SET | SERPAR_CTL_SET)

static const struct re_config config = {
    serial_STX, serial_ETX, serial_ESC, /* self-explanatory?               */
    SERPAR_FC_SET,                      /* set of flow-control characters  */
    SERPAR_ESC_SET,                     /* set of characters to be escaped */
    NULL,                               /* serial_flow_control */
    NULL,                               /* what to do with FC chars */
    angel_DD_RxEng_BufferAlloc, NULL    /* how to get a buffer */
};

static struct re_state rxstate;

/*
 * structure used for manipulating transmit data
 */
typedef struct TxState
{
    struct te_state state;
    unsigned int    index;
    unsigned char   writebuf[MAXWRITESIZE];
} TxState;

/*
 * The set of parameter options supported by the device
 */
static unsigned int baud_options[] =
{
#ifdef __hpux
    115200, 57600,
#endif
    38400, 19200, 9600
};

static ParameterList param_list[] =
{
    {
        AP_BAUD_RATE,
        sizeof(baud_options) / sizeof(unsigned int),
        baud_options
    }
};

static const ParameterOptions serpar_options =
{
    sizeof(param_list) / sizeof(ParameterList),
    param_list
};

/*
 * The default parameter config for the device
 */
static Parameter param_default[] =
{
    { AP_BAUD_RATE, 9600 }
};

static const ParameterConfig serpar_defaults =
{
    sizeof(param_default)/sizeof(Parameter),
    param_default
};

/*
 * The user-modified options for the device
 */
static unsigned int user_baud_options[sizeof(baud_options) /
                                     sizeof(unsigned int)];

static ParameterList param_user_list[] =
{
    {
        AP_BAUD_RATE,
        sizeof(user_baud_options) / sizeof(unsigned),
        user_baud_options
    }
};

static ParameterOptions user_options =
{
    sizeof(param_user_list) / sizeof(ParameterList),
    param_user_list
};

static bool user_options_set;

/* forward declarations */
static int serpar_reset(void);
static int serpar_set_params(const ParameterConfig *config);
static int SerparMatch(const char *name, const char *arg);

static void process_baud_rate(unsigned int target_baud_rate)
{
    const ParameterList *full_list;
    ParameterList       *user_list;

    /* create subset of full options */
    full_list = Angel_FindParamList(&serpar_options, AP_BAUD_RATE);
    user_list = Angel_FindParamList(&user_options,   AP_BAUD_RATE);

    if (full_list != NULL && user_list != NULL)
    {
        unsigned int i, j;
        unsigned int def_baud = 0;

        /* find lower or equal to */
        for (i = 0; i < full_list->num_options; ++i)
           if (target_baud_rate >= full_list->option[i])
           {
               /* copy remaining */
               for (j = 0; j < (full_list->num_options - i); ++j)
                  user_list->option[j] = full_list->option[i+j];
               user_list->num_options = j;

               /* check this is not the default */
               Angel_FindParam(AP_BAUD_RATE, &serpar_defaults, &def_baud);
               if ((j == 1) && (user_list->option[0] == def_baud))
               {
#ifdef DEBUG
                   printf("user selected default\n");
#endif
               }
               else
               {
                   user_options_set = TRUE;
#ifdef DEBUG
                   printf("user options are: ");
                   for (j = 0; j < user_list->num_options; ++j)
                      printf("%u ", user_list->option[j]);
                   printf("\n");
#endif
               }

               break;   /* out of i loop */
           }

#ifdef DEBUG
        if (i >= full_list->num_options)
           printf("couldn't match baud rate %u\n", target_baud_rate);
#endif
    }
#ifdef DEBUG
    else
       printf("failed to find lists\n");
#endif
}

static int SerparOpen(const char *name, const char *arg)
{
    char *sername = NULL;
    char *parname = NULL;

#ifdef DEBUG
    printf("SerparOpen: name %s arg %s\n", name, arg ? arg : "<NULL>");
#endif

#ifdef COMPILING_ON_WINDOWS
    if (IsOpenSerial() || IsOpenParallel()) return -1;
#else
    if (Unix_IsSerialInUse() || Unix_IsParallelInUse()) return -1;
#endif

#ifdef COMPILING_ON_WINDOWS
    if (SerparMatch(name, arg) == -1)
        return -1;
#else
    Unix_IsValidParallelDevice(name,&sername,&parname);
# ifdef DEBUG
    printf("translated %s to serial %s and parallel %s\n",
           name==0 ? "NULL" : name,
           sername==0 ? "NULL" : sername,
           parname==0 ? "NULL" : parname);
# endif
    if (sername==NULL || parname==NULL) return -1;
#endif

    user_options_set = FALSE;

    /* interpret and store the arguments */
    if (arg != NULL)
    {
        unsigned int target_baud_rate;

        target_baud_rate = (unsigned int)strtoul(arg, NULL, 10);

        if (target_baud_rate > 0)
        {
#ifdef DEBUG
            printf("user selected baud rate %u\n", target_baud_rate);
#endif
            process_baud_rate(target_baud_rate);
        }
#ifdef DEBUG
        else
            printf("could not understand baud rate %s\n", arg);
#endif
    }

#ifdef COMPILING_ON_WINDOWS
    {
        /*
         * The serial port number is in name[0] followed by
         * the parallel port number in name[1]
         */

        int sport = name[0] - '0';
        int pport = name[1] - '0';

        if (OpenParallel(pport) != COM_OK)
            return -1;

        if (OpenSerial(sport, FALSE) != COM_OK)
        {
            CloseParallel();
            return -1;
        }
    }
#else
    Unix_OpenParallel(parname);
    Unix_OpenSerial(sername);
#endif

    serpar_reset();

#if defined(__unix) || defined(__CYGWIN__)
    Unix_ioctlNonBlocking();
#endif

    Angel_RxEngineInit(&config, &rxstate);

    return 0;
}

#ifdef COMPILING_ON_WINDOWS
static int SerparMatch(const char *name, const char *arg)
{
    char sername[2];
    char parname[2];

    UNUSED(arg);

    sername[0] = name[0];
    parname[0] = name[1];
    sername[1] = parname[1] = 0;

    if (IsValidDevice(sername) == COM_DEVICENOTVALID ||
        IsValidDevice(parname) == COM_DEVICENOTVALID)
        return -1;
    else
        return 0;
}
#else
static int SerparMatch(const char *portstring, const char *arg)
{
    char *sername=NULL, *parname=NULL;
    UNUSED(arg);

    Unix_IsValidParallelDevice(portstring,&sername,&parname);

      /* Match failed if either sername or parname are still NULL */
    if (sername==NULL || parname==NULL) return -1;
    return 0;
}
#endif

static void SerparClose(void)
{
#ifdef COMPILING_ON_WINDOWS
    CloseParallel();
    CloseSerial();
#else
    Unix_CloseParallel();
    Unix_CloseSerial();
#endif
}

static int SerparRead(DriverCall *dc, bool block)
{
    static unsigned char readbuf[MAXREADSIZE];
    static int rbindex = 0;

    int nread;
    int read_errno;
    int c = 0;
    re_status restatus;
    int ret_code = -1;            /* assume bad packet or error */

    /*
     * we must not overflow buffer, and must start after
     * the existing data
     */
#ifdef COMPILING_ON_WINDOWS
    {
        BOOL dummy = FALSE;
        nread = BytesInRXBufferSerial();

        if (nread > MAXREADSIZE - rbindex)
            nread = MAXREADSIZE - rbindex;
        read_errno = ReadSerial(readbuf+rbindex, nread, &dummy);
        if (pfnProgressCallback != NULL && read_errno == COM_OK)
        {
            progressInfo.nRead += nread;
            (*pfnProgressCallback)(&progressInfo);
        }
    }
#else
    nread = Unix_ReadSerial(readbuf+rbindex, MAXREADSIZE-rbindex, block);
    read_errno = errno;
#endif

    if ((nread > 0) || (rbindex > 0))
    {
#ifdef DO_TRACE
        printf("[%d@%d] ", nread, rbindex);
#endif

        if (nread > 0)
            rbindex = rbindex + nread;

        do
        {
            restatus = Angel_RxEngine(readbuf[c], &(dc->dc_packet), &rxstate);

#ifdef DO_TRACE
            printf("<%02X ",readbuf[c]);
#endif
            c++;
        } while (c < rbindex &&
                 ((restatus == RS_IN_PKT) || (restatus == RS_WAIT_PKT)));

#ifdef DO_TRACE
        printf("\n");
#endif

        switch(restatus)
        {
          case RS_GOOD_PKT:
              ret_code = 1;
              /* fall through to: */

          case RS_BAD_PKT:
              /*
               * We now need to shuffle any left over data down to the
               * beginning of our private buffer ready to be used
               *for the next packet
               */
#ifdef DO_TRACE
              printf("SerparRead() processed %d, moving down %d\n",
                     c, rbindex - c);
#endif

              if (c != rbindex)
                  memmove((char *) readbuf, (char *) (readbuf + c), rbindex - c);

              rbindex -= c;

              break;

          case RS_IN_PKT:
          case RS_WAIT_PKT:
              rbindex = 0;            /* will have processed all we had */
              ret_code = 0;
              break;

          default:
#ifdef DEBUG
              printf("Bad re_status in SerparRead()\n");
#endif
              break;
        }
    }
    else if (nread == 0)
        /* nothing to read */
        ret_code = 0;
    else if (read_errno == ERRNO_FOR_BLOCKED_IO) /* nread < 0 */
        ret_code = 0;

#ifdef DEBUG
    if ((nread < 0) && (read_errno != ERRNO_FOR_BLOCKED_IO))
        perror("read() error in SerparRead()");
#endif

    return ret_code;
}

/*
 *  Function: send_packet
 *   Purpose: Send a stream of bytes to Angel through the parallel port
 *
 * Algorithm: We need to present the data in a form that all boards can
 *            swallow.  With the PID board, this is a problem: for reasons
 *            described in the driver (angel/pid/st16c552.c), data are
 *            sent a nybble at a time on D0-D2 and D4; D3 is wired to ACK,
 *            which generates an interrupt when it goes low.  This routine
 *            fills in an array of nybbles, with ACK clear in all but the
 *            last one.  If, for whatever reason, the write fails, then
 *            ACK is forced high (thereby enabling the next write a chance
 *            to be noticed when the falling edge of ACK generates an
 *            interrupt (hopefully).
 *
 *    Params:
 *       Input: txstate Contains the packet to be sent
 *
 *   Returns: Number of *complete* bytes written
 */

static int SerparWrite(DriverCall *dc)
{
    te_status status;
    int nwritten = 0;
    static TxState txstate;

    /*
     * is this a new packet?
     */
    if (dc->dc_context == NULL)
    {
        /*
         * yes - initialise TxEngine
         */
        Angel_TxEngineInit(&config, &dc->dc_packet, &txstate.state);

        txstate.index = 0;
        dc->dc_context = &txstate;
    }

    /*
     * fill the buffer using the Tx Engine
     */
    do
    {
        status = Angel_TxEngine(&dc->dc_packet, &txstate.state,
                                &txstate.writebuf[txstate.index]);
        if (status != TS_IDLE) txstate.index++;

    } while (status == TS_IN_PKT && txstate.index < MAXWRITESIZE);

#ifdef DO_TRACE
    {
        unsigned int i = 0;

        while (i < txstate.index)
        {
            printf(">%02X ", txstate.writebuf[i]);

            if (!(++i % 16))
                putc('\n', stdout);
        }

        if (i % 16)
            putc('\n', stdout);
    }
#endif

    /*
     * the data are ready, all we need now is to send them out
     * in a form that Angel can swallow.
     */
#ifdef COMPILING_ON_WINDOWS
  if (WriteParallel(txstate.writebuf, txstate.index) == COM_OK)
  {
    nwritten = txstate.index;
    if (pfnProgressCallback != NULL)
    {
      progressInfo.nWritten += nwritten;
      (*pfnProgressCallback)(&progressInfo);
    }
  }
  else
  {
      MessageBox(GetFocus(), "Write error\n", "Angel", MB_OK | MB_ICONSTOP);
      return -1;   /* SJ - This really needs to return a value, which is picked up in */
                   /*      DevSW_Read as meaning stop debugger but don't kill. */
  }
#else
    nwritten = Unix_WriteParallel(txstate.writebuf, txstate.index);
#endif

    if (nwritten < 0) nwritten = 0;

#ifdef DO_TRACE
    printf("SerparWrite: wrote %d out of %d bytes\n",
           nwritten, txstate.index);
#endif

    /*
     * has the whole packet gone?
     */
    if (nwritten == (int)txstate.index &&
        (status == TS_DONE_PKT || status == TS_IDLE))
        /*
         * yes it has
         */
        return 1;
    else
    {
        /*
         * if some data are left, shuffle them
         * to the start of the buffer
         */
        if (nwritten != (int)txstate.index && nwritten != 0)
        {
            txstate.index -= nwritten;
            (void)memmove((char *) txstate.writebuf,
                          (char *) (txstate.writebuf + nwritten),
                          txstate.index);
        }
        else if (nwritten == (int)txstate.index)
            txstate.index = 0;

        return 0;
    }
}

static int serpar_reset(void)
{
#ifdef COMPILING_ON_WINDOWS
    FlushParallel();
    FlushSerial();
#else
    Unix_ResetParallel();
    Unix_ResetSerial();
#endif

    return serpar_set_params(&serpar_defaults);
}

static int find_baud_rate(unsigned int *speed)
{
    static struct
    {
        unsigned int baud;
        int termiosValue;
    } possibleBaudRates[] =
      {
#if defined(__hpux)
          {115200, _B115200}, {57600, _B57600},
#endif
#ifdef COMPILING_ON_WINDOWS
        {38400, CBR_38400}, {19200, CBR_19200}, {9600, CBR_9600}, {0, 0}
#else
        {38400, B38400}, {19200, B19200}, {9600, B9600}, {0, 0}
#endif
    };
    unsigned int i;

    /* look for lower or matching -- will always terminate at 0 end marker */
    for (i = 0; possibleBaudRates[i].baud > *speed; ++i)
        /* do nothing */
        ;

    if (possibleBaudRates[i].baud > 0)
       *speed = possibleBaudRates[i].baud;

    return possibleBaudRates[i].termiosValue;
}

static int serpar_set_params(const ParameterConfig *config)
{
    unsigned int speed;
    int termios_value;

#ifdef DEBUG
    printf("serpar_set_params\n");
#endif

    if (!Angel_FindParam(AP_BAUD_RATE, config, &speed))
    {
#ifdef DEBUG
        printf("speed not found in config\n");
#endif
        return DE_OKAY;
    }

    termios_value = find_baud_rate(&speed);
    if (termios_value == 0)
    {
#ifdef DEBUG
        printf("speed not valid: %u\n", speed);
#endif
        return DE_OKAY;
    }

#ifdef DEBUG
    printf("setting speed to %u\n", speed);
#endif

#ifdef COMPILING_ON_WINDOWS
    SetBaudRate((WORD)termios_value);
#else
    Unix_SetSerialBaudRate(termios_value);
#endif

    return DE_OKAY;
}


static int serpar_get_user_params(ParameterOptions **p_options)
{
#ifdef DEBUG
    printf("serpar_get_user_params\n");
#endif

    if (user_options_set)
    {
        *p_options = &user_options;
    }
    else
    {
        *p_options = NULL;
    }

    return DE_OKAY;
}


static int serial_get_default_params( const ParameterConfig **p_config )
{
#ifdef DEBUG
    printf( "serial_get_default_params\n" );
#endif

    *p_config = &serpar_defaults;
    return DE_OKAY;
}


static int SerparIoctl(const int opcode, void *args)
{
    int ret_code;

#ifdef DEBUG
    printf("SerparIoctl: op %d arg %p\n", opcode, args ? args : "<NULL>");
#endif

    switch (opcode)
    {
       case DC_RESET:
           ret_code = serpar_reset();
           break;

       case DC_SET_PARAMS:
           ret_code = serpar_set_params((const ParameterConfig *)args);
           break;

       case DC_GET_USER_PARAMS:
           ret_code = serpar_get_user_params((ParameterOptions **)args);
           break;

       case DC_GET_DEFAULT_PARAMS:
           ret_code =
               serial_get_default_params((const ParameterConfig **)args);
           break;

       default:
           ret_code = DE_BAD_OP;
           break;
    }

  return ret_code;
}

DeviceDescr angel_SerparDevice =
{
    "SERPAR",
    SerparOpen,
    SerparMatch,
    SerparClose,
    SerparRead,
    SerparWrite,
    SerparIoctl
};

/* EOF serpardr.c */
