/**
 * PdaLink -- Connect Palm with Amiga
 *
 * Serial interface
 *
 * (C) 1998-2000 Richard Körber <rkoerber@gmx.de>
 *
 *------------------------------------------------------------------
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * You must not use this source code to gain profit of any kind!
 */

#include "pdalink_glob.h"

#include <devices/serial.h>
#include <devices/timer.h>



/*------------------------------------------------------**
** Name:        OpenPalmSerial                 protected
**
** Funktion:    Open a serial connection
**
** Parameter:   socket    Socket
**              device    Devicename
**              unit      Deviceunit
**              baud      Baudrate
** Ergebnis:    success
//>
** Bemerkungen:
**
** Revision:    31. Mai 1998, 13:40:02
*/
int OpenPalmSerial
(
  struct PL_Socket *sock,
  STRPTR device,
  ULONG  unit,
  ULONG  baud
)
{
  struct PL_Serial *ser;
  LONG err;

  err = PLERR_NOMEM;
  ser = (struct PL_Serial *)AllocVec(sizeof(struct PL_Serial),MEMF_PUBLIC|MEMF_CLEAR);
  if(ser)
  {
    if(ser->timerport = CreateMsgPort())          // Create timer message port
    {
      if(ser->timerio = (struct timerequest *)CreateIORequest(ser->timerport,sizeof(struct timerequest)))
      {
        err = PLERR_NORESOURCE;
        if(!OpenDevice("timer.device",UNIT_VBLANK,(struct IORequest *)ser->timerio,0L))
        {
          ser->timerbase = ser->timerio->tr_node.io_Device;
          err = PLERR_NOMEM;
          if(ser->serport = CreateMsgPort())      // create serial message port
          {
            if(ser->serio = (struct IOExtSer *)CreateIORequest(ser->serport,sizeof(struct IOExtSer)))
            {
              err = PLERR_NORESOURCE;
              ser->serio->io_SerFlags |= SERF_7WIRE;
//              ser->serio->io_SerFlags &= ~SERF_7WIRE;
              if(!OpenDevice(device,unit,(struct IORequest *)ser->serio,0L))
              {
                ser->serio->IOSer.io_Command = SDCMD_SETPARAMS;
                ser->serio->io_Baud          = baud;
                ser->serio->io_StopBits      = 1;
                ser->serio->io_ReadLen       = 8;
                ser->serio->io_WriteLen      = 8;
                ser->serio->io_SerFlags     |= SERF_RAD_BOOGIE|SERF_XDISABLED|SERF_7WIRE;
                ser->serio->io_ExtFlags      = 0;
                err = PLERR_SERIAL;
                if(  (!DoIO((struct IORequest *)ser->serio))
                   ||(ser->serio->IOSer.io_Error == SerErr_NoDSR))
                {
                  sock->serial = ser;
                  sock->lastError = PLERR_OKAY;
                  ser->currBaud = baud;
                  return(TRUE);
                }
                CloseDevice((struct IORequest *)ser->serio);
              }
              DeleteIORequest(ser->serio);
            }
            DeleteMsgPort(ser->serport);
          }
        }
        DeleteIORequest(ser->timerio);
      }
      DeleteMsgPort(ser->timerport);
    }
    FreeVec(ser);
  }
  sock->lastError = err;
  return(FALSE);
}
//<

/*------------------------------------------------------**
** Name:        ClosePalmSerial                protected
**
** Funktion:    Schließt die serielle Verbindung
**
** Parameter:   sock      Socket
//>
** Bemerkungen: Wird nur aufgerufen, wenn OpenPalmSerial()
**              _nicht_ fehlgeschlagen ist.
**
** Revision:    31. Mai 1998, 14:05:05
*/
void ClosePalmSerial
(
  struct PL_Socket *sock
)
{
  struct PL_Serial *ser = sock->serial;
  CloseDevice((struct IORequest *)ser->serio);
  DeleteIORequest(ser->serio);
  DeleteMsgPort(ser->serport);
  DeleteIORequest(ser->timerio);
  DeleteMsgPort(ser->timerport);
  FreeVec(ser);
}
//<

/*------------------------------------------------------**
** Name:        PL_RawSetRate                     public
**
** Funktion:    Stellt die Baudrate ein
**
** Parameter:   socket    Socket
**              baud      Baudrate
** Ergebnis:    success
//>
** Bemerkungen:
**
** Revision:     5. Juni 1998, 00:15:11
*/
__saveds __asm int PL_RawSetRate
(
  register __a0 APTR socket,
  register __d0 ULONG baud
)
{
  struct PL_Socket *sock = (struct PL_Socket *)socket;
  struct PL_Serial *ser = sock->serial;
  sock->lastError = PLERR_OKAY;

#ifdef SERDEBUG
    Printf("SER: Change baud to %ld\n",baud);
#endif

  if(ser->currBaud == baud) return(TRUE);         // has already been set
  ser->serio->IOSer.io_Command = SDCMD_SETPARAMS;
  ser->serio->io_Baud          = baud;
  if(  (!DoIO((struct IORequest *)ser->serio))
     ||(ser->serio->IOSer.io_Error == SerErr_NoDSR))
  {
    ser->currBaud = baud;
    Delay(10);                                    // take some time to settle
    return(TRUE);
  }

#ifdef SERDEBUG
  Printf("PL_RawSetRate Error %ld\n",ser->serio->IOSer.io_Error);
#endif
  sock->lastError = PLERR_SERIAL;
  return(FALSE);
}
//<

/*------------------------------------------------------**
** Name:        PL_RawRead                        public
**
** Funktion:    Liest Rohdaten vom Palm, wartet bis
**              genügend Daten vorhanden sind, oder bis
**              Timeout.
**
** Parameter:   socket    Socket
**              buffer    Datenpuffer
**              length    Länge des Puffers
** Ergebnis:    Gelesene Länge oder 0: Fehler
//>
** Bemerkungen:
**
** Revision:    31. Mai 1998, 16:28:12
*/
__saveds __asm LONG PL_RawRead
(
  register __a0 APTR socket,
  register __a1 APTR buffer,
  register __d0 LONG length
)
{
  struct PL_Socket *sock = (struct PL_Socket *)socket;
  struct PL_Serial *ser  = sock->serial;
  ULONG waitmask =   (1L<<ser->serport->mp_SigBit)
                   | (sock->abortmask);
  ULONG signals;

#ifdef SERDEBUG
  UWORD i;
#endif

  sock->lastError = PLERR_OKAY;

  ser->serio->IOSer.io_Command = CMD_READ;
  ser->serio->IOSer.io_Length  = length;
  ser->serio->IOSer.io_Data    = buffer;

  ser->timerio->tr_node.io_Command = TR_ADDREQUEST;
  ser->timerio->tr_time.tv_secs    = sock->timeout;
  ser->timerio->tr_time.tv_micro   = 0;

  if(sock->timeout)
  {
    SendIO((struct IORequest *)ser->timerio);     // start timer
    waitmask |= (1L<<ser->timerport->mp_SigBit);
  }
  SendIO((struct IORequest *)ser->serio);         // send command

  for(;;)
  {
    signals = Wait(waitmask);
    if(signals & sock->abortmask)
    {
      sock->lastError = PLERR_ABORTED;
      AbortIO((struct IORequest *)ser->serio);    // Abort serial command
      WaitIO((struct IORequest *)ser->serio);     // wait until abortion is acknowledged
      if(sock->timeout)
      {
        AbortIO((struct IORequest *)ser->timerio);  // stop timer
        WaitIO((struct IORequest *)ser->timerio);   // wait until timer is stopped
      }
      return 0;
    }

    if(CheckIO((struct IORequest *)ser->serio))   // request done?
    {
      WaitIO((struct IORequest *)ser->serio);     // Wait for request to arrive
      if(sock->timeout)
      {
        AbortIO((struct IORequest *)ser->timerio);  // abort timer
        WaitIO((struct IORequest *)ser->timerio);   // wait until timer is aborted
      }
#ifdef SERDEBUG
  for(i=0; i<length; i++)
    Printf("A<-P %02lx\n",((UBYTE *)buffer)[i]);
#endif
      if(ser->serio->IOSer.io_Error)              // Error?
      {
        if(ser->serio->IOSer.io_Error != SerErr_NoDSR)
        {
#ifdef SERDEBUG
  Printf("PL_RawRead Error %ld\n",ser->serio->IOSer.io_Error);
#endif
          sock->lastError = PLERR_SERIAL;
          return 0;                               // Nothing was read
        }
      }
      break;                                      // Leave loop
    }

    if(sock->timeout && CheckIO((struct IORequest *)ser->timerio)) // Timeout?
    {
      WaitIO((struct IORequest *)ser->timerio);   // wait until timer returns
      AbortIO((struct IORequest *)ser->serio);    // abort serial request
      WaitIO((struct IORequest *)ser->serio);     // wait until request is aborted
      sock->lastError = PLERR_TIMEOUT;            // Timeout
      return 0;                                   // failure (Timeout)
    }
  }

  return length;                                  // everything was read
}
//<

/*------------------------------------------------------**
** Name:        PL_RawWrite                       public
**
** Funktion:    Sendet Rohdaten zum Palm
**
** Parameter:   socket    Socket
**              buffer    Datenpuffer
**              length    Länge des Puffers
** Ergebnis:    Geschriebene Länge oder 0: Fehler
//>
** Bemerkungen:
**
** Revision:    31. Mai 1998, 16:35:59
*/
__saveds __asm LONG PL_RawWrite
(
  register __a0 APTR socket,
  register __a1 APTR buffer,
  register __d0 LONG length
)
{
  struct PL_Socket *sock = (struct PL_Socket *)socket;
  struct PL_Serial *ser  = sock->serial;
  ULONG waitmask =   (1L<<ser->serport->mp_SigBit)
                   | (sock->abortmask);
  ULONG signals;

#ifdef SERDEBUG
  UWORD i;
#endif

  sock->lastError = PLERR_OKAY;

  ser->serio->IOSer.io_Command = CMD_WRITE;
  ser->serio->IOSer.io_Length  = length;
  ser->serio->IOSer.io_Data    = buffer;

  ser->timerio->tr_node.io_Command = TR_ADDREQUEST;
  ser->timerio->tr_time.tv_secs    = sock->timeout;
  ser->timerio->tr_time.tv_micro   = 0;

  if(sock->timeout) {
    SendIO((struct IORequest *)ser->timerio);       // Start timer
    waitmask |= (1L<<ser->timerport->mp_SigBit);
  }
  SendIO((struct IORequest *)ser->serio);         // Start request

  for(;;)
  {
    signals = Wait(waitmask);

    if(signals & sock->abortmask)
    {
      sock->lastError = PLERR_ABORTED;
      AbortIO((struct IORequest *)ser->serio);    // stop serial request
      WaitIO((struct IORequest *)ser->serio);     // wait until stopped
      if(sock->timeout) {
        AbortIO((struct IORequest *)ser->timerio);  // stop timer
        WaitIO((struct IORequest *)ser->timerio);   // wait until timer is stopped
      }
      return 0;
    }

    if(CheckIO((struct IORequest *)ser->serio))   // request finished?
    {
      WaitIO((struct IORequest *)ser->serio);     // wait for the request to return
      if(sock->timeout) {
        AbortIO((struct IORequest *)ser->timerio);  // stop timer
        WaitIO((struct IORequest *)ser->timerio);   // wait until timer is stopped
      }
#ifdef SERDEBUG
  for(i=0; i<length; i++)
    Printf("A->P %02lx\n",((UBYTE *)buffer)[i]);
#endif
      if(ser->serio->IOSer.io_Error)              // Error?
      {
        if(ser->serio->IOSer.io_Error != SerErr_NoDSR)
        {
#ifdef SERDEBUG
  Printf("PL_RawWrite Error %ld\n",ser->serio->IOSer.io_Error);
#endif
          sock->lastError = PLERR_SERIAL;
          return 0;                               // Nothing was read
        }
      }
      break;                                      // break loop
    }

    if(sock->timeout && CheckIO((struct IORequest *)ser->timerio)) // Timeout?
    {
      WaitIO((struct IORequest *)ser->timerio);   // wait for the timer to return
      AbortIO((struct IORequest *)ser->serio);    // stop serial request
      WaitIO((struct IORequest *)ser->serio);     // wait for request to return
      sock->lastError = PLERR_TIMEOUT;            // timeout
      return 0;                                   // failure (Timeout)
    }
  }

  return length;                                  // Everything was read
}
//<

/*------------------------------------------------------**
** Name:        PL_RawFlush                       public
**
** Funktion:    Leert den Sende- und Empfangspuffer
**
** Parameter:   socket    Socket
** Ergebnis:    success
//>
** Bemerkungen:
**
** Revision:     9. September 1998, 11:20:26
*/
__saveds __asm int PL_RawFlush
(
  register __a0 APTR socket
)
{
  struct PL_Socket *sock = (struct PL_Socket *)socket;
  struct PL_Serial *ser  = sock->serial;
  sock->lastError = PLERR_OKAY;

  ser->serio->IOSer.io_Command = CMD_FLUSH;
  DoIO((struct IORequest *)ser->serio);           // Put command

  if(ser->serio->IOSer.io_Error)              // Error?
  {
    if(ser->serio->IOSer.io_Error != SerErr_NoDSR)
    {
#ifdef SERDEBUG
  Printf("PL_RawFlush Error %ld\n",ser->serio->IOSer.io_Error);
#endif
      sock->lastError = PLERR_SERIAL;
      return FALSE;
    }
  }

  return TRUE;
}
//<


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