/*
 * NES streams driver.
 *
 * Derived, through several generations, from code typed in from 
 * the AT&T Streams Programmer's Guide.
 *
 * Supports Nintendeo Entertainment System joysticks via a parallel port.
 * No interrupts.  No zap-gun support.
 *
 * Each minor number corresponds to one joystick plug.  It should be possible
 * to hook up a few NES gizmos via parallel port.
 *
 * Copyright 1991, Lance C. Norskog
 *
 * version 0.1 - base, running with X
 * version 0.2 - switch to double-bucket input management
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/sysmacros.h>
#ifndef	__GNUC__
#include <sys/inline.h>
#endif
#include <sys/errno.h>

static struct module_info rnes = {
	3, "nes", 0, INFPSZ, 500, 100
};

static struct module_info wnes = {
	3, "nes", 0, INFPSZ, 500, 100
};

static int nesopen(), nesclose(), neswput(), nesrsrv();

static struct qinit urqinit = {		/* Upper read */
	0, nesrsrv, nesopen, nesclose, NULL, &rnes, 0
};

static struct qinit uwqinit = {		/* Upper write */
	neswput, 0, nesopen, nesclose, NULL, &wnes, 0
};

struct streamtab nesinfo = {
	&urqinit, &uwqinit, NULL, NULL
};

int nes_debug = 0, nes_notimer = 0, nes_nostrobe = 0;
#define	debug(mask, x)	if (nes_debug & mask) printf x;
/* 1 for upper-level, 2 for lower-level, 4 for input data */

int nes_in, 			/* number of input packets 		*/
    nes_inb;			/* number of input bytes   		*/
int nes_nomblk, 		/* number of allocb failures		*/
    nes_nodupb, 		/* number of dupb failures		*/
    nes_full, 			/* number of buffer-full failures	*/
    nes_cant, 			/* number of canput failures		*/
    nes_busy;			/* number of bucket-busy		*/

int nes_bsize = 1024;		/* several seconds worth */

#define	NNES	1

#define	NES_right	0x01
#define	NES_left	0x02
#define	NES_down	0x04
#define	NES_up		0x08
#define	NES_start	0x10
#define	NES_select	0x20
#define	NES_B		0x40
#define	NES_A		0x80

#define	NES_SWITCHES	(NES_start | NES_select | NES_A | NES_B)
#define	NES_MOVES	(NES_right | NES_left | NES_up | NES_up)

struct	nes_nes {
	unsigned short	ctrl_port, ctrl_clock, ctrl_reset,
			data_port, data_mask,
			ticks;
	unsigned char	canopen;
	queue_t	*rdq;
	int	timer;
	int	nes;			/* last buttons */
	int	switches;
	mblk_t	*mp, *other;		/* pointer to input blocks */
} nes_nes[NNES] = {
{
	0x378,
	1,
	2,
	0x379,
	0x10,
	HZ,
}
};

typedef	struct nes_nes nes_t;

int nnes = NNES;

int nes_c1 = 20;
int nes_c2 = 20;
int nes_c3 = 20;
int nes_c4 = 20;
int nes_c5 = 20;

/* move to space.c */

nesinit() {
	int i, hz;

	for(i=0; i<nnes; i++) {
		nes_nes[i].rdq = (queue_t *) 0;
		nes_nes[i].canopen = 1;
		nes_strobe_init(&nes_nes[i]);
		hz = HZ / nes_nes[i].ticks;
		nes_nes[i].ticks = ((hz == 0) ? 1 : hz);
		nes_nes[i].switches = 0;
	}
}

nesopen(q, dev, flag, sflag)
queue_t *q;
{
	nes_t	*nes;
	int	nes_timer();

	if (sflag == CLONEOPEN) {
		return OPENFAIL;
/*		for (dev = 0; dev < nnes; dev++)
			if (nes_nes[dev].rdq == (queue_t *) 0)
				break; */
	} else	dev = minor(dev);
	if (dev > nnes)
		return OPENFAIL;
	nes = &nes_nes[dev];
	if (nes->rdq)
		return dev;

	/* q_ptr is the q's repository for our per-chan structure */
	q->q_ptr = (caddr_t) nes;
	WR(q)->q_ptr = (caddr_t) nes;
	nes->rdq = q;

debug(1, ("Line %d\n", __LINE__));
	/* Start timer */
	if (! nes_notimer) 
		nes->timer = timeout(nes_timer, q, nes->ticks);	
	nes->mp = allocb(nes_bsize, BPRI_LO);
	nes->other = allocb(nes_bsize, BPRI_LO);
	if (! nes->mp)	nes_nomblk++;

debug(1, ("Line %d\n", __LINE__));
	return dev;
}

static
nesclose(q)
queue_t *q;
{
	nes_t *nes = (nes_t *) q->q_ptr;
	mblk_t *mp;

debug(1, ("Line %d\n", __LINE__));
	untimeout(nes->timer);
	nes->rdq = (queue_t *) 0;
	if (nes->mp)
		freemsg(nes->mp);
	if (nes->other)
		freemsg(nes->other);
	nes->mp = (mblk_t *) 0;
	nes->other = (mblk_t *) 0;
	nes_strobe_init(nes);
debug(1, ("Line %d\n", __LINE__));
}

/* This is called when, and only when, the above reader can read. */
/*
 * Double-bucket method:
 *	Only send up when other bucket is finished processing.
 *	Always duplicate buffers before sending them up.
 */
nesrsrv(q)
queue_t *q;
{
	nes_t *nes = (nes_t *) q->q_ptr;
	mblk_t *mp;
	int can, bytes = 0;

debug(1, ("Line %d\n", __LINE__));
	if (!nes->mp)
		return;
	if (nes->other && (nes->other->b_datap->db_ref > 1)) {
		nes_busy++;
		return;
	}
debug(1, ("Line %d\n", __LINE__));
	if (! (bytes = (nes->mp->b_wptr > nes->mp->b_rptr)))
		return;
	can = canput(q->q_next);
debug(1, ("Line %d\n", __LINE__));
	if (! can) {
		nes_cant++;
		return;
	}
	if (!(mp = dupb(nes->mp))) {
		nes_nodupb++;
		return;
	}

debug(1, ("Line %d\n", __LINE__));
	/* send up copy of current bucket */
	nes_in++;
	nes_inb += mp->b_wptr - mp->b_rptr;
	putnext(q, mp);

	/* Swap buckets */
	mp = nes->other;
	nes->other = nes->mp;
	nes->mp = mp;

debug(1, ("Line %d\n", __LINE__));
	if (!nes->mp)
		nes->mp = allocb(nes_bsize, BPRI_LO);
	if (! nes->mp)	
		nes_nomblk++;
	else	/* reset read/write pointers of recycled bucket */
		nes->mp->b_rptr = nes->mp->b_wptr = nes->mp->b_datap->db_base;
}

/* This is called strioctl(), whether or not it's there!  */
neswput(q, mp)
queue_t *q;
mblk_t *mp;
{
	nes_t *nes = (nes_t *) q->q_ptr;

	switch(mp->b_datap->db_type) {
	    case M_IOCTL:
		/* process */

		mp->b_datap->db_type = M_IOCNAK;
		qreply(q, mp);
		break;
	    
	    /* flush handling must be here */
	    case M_FLUSH:
		if (*mp->b_rptr & FLUSHW)
			flushq(q, FLUSHDATA);
		if (*mp->b_rptr & FLUSHR) {
			flushq(RD(q), FLUSHDATA);
			*mp->b_rptr &= ~FLUSHW;
			qreply(q, mp);
		} else
			freemsg(mp);
		break;
	    default:
		freemsg(q, mp);
	}
}

nes_timer(q)
queue_t *q;
{
	nes_t *nes = (nes_t *) q->q_ptr;
	unsigned char bits, nes_strobe();
	mblk_t *tmp;

	if (nes_notimer)
		goto resched;
debug(2, ("Line %d\n", __LINE__));
	if (!nes->mp && !(nes->mp = allocb(nes_bsize, BPRI_LO))) {
		nes_nomblk++;
		goto resched;
	}

debug(2, ("Line %d\n", __LINE__));
	if (nes->mp->b_wptr == nes->mp->b_datap->db_lim) {
		nes_full++;
		goto resched;
	}

debug(2, ("Line %d\n", __LINE__));
	bits = nes_strobe(nes);
	if (((bits & NES_right) && (bits & NES_left)) ||
	    ((bits & NES_up) && (bits & NES_down))) {
		/* skip */
	} else {
debug(2, ("Line %d\n", __LINE__));
		/* report on movement or change in switches */
		if (bits || ((bits & (NES_SWITCHES)) != nes->switches)) {
			*(nes->mp->b_wptr++) = bits;
debug(2, ("Line %d\n", __LINE__));
			qenable(nes->rdq);
			nes->switches = (bits & NES_SWITCHES);
		}
debug(2, ("Line %d\n", __LINE__));
	}
   resched:
	nes->timer = timeout(nes_timer, q, nes->ticks);
}

/* 
 * Put port in quiescent state
 */

nes_strobe_init(nes)
nes_t *nes;
{
	int i, j;

	if (nes_nostrobe)
		return;
	for(i = 0; i < 8; i++)
		if ((1<<i) & nes->data_mask)
			break;
	if (i == 8) {
		nes->canopen = 0;
		printf("NES%d: data_mask not set!\n", nes - nes_nes);
	}
	outb(nes->ctrl_port, 0);
	outb(nes->data_port, 0);
}

#define	hold(clk)		for(slow = clk; slow; slow--);

/* Actually strobe the NES and add events to the record */
unsigned char
nes_strobe(nes)
nes_t *nes;
{
	int i, slow;
	unsigned char data;
	unsigned short bits = 0;	/* not char! */

debug(2, ("Line %d\n", __LINE__));
	if (nes_nostrobe)
		return 0;
/*
#ifndef	__GNUC__
	intr_disable();
#endif
*/
debug(2, ("Line %d\n", __LINE__));
	outb(nes->ctrl_port, nes->ctrl_reset | nes->ctrl_clock);
	hold(nes_c1);
	outb(nes->ctrl_port, nes->ctrl_clock);
	hold(nes_c2);
	for(i = 0; i < 8; i++) {
		data = inb(nes->data_port);
		bits |= (data & nes->data_mask);
		hold(nes_c3);
		outb(nes->ctrl_port, 0);
		hold(nes_c4);
		outb(nes->ctrl_port, nes->ctrl_clock);
		hold(nes_c5);
		bits <<= 1;
	}
debug(2, ("Line %d\n", __LINE__));
	/* bits now straddles word, is upwards by one */
	/* downshift into place */
	for(i = 0; i < 8; i++) {
		bits >>= 1; 
		if (nes->data_mask & (1 << i))
			break;
	}
debug(2, ("Line %d\n", __LINE__));
/*
#ifndef	__GNUC__
	intr_restore();
#endif
*/
debug(2, ("Line %d\n", __LINE__));
	bits = ~bits;
if (bits && (nes_debug & 4))
		printf("Input: 0x%x\n", bits & 0x1ff);
	return (unsigned char) bits;
}

static char *
M_string(type)
{
	switch(type) {
		case M_DATA:	return "M_DATA";
		case M_PROTO:	return "M_PROTO";
		case M_BREAK:	return "M_BREAK";
		case M_PASSFP:	return "M_PASSFP";
		case M_SIG:	return "M_SIG";
		case M_DELAY:	return "M_DELAY";
		case M_CTL:	return "M_CTL";
		case M_IOCTL:	return "M_IOCTL";
		case M_SETOPTS:	return "M_SETOPTS";
		case M_IOCACK:	return "M_IOCACK";
		case M_IOCNAK:	return "M_IOCNAK";
		case M_PCPROTO:	return "M_PCPROTO";
		case M_PCSIG:	return "M_PCSIG";
		case M_FLUSH:	return "M_FLUSH";
		case M_STOP:	return "M_STOP";
		case M_START:	return "M_START";
		case M_HANGUP:	return "M_HANGUP";
		case M_ERROR:	return "M_ERROR";
		default:	return "M_UNKNOWN";
	}
}

nesintr() {;}
/* Don't digest it here.
		if (bits & NES_right)
			nes->right++;
		if (bits & NES_left)
			nes->left++;
		if (bits & NES_up)
			nes->up++;
		if (bits & NES_down)
			nes->down++;

		if (bits & NES_A)
			nes->A = 1;
		if (bits & NES_B)
			nes->B = 1;
*/

