/*
 * mpu.c - Roland MPU401 MIDI device driver
 *
 * 386BSD/isa bus. Dumb UART mode.
 *
 * Overview:  the mpu is a board developed by Roland to allow
 *	a computer to read/write MIDI byte messages on a somewhat 
 *	serial (byte-sized) "bus" and as a result - to play music
 *	on external synthesizers.  This driver offers a simple
 *	"dumb uart" (non-interrupt) mode for reading and writing
 *	MIDI messages. 
 *
 * routines:
 *	mpuprobe
 *	mpuattach
 *	mpuopen
 *	mpuclose
 *	mpuread
 *	mpuwrite
 *	mpuioctl
 *	mpuselect
 *	mpuintr
 */

#include "mpu.h"
#if NMPU > 0
/*
 * mpu driver.
 */
#include "param.h"
#include "systm.h"
#include "ioctl.h"
#include "proc.h"
#include "user.h"
#include "conf.h"
#include "file.h"
#include "uio.h"
#include "kernel.h"
#include "syslog.h"

#include "i386/isa/isa_device.h"
#include "i386/isa/mpureg.h"

#define mpuminor(d)

int 	mpuprobe(), mpuattach(), mpuintr();

struct	isa_driver mpudriver = {
	mpuprobe, mpuattach, "mpu"
};

struct mpu {
	int dataport;
	int statport;
	int commport;
	int state;
	struct mqueue mq;
} mpusoftc[NMPU];

#define DATAPORT	mp->dataport
#define STATPORT	mp->statport
#define COMMPORT	mp->commport

#define TIMEOUTCYCLES   100000
#define ACKCYCLES       50000
#define MPURESET	0xff	/* hw reset of mpu */
#define MPUUARTMODE	0x3f	/* dumb uart mode */
#define MPUTHRUOFF	0x33	/* midi in bytes not echoed in hw to midi out */
#define MPUACK		0xfe    /* acknowledgement from MPU */

/* states
*/
#define MPUEXISTS	0x1
#define MPUOPEN		0x2

/*
 * midi input Q
 */
static void mqInit();
static int  mqGet();
static int  mqPut();

static int mpuReset();
static int mpuCommandAndAck();

/* turn debug printf's on/off
*/
int mpudebug=0;

mpuprobe(dev)
struct isa_device *dev;
{
	int unit;
	struct mpu *mp;

	unit = dev->id_unit;
#ifdef DEBUG
	printf("mpuprobe %lx unit: %d\n", dev->id_iobase, dev->id_unit);
#endif
	if (unit > NMPU)
		return(0);
	mp = &mpusoftc[unit];
	mp->state = 0;		/* no state yet */
	DATAPORT = dev->id_iobase;
	COMMPORT = STATPORT = (dev->id_iobase + 1);

	if (mpuReset(mp, 5)) {
		mp->state = MPUEXISTS;
		return(1);
	}
	return(0);
}


int
mpuattach(dev)
struct isa_device *dev;
{
	register struct mpu *mp;
	register int unit;

	unit = dev->id_unit;
	if (unit > NMPU) {
		return(0);
	}
	mp = &mpusoftc[unit];

	/* initialize the input queue
	*/
	mqInit(mp);

	return (1);
}

mpuopen(dev, flag, mode, p)
dev_t dev;
int flag;
int mode;
struct proc *p;
{
	register int unit;
	register struct mpu *mp;
	unsigned char input;

	unit = minor(dev);
	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];

	if (mp->state & MPUOPEN) {
		return (EBUSY);
	}
	mp->state |= MPUOPEN;

	mpuReset(mp, 5);
	mpuCommandAndAck(mp, MPUTHRUOFF);
	mpuCommandAndAck(mp, MPUUARTMODE);

	/* drain mpu.
	 */
	while ((inb(STATPORT) & 0x80) == 0 ) {
	 	input = inb(DATAPORT);
	}
	return(0);
}
 
mpuclose(dev, flag, mode, p)
dev_t dev;
int flag, mode;
struct proc *p;
{
	register int unit;
	register struct mpu *mp;

	if (mpudebug)
		printf("mpuclose\n");
	unit = minor(dev);
	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];
	mp->state = 0;
	mpuReset(mp, 1);
	return(0);
}
 
int
mpuread(dev_t dev, struct uio *uio, int flag)
{
	unsigned char c;
	int timeout;
	int error;
	int unit;
	struct mpu *mp;
	int count;

	unit = minor(dev);
	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];

	count = uio->uio_resid;
	while (count) {
		/* service input queue first
		*/
		if (mp->mq.cc) {
			c = (unsigned char) mqGet(mp);
			if (mpudebug)
				printf("mpuread: Q input %x\n", c);
			error = uiomove(&c, 1, uio);
			if (error)
				return(error);
		}
		else {
			/* try for one polled midi byte
			*/
			timeout = TIMEOUTCYCLES;
			while(timeout--) {
				/* if status indicates ready to read
				*/
				if ( (inb(STATPORT) & 0x80) == 0 ) {
					if (mpudebug) {
						printf("mpuread STATport %x\n", inb(STATPORT));
					}
					/* then read
					*/
					c = ((unsigned char) inb(DATAPORT)) & 0xff;
					if (mpudebug) {
						printf("mpuread: %x\n", (int)c);
					}

					/* copy to user space
					*/
					error = uiomove(&c, 1, uio);
					if (mpudebug && error)
						printf("mpuread: uiomove error %d\n", error);
					if (error) {
						return(error);
					}
					break;
				}
			} 
			if (timeout <= 0) {
				if (mpudebug)
					printf("mpuread: timeout\n");
				return(EIO);   
			}
		}
		count--;
	}
	return(0);
}
 
int
mpuwrite(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
	int timeout;
	int error;
	int unit;
	struct mpu *mp;
	int state;
	unsigned char c;

	unit = minor(dev);
	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];

	while(uio->uio_resid) {
		error = uiomove(&c, 1, uio);
		if (error)
			return(error);

		timeout = TIMEOUTCYCLES;
		/* while not timed out and mpu not ready
		*/
		while (timeout-- 
		    && ((state = inb(STATPORT)) & 0x40)) { 

			/* if statport not ready for output, check
			 * for input. That may hang us up. 
			 * input bytes put in circular queue for later
			 * reads.
			 */
			if ( ( state & 0x0080 ) == 0 ) {
				c = inb(DATAPORT);
				if (mpudebug) {
					printf("mpuwrite: read data %x\n", c);
				}
				mqPut(mp, c);
				/* start over again, we were trying to do
				 * output until we got distracted
				 */
				timeout = TIMEOUTCYCLES;
				continue;
			}
		}
		if (timeout <= 0) {
			return(EIO);  /* gag */
		}
		if (mpudebug)
			printf("mpuwrite %x\n", c);
		outb(DATAPORT,c&0xff);
	}
	return(0);
}

/*
 * currently nothing
 */
int
mpuioctl(dev, cmd, data, flag)
	dev_t dev;
	caddr_t data;
{
	return(0);
}

/*
 * at this point, only really support READ
 */
int
mpuselect(dev, flag)
dev_t dev;
int flag;
{
	struct mpu *mp;
	int unit;

	unit = minor(dev);
	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];

	if ( flag == FREAD ) {
		/* input Q, then device
		*/
		if ( mp->mq.cc ) {
			if (mpudebug)
				printf("mpu select Q T\n");
			return(1);
		}
		if ((inb(STATPORT) & 0x80) == 0 ) {
			if (mpudebug)
				printf("mpu select %x\n", inb(STATPORT));
			return(1);
		}
	}
	return(0);
}

int mpuintrcount=0;
int mpustate;
int mpudata;

/*
 * interrupts seem to be happening in 
 * UART mode. Now if I knew why, it would be nice.
 * They don't seem to want anything though.
 */
mpuintr(unit)
	register int unit;
{
	struct mpu *mp;

	if (unit >= NMPU) {
		return (ENXIO);
	}
	mp = &mpusoftc[unit];
	mpuintrcount++;

	mpustate = inb(STATPORT);
}

/*
 * try to reset the mpu
 */
static
mpuReset(mp, count)
struct mpu *mp; 
int count;
{
	int i;

	for ( i = 0; i < count; i++) {
		if (mpuCommandAndAck(mp, MPURESET));
			return(1);
	}
	return(0);
}

/* send command to mpu and wait for response.
 * This routine will read any amount of queued response.
 *
 * returns:
 *	-1: no ack.
 *	 0: unexpected char.
 *	 1: got ack.
 */
static
mpuCommandAndAck(mp, command)
struct mpu *mp;
int command;
{
	int i;
	unsigned char input;

	outb(COMMPORT, command);

	/* loop to soak up any ACK received from mpu.
	 * will timeout if ACK not received.
	 */
	for(i = 0; i < ACKCYCLES; i++) {
		while ((inb(STATPORT) & 0x80) == 0 ) {
		 	input = inb(DATAPORT);
			if ( input == (unsigned char)MPUACK ) {
				return(1);
			}
		}
	}
	return(0);
}

 
/* queue handling code for input music queue.
 * The queue exists because of mpu brain-damage -- we
 * must funnel bytes received while attempting to write over 
 * to the read side of the driver.
 */


static void
mqInit(mp)
struct mpu *mp;
{

	/*
	inq.ovflag = FALSE;
	inq.cc = 0;
	inq.qs = inq.cf = inq.ct = inqueue;
	inq.qe = inqueue + INQUEUESIZE - 1;
	*/
	mp->mq.ovflag = FALSE;
	mp->mq.cc = 0;
	mp->mq.qs = mp->mq.cf = mp->mq.ct = mp->mq.buf;
	mp->mq.qe = mp->mq.buf + QSIZE - 1;
}

/* mqPut
 *
 * put byte in queue.
 *
 * if queue FULL
 *	cycle queue
 *
 */
static int
mqPut(mp, c)
struct mpu *mp;
int c;
{
	/* check for queue FULL condition
	*/
	if ( mp->mq.ovflag == TRUE || (mp->mq.cf == mp->mq.ct && mp->mq.cc > 0 )) {
		mp->mq.ovflag = TRUE;
		/* if you want to cycle queue, uncomment
		*/
		mp->mq.cf++;
		return(0);
	}

	/* put char in queue tail or end of queue
	*/
	*mp->mq.ct++ = (char) c;

	/* wrap tail pointer around if necessary
	*/
	if ( mp->mq.ct > mp->mq.qe )
		mp->mq.ct = mp->mq.qs;
	mp->mq.cc++;
	return(1);
}

/* caller should cast result 
*/
static int
mqGet(mp)
struct mpu *mp;
{
	int c;

	/* get the character from the queue front
	*/
	c = *mp->mq.cf++ & 0xff;

	/* wrap if necessary
	*/
	if ( mp->mq.cf > mp->mq.qe )
		mp->mq.cf = mp->mq.qs;
 	
	/* decrement char count
	*/
	mp->mq.cc--;
	return((int) c);
}

#endif /* NMPU */
