/*-
 * Copyright (c) 1997 Causality Limited.
 * All rights reserved.
 *
 * This code was written by Mark Brinicombe
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Causality Limited.
 * 4. The name of Causality Limited may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CAUSALITY LIMITED ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUSALITY LIMITED BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* Modified by Deborah A. Wallach (kerr@pa.dec.com) 12/22/97 */

/*#define _POSIX_SOURCE 1*/

#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>

/*
 * Set the baudrate
 */

int serial_setbaudrate(int fd, int baudrate)
{
	struct termios serial_tio;

	tcgetattr(fd, &serial_tio);

	switch(baudrate) {
	case 115200:
	  cfsetispeed(&serial_tio, B115200);
	  cfsetospeed(&serial_tio, B115200);
	  break ;
	case 57600:
	  cfsetispeed(&serial_tio, B57600);
	  cfsetospeed(&serial_tio, B57600);
	  break ;
	case 38400:
	  cfsetispeed(&serial_tio, B38400);
	  cfsetospeed(&serial_tio, B38400);
	  break ;
	case 19200:
	  cfsetispeed(&serial_tio, B19200);
	  cfsetospeed(&serial_tio, B19200);
	  break ;
	case 9600:
	  cfsetispeed(&serial_tio, B9600);
	  cfsetospeed(&serial_tio, B9600);
	  break ;
	default:
	  fprintf(stderr, "Invalid baudrate %d\n", baudrate);
	  return(EINVAL);
	}
	
	tcflush(fd, TCIFLUSH);

	tcsetattr(fd, TCSANOW, &serial_tio);
	return(0);
}

/*
 * Setup of the communication port. Hope it will work
 */

int serial_set_tty(int fd, char *options)
{
	struct termios serial_tio;
	int baudrate;
	int databits;
	char parity;
	int stopbits;
	int loop;

	if (sscanf(options, "%d %d%c%d", &baudrate, &databits, &parity,
	    &stopbits) != 4) {
		fprintf(stderr, "Invalid serial options - %s\n", options);
		return(EINVAL);
	}

	switch (databits) {
	case 6:
		databits = CS6;
		break;
	case 7:
		databits = CS7;
		break;
	case 8:
		databits = CS8;
		break;
	default:
		fprintf(stderr, "Invalid data bits %d\n", databits);
		return(EINVAL);
		break;
	}

	switch (parity) {
	case 'N':
		parity = IGNPAR;
		break;
	default:
		fprintf(stderr, "Invalid parity %c\n", parity);
		return(EINVAL);
		break;
	}

	switch (stopbits) {
	case 1:
		stopbits = 0;
		break;
	case 2:
		stopbits = CSTOPB;
		break;
	default:
		fprintf(stderr, "Invalid stopbits %d\n", stopbits);
		return(EINVAL);
		break;
	}

	/* get the terminal attributes */
	if (tcgetattr(fd, &serial_tio) != 0) {
	        fprintf(stderr, "Couldn't get terminal attributes\n");
		return(EINVAL);
	}

	serial_tio.c_cc[VMIN] = 32;                 /* wake up after 32 chars */
	serial_tio.c_cc[VTIME] = 1;                 /* wake up 0.1 seconds after 1st char */

        serial_tio.c_iflag &= ~(BRKINT              /* Ignore break       */
		       | IGNPAR | PARMRK |          /* Ignore parity      */
		       INPCK |                      /* Ignore parity      */
		       ISTRIP |                     /* Don't mask         */
		       INLCR | IGNCR | ICRNL        /* No <cr> or <lf>    */
		       | IXON);                     /* Ignore STOP char   */

	serial_tio.c_iflag |= IGNBRK | IXOFF ;

	serial_tio.c_oflag &= ~(OPOST);             /* No output flags     */

	serial_tio.c_lflag &= ~(                    /* No local flags.  In */
		       ECHO|ECHOE|ECHOK|ECHONL|     /* particular, no echo */
		       ICANON |                     /* no canonical input  */
		                                    /* processing,         */
		       ISIG |                       /* no signals,         */
		       NOFLSH |                     /* no queue flush,     */
		       TOSTOP);                     /* and no job control. */
	
	serial_tio.c_cflag &= (                     /* Clear out old bits  */
		      CSIZE |                       /* Character size      */
		      CSTOPB |                      /* 2 Stop bits         */
		      HUPCL |                       /* Hangup on last close*/
		      PARENB);                      /* Parity              */


	serial_tio.c_cflag |= CLOCAL | CREAD | databits | stopbits ;

	tcsetattr(fd, TCSANOW, &serial_tio);

	/* set up the baud rate in struct termios serial_tio */
	return (serial_setbaudrate(fd, baudrate)) ;
}

int serial_open(char *name, char *options)
{
	int fd;

	fd = open(name, O_RDWR | O_NOCTTY);
	if (fd == -1) {
		fprintf(stderr, "Unable to open %s\n", name);
		return(-1);
	}
	if (serial_set_tty(fd, options))
		return(-1);
	return(fd);
}


int serial_close(int fd)
{
	close(fd);
	return(0);
}


int serial_putc(int fd, int byte)
{
	unsigned char data;

	data = byte;
	return(write(fd, &data, 1));
}

int serial_puts(int fd, int count, char *buf)
{
        int status ;

	status = write(fd, buf, count) ;
	if (status == count) 
	  tcdrain(fd) ;
	return(status);
}

int serial_getc(int fd)
{
	unsigned char data;

	if (read(fd, &data, 1) == 1)
		return(data);
	return(-1);
}

#if 0
int serial_getc_timeout(int fd, int timeout)
{
	fd_set fdset;
	int fdsetsize;
	struct timeval timeval;
	int ready;

	fdsetsize = getdtablesize();
	if (fdsetsize > FD_SETSIZE)
		fdsetsize = FD_SETSIZE;

	FD_ZERO(&fdset);
	FD_SET(fd, &fdset);

	timeval.tv_sec = timeout;
	timeval.tv_usec = 0;
	
	ready = select(fdsetsize, &fdset, NULL, NULL,
	    &timeval);

	return(serial_getc(fd));
}
#endif

int serial_gets(int fd, unsigned char *buf, int count)
{
  int saw = 0;
  saw = read(fd, buf, count);
  return(saw);
}

int f_serial_gets(int fd, unsigned char *buf, int count)
{
  static FILE *file = 0;
  int saw = 0;

  if(!file) {
    file = fdopen(fd, O_RDWR | O_NOCTTY);
    if (!file) { 
      fprintf(stderr, "fdopen failed\n");
      exit(0);
    }
  }
  saw = fread(buf, count, count, file);
  return(saw);
}

/* 
/* Because the sa1100-eval board does not do flow control, the
 * host side ends up occasionally losing the later parts of some
 * acknowledgement packets from the angel.  Since there are just
 * acknowledgements anyway, we pretend to have received the entire
 * packet when this happens.
 */
int serial_gets_timeout(int fd, unsigned char *buf, int count, int timeout)
{
	int fdsetsize;
	struct timeval timeval;
	int ready;
	fd_set fdset;

	fdsetsize = getdtablesize();
	if (fdsetsize > FD_SETSIZE)
		fdsetsize = FD_SETSIZE;

	FD_ZERO(&fdset);
	FD_SET(fd, &fdset);

	timeval.tv_sec = timeout;
	timeval.tv_usec = 0;
	
	while(1) {
	  ready = select(fdsetsize, &fdset, NULL, NULL,
			 &timeval);

	  if(ready) {
	    return(serial_gets(fd, buf, count));
	  }
	  else {
	    putchar('*');
	    /* hack!  XXX */
	    buf[0] = 0x1d;
	    return(1);
	  }
	}
}


