/* Atari ST async I/O routines, used by "attach asy ..." */
/* written by Rob Janssen, PE1CHL */

#include <stdio.h>
#include <time.h>
#ifndef __TURBOC__
#include <osbind.h>			/* os interface defines */
# ifdef MWC
#  undef Rsconf
#  define Rsconf(a,b,c,d,e,f)	(unsigned long) xbios(15,a,b,c,d,e,f)
# endif
#endif

#ifndef Bconmap
# define Bconmap(dev)	(unsigned) xbios(44,dev) /* TT Xbios Bconmap() function */
#endif

#include "global.h"
#include "mbuf.h"
#include "internet.h"
#include "iface.h"
#include "asy.h"
#include "atari.h"

struct asy asy[ASY_MAX];
unsigned nasy;

/* Interface list header */
extern struct interface *ifaces;

static int asy_setbuf();

/* Initialize asynch port "dev" */
int
asy_init(dev,arg1,arg2,bufsize)
int16 dev;
char *arg1,*arg2;
unsigned bufsize;
{
	register struct asy *ap;
	unsigned tbufsize;
	unsigned oldmap;
	unsigned long rsconf;

#ifdef DEBUG
	printf("asy_init(%d,%s,%s,%d)\n",dev,arg1,arg2,bufsize); fflush(stdout);
#endif
	ap = &asy[dev];
	ap->addr = atoi(arg1);			/* device number */
	ap->vec = htoi(arg2);			/* used for mode flags */

	if (ap->addr == 0 || ap->addr == 2)	/* backward compatability... */
		ap->addr++;			/* (old devs 0,2 are now 1,3) */

	if (ap->addr == 4 || ap->addr == 5 || ap->addr > 15) {
		printf("asy%d: Bad address %d\n",dev,ap->addr);
		return -1;
	}

#ifdef DEBUG
	printf("asy_init: ap->addr=%u, ap->vec=%u\n",ap->addr,ap->vec); fflush(stdout);
#endif
	/* devno's above 5 need Bconmap() to be configured */

	if (ap->addr >= MODEM1) {
		if ((oldmap = Bconmap(ap->addr)) >= 44) {
			printf("Warning: cannot configure asy dev %d\n",ap->addr);
			return 0;
		}
	}

	/* determine buffering.	 everybody has receive buffer, but a transmit */
	/* buffer is NOT used for MIDI and RS232-direct */

	tbufsize = 0;				/* default no tx buffer */

	switch (ap->addr)
	{
	case RS232:
		if (ap->vec & 1)		/* direct buffer access? */
			goto no_txbuf;

	default:
		if ((tbufsize = bufsize) > 1024) /* set tx buffer */
			tbufsize = 1024;

	case MIDI:
	no_txbuf:
		if (asy_setbuf(ap,bufsize,tbufsize)) {
			if (ap->addr >= MODEM1)
				Bconmap(oldmap);

			return -1;
		}
		break;
	}

	if (ap->addr != MIDI) {
		rsconf = Rsconf(-1,-1,-1,-1,-1,-1); /* read present config */
#ifdef DEBUG
		printf("asy_init: rsconf was %08lx\n",rsconf); fflush(stdout);
#endif

		/* setup for no flow, 8 bits, no parity, leave rest as-is */

		Rsconf(-1,0,(int) ((rsconf>>24) & 0x81) | 0x08,-1,(int) (rsconf>>8) & 0x77,-1);
	}

	if (ap->addr >= MODEM1)
		Bconmap(oldmap);

	return 0;
}

static int
asy_setbuf (ap,rbufsize,tbufsize)
register struct asy *ap;
unsigned rbufsize,tbufsize;

{
	register struct iorec *ip;
	char *rbufp,*tbufp;
	char i_state;

#ifdef DEBUG
	printf("asy_setbuf: addr=%u rbufsize=%u tbufsize=%u\n",ap->addr,rbufsize,tbufsize); fflush(stdout);
#endif
	if (ap->addr == MIDI)
		tbufsize = 0;			/* MIDI can't have tx buf */

	if ((rbufp = malloc(rbufsize)) == NULLCHAR){
		printf("asy %d: No space for rx buffer\n",ap->addr);
		return -1;
	}

	if (tbufsize != 0){
		if ((tbufp = malloc(tbufsize)) == NULLCHAR){
			free(rbufp);
			printf("asy %d: No space for tx buffer\n",ap->addr);
			return -1;
		}
	}

	ip = (struct iorec *) Iorec((ap->addr == MIDI)? 2 : 0);

	ap->in = ip;
	ap->out = ip + 1;

	i_state = disable();

	/* Save original IOREC values */
	memcpy(&ap->oldin,ap->in,sizeof(struct iorec));
	if (ap->addr != MIDI)
		memcpy(&ap->oldout,ap->out,sizeof(struct iorec));

	/* Set up receiver FIFO */
	ap->in->ibuf = rbufp;
	ap->in->ibufsiz = rbufsize;
	ap->in->ibufhd = ap->in->ibuftl = 0;
	ap->in->ibuflow = 0;
	ap->in->ibufhi = rbufsize;

	if (tbufsize != 0) {
		/* Set up transmitter FIFO */
		ap->out->ibuf = tbufp;
		ap->out->ibufsiz = tbufsize;
		ap->out->ibufhd = ap->out->ibuftl = 0;
		ap->out->ibuflow = 0;
		ap->out->ibufhi = tbufsize;
	}

	restore(i_state);

	return 0;
}

void
asy_stop(iface)
struct interface *iface;
{
	register struct asy *ap;
	char i_state;

#ifdef DEBUG
	printf("asy_stop(0x%lx), dev=%d\n",iface,iface->dev);
	fflush(stdout);
#endif

	ap = &asy[iface->dev];

	if (ap->in == NULL)		/* no Iorec setup done? */
		return;

	if (ap->addr != MIDI)
		while (ap->out->ibufhd != ap->out->ibuftl)
			;		/* wait until all transmitted */

	/* Restore old iorecs */

	i_state = disable();

	free(ap->in->ibuf);		/* free the buffer */
	memcpy(ap->in,&ap->oldin,sizeof(struct iorec));

	if (ap->addr != MIDI)
	{
#if 0
		free(ap->out->ibuf);	/* transmit buffer too */
#endif
		memcpy(ap->out,&ap->oldout,sizeof(struct iorec));
	}

	restore(i_state);
}

/* Asynchronous line I/O control */
asy_ioctl(interface,argc,argv)
struct interface *interface;
int argc;
char *argv[];
{
	if(argc < 1){
		printf("%d\r\n",asy[interface->dev].speed);
		return 0;
	}
	return asy_speed(interface->dev,atoi(argv[0]));
}

/* Set asynch line speed */
int
asy_speed(dev,speed)
int dev;
unsigned int speed;
{
	register struct asy *ap;
	register int sp;
	unsigned oldmap;
	long sav_ssp;

#ifdef DEBUG
	printf("asy_speed(%d,%u)\n",dev,speed); fflush(stdout);
#endif

	if(speed == 0 || dev >= nasy)
		return -1;

	ap = &asy[dev];

	switch (ap->addr)
	{
	case MIDI:
		/* midi can run on 500000 or 614400Hz clock (hardware mod) */
		switch (speed)
		{
		case 0x9600:		/* = 38400 unsigned... */
		case 31250:
		case 16:		/* divide-by-16 */
			sp = 0x95;
			break;
		case 9600:
		case 7812:
		case 64:		/* divide-by-64 */
			sp = 0x96;
			break;
		default:
			printf("asy: bad speed on MIDI\n");
			return -1;
		}

		sav_ssp = Super(NULL);		/* access chip */
		*((char *) 0xfffffc04L) = 0x03; /* RESET it */
		*((char *) 0xfffffc04L) = sp;	/* set new program */
		Super(sav_ssp);
		break;

	default:
		switch (speed)
		{
		case 19200:  sp = 0; break;
		case  9600:  sp = 1; break;
		case  4800:  sp = 2; break;
		case  2400:  sp = 4; break;
		case  1200:  sp = 7; break;
		case   600:  sp = 8; break;
		case   300:  sp = 9; break;
		default:
			if (speed > 32) {
				printf("asy: bad speed on RS232\n");
				return -1;
			}
			sp = speed;
			break;
		}

		/* devno's above 5 need Bconmap() to be configured */

		if (ap->addr >= MODEM1) {
			if ((oldmap = Bconmap(ap->addr)) >= 44) {
				printf("Warning: cannot set speed for asy dev %d\n",ap->addr);
				return 0;
			}
		}

		Rsconf(sp,-1,-1,-1,-1,-1);	/* set new speed */

		if (ap->addr >= MODEM1)
			Bconmap(oldmap);

		break;
	}

	ap->speed = speed;

	return 0;
}

/* Send a buffer to serial transmitter */
asy_output(dev,buf,cnt)
unsigned dev;
char *buf;
unsigned short cnt;
{
	register struct asy *ap;
	register struct iorec *ip;
	long sav_ssp;

#ifdef XDEBUG
	int i;
	printf("asy_output(%d,0x%lx,%d) data=",dev,buf,cnt);
	for (i = 0; i < cnt; i++)
		printf("%02x ",buf[i] & 0xff);
	printf("\n");
	fflush(stdout);
#endif

	if (dev >= nasy || cnt == 0)	/* these "cannot happen" */
		return;

	ap = &asy[dev];

	switch (ap->addr)
	{
	case MIDI:
		Midiws(cnt - 1,buf);	/* write MIDI (unbuffered...) */
		break;

	case RS232:
		if (ap->vec & 1) {
			ip = ap->out;

			if(ip->ibufhd != ip->ibuftl)
				return; /* Already busy */

			/* fake the output record to transmit data */
			ip->ibuf = buf;
			ip->ibufsiz = cnt;
			ip->ibufhd = 0;
			ip->ibuftl = cnt - 1;
			ip->ibuflow = 0;
			ip->ibufhi = cnt + 1;

			sav_ssp = Super(NULL); /* supervisor mode to access chip */

			/* kick the chip to start interrupts */
			*((char *) (0xfffffa01L+46)) = ip->ibuf[0];

			Super(sav_ssp); /* back to user mode */
			break;
		}

	default:
		do {
			Bconout(ap->addr,*buf++);
		}
		while (--cnt);

		break;
	}
}

/* Receive characters from asynch line
 * Returns count of characters read
 */
int16
asy_recv(dev,buf,cnt)
int dev;
register char *buf;
unsigned cnt;
{
	unsigned tot;
	register struct asy *ap;
	register struct iorec *ip;
#ifdef XDEBUG
	int i;
	char *b = buf;
#endif

	ap = &asy[dev];
	tot = 0;

	/* Read from serial I/O input buffer */

	if (ap->addr == RS232 && (ap->vec & 1)) {
		ip = ap->in;

		while (cnt--) {
			if(ip->ibufhd == ip->ibuftl)
				break;

			if (++(ip->ibufhd) >= ip->ibufsiz)
				ip->ibufhd = 0;

			*buf++ = ip->ibuf[ip->ibufhd];
			tot++;
		}
	} else {
		while (cnt--) {
			if (Bconstat(ap->addr) >= 0)
				break;

			*buf++ = Bconin(ap->addr);
			tot++;
		}
	}

#ifdef XDEBUG
	if (tot){
		printf("asy_recv returns %d, data=",tot);
		for (i = 0; i < tot; i++)
			printf("%02x ",b[i] & 0xff);
		printf("\n");
		fflush(stdout);
	}
#endif

	return tot;
}

int
stxrdy(dev)
int dev;
{
	register struct asy *ap;

	if (dev >= nasy)
		return 0;

	ap = &asy[dev];

	switch (ap->addr)
	{
	case MIDI:
		return 1;		/* always ready */

	case RS232:
		if (ap->vec & 1)
			return (ap->out->ibufhd == ap->out->ibuftl);

	default:
#ifdef DEBUG
		printf("stxrdy: Bcostat(%d) = %ld\n",ap->addr,Bcostat(ap->addr)); fflush(stdout);
#endif
		return (Bcostat(ap->addr) != 0);
	}

	return 0;
}

