/* ------------------------------ joytest.c --------------------------------- */

/* An example of how to read the PC joystick port by polling the hardware
 * port directly.
 * Uses inp()/outp() for byte port access.
 * Will timeout after JS_TIMEOUT reads. On my 486DX2/66, using my joystick port
 * and my joystick, full deflection reads 1600-1800 counts. I repeat the 'my'
 * above to indicate that each joystick card and joystick has different
 * characteristics. This shows as 6000-7000 counts if the READ_TIMER option
 * is used (depends on the mode the timer is set to...).
 *
 * There is no need to optimize this routine since it runs for as long as
 * the joystick circuitry needs. Nevertheless, on slow machines increasing
 * the speed will yield higher resolution. This version is already optimized
 * for speed.
 *
 * About interrupts: these will cause some noise in the reading. The easiest
 * way around it is to read the stick twice and select the SMALLEST reading.
 * You may want to disable interrupts for the process (as the program shows
 * as comments) but watch out for lost serial-port characters and what not.
 * A middle way is to read the system timer which gets around a lot of the
 * noise; just #define USE_TIMER to enable this feature.
 * The enable/disable functions control the interrupts, you can use other
 * functions if your compiler provides them.
 *
 * To compile:
 * 	>cl -O2 joytest.c
 * To Run:
 * 	>joytest
 *
 * Written by Eyal Lebedinsky (eyal@ise.canberra.edu.au).
 * This version: January 1994.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

/* #include "pc8254.h" */

/* --------------------------------- pc8254.h ------------------------------- */

/* This is part of the flight simulator 'fly8'.
 * Author: Eyal Lebedinsky (eyal@ise.canberra.edu.au).
*/

/* Definitions for the timer chip 8254. Used for timekeeping and speaker
 * control.
*/

#define CHANNEL_0	0x0040		/* system tick */
#define	CHANNEL_2	0x0042		/* speaker tone */
#define	COMMAND_REG	0x0043
#define WRITE_CH0	0x0036
#define WRITE_CH2	0x00b6
#define READ_SPECIAL	0x00c2
#define	PORT_B		0x0061		/* speaker on/off control */
#define	XTAL		1193000L
#define TIMER_MODES	0x000e
#define TIMER_MODE	0x0002
#define TIMER_OUT	0x0080
#define TIMER_PERIOD	0x0000ffffL

/* --------------------------------- pc8254.h end---------------------------- */


#define JS_PORT		0x201
#define JS_TIMEOUT	32000
#define JS_READ		inp (JS_PORT)

typedef unsigned short	Ushort;
typedef unsigned int	Uint;
typedef unsigned long	Ulong;

/* These are the timer reading support functions.
*/
static Ushort near
disable (void)
{
	Ushort	flags;

	_asm {
		pushf
		pop	flags
		cli
	}
	return (flags);
}

static void near
enable (Ushort flags)
{
	_asm {
		push	flags
		popf
	}
}

static Uint near
get_timer (void)		/* get fastest timer available */
{
	Ushort		t, status, flags;

	do {
		flags = disable ();
		outp (COMMAND_REG, READ_SPECIAL);
		status = (Ushort) inp (CHANNEL_0);	/* get status */
		t  = (Ushort)inp (CHANNEL_0);		/* low byte */
		t += (Ushort)(inp (CHANNEL_0) << 8);	/* high byte */
		enable (flags);
	} while (0 == t);

	return ((Uint)(Ushort)~((status & TIMER_MODES) == TIMER_MODE*2 ? t
		: ((t>>1) + (Ushort)((status&TIMER_OUT)<<8))));
}

#define READING	(mode ? get_timer () : JS_TIMEOUT-i)

struct stick {
	Ushort	a[4];
	Ushort	b[4];
};
typedef struct stick	STICK;

static int near
readjoy (STICK *s, int mode, int mask, int nread, int delay)
{
	register int	i;
	register Uint	m;
	unsigned int	t, x1, y1, x2, y2, minx1, miny1, minx2, miny2;
	int		js, tt, ntimes;

	minx1 = miny1 = minx2 = miny2 = 0xffffU; /* avoid compiler warning */
	memset (s->a, 0, sizeof (s->a));

	for (ntimes = 0;;) {
		i = JS_TIMEOUT;
		t = READING;
		x1 = y1 = x2 = y2 = t;
		outp (JS_PORT, 0);		/* set trigger */
		for (m = mask; m;) {
			while (!(~JS_READ & m) && --i)
				;
			if (!i)
				break;
			tt = READING;
			js = ~JS_READ & m;
			if (js & 0x01) {
				x1 = tt;
				m &= ~0x01;
			}
			if (js & 0x02) {
				y1 = tt;
				m &= ~0x02;
			}
			if (js & 0x04) {
				x2 = tt;
				m &= ~0x04;
			}
			if (js & 0x08) {
				y2 = tt;
				m &= ~0x08;
			}
		}
		if (minx1 > (x1 -= t))
			minx1 = x1;
		if (miny1 > (y1 -= t))
			miny1 = y1;
		if (minx2 > (x2 -= t))
			minx2 = x2;
		if (miny2 > (y2 -= t))
			miny2 = y2;

		if (++ntimes >= nread)	/* read more? */
			break;

		if (0 != (i = delay)) {		/* delay? */
			tt = 1234;
			for (i *= 10; i-- > 0;)
				tt *= 19;
		}
	}

	js = m | ~mask;
	s->a[0] = (js & 0x01) ? 0 : minx1;	/* analog 1 */
	s->a[1] = (js & 0x02) ? 0 : miny1;	/* analog 2 */
	s->a[2] = (js & 0x04) ? 0 : minx2;	/* analog 3 */
	s->a[3] = (js & 0x08) ? 0 : miny2;	/* analog 4 */

	js = ~JS_READ;
	s->b[0] = !!(js & 0x10);		/* button 1 */
	s->b[1] = !!(js & 0x20);		/* button 2 */
	s->b[2] = !!(js & 0x40);		/* button 3 */
	s->b[3] = !!(js & 0x80);		/* button 4 */

	return (m);
}

static void near
usage (int die)
{
	printf ("Usage:\n");
	printf ("   joytest Mode Mask [q] [Nread [Delay]]\n");
	printf ("Mode is 'count' or 'timer' to indicate the reading method.\n");
	printf ("Mask defines which channels to read. It is one hexadecimal");
	printf (" digit, e.g.:\n");
	printf ("   '3' will read joystock A.\n");
	printf ("   'c' will read joystock B.\n");
	printf ("   'f' will read both.\n");
	printf ("   'b' will read a CH or FCS.\n");
	printf (" Try 'f', if you get st=n with a non-zero n then");
	printf (" use 15-n as your parameter.\n");
	printf ("q will not print the readings, good for timing.\n");
	printf ("Nread will read the stick that many times and return the");
	printf (" minimum as the result.\n");
	printf ("Delay will pause that much between readings.\n");
	printf (" These are useful on fast machines or when");
	printf (" there is interference (network etc.).\n");

	if (die)
		exit (1);
}


/* This main() is for demonstration.
*/

int
main (int argc, char *argv[])
{
	int	i, mode, mask, quiet, nread, delay;
	Ulong	testno;
	STICK	s[1];

	printf ("joystick test,");
	printf (" by Eyal Lebedinsky [eyal@ise.canberra.edu.au]\n");

	usage (0);

	if (argc < 3)
		usage (1);

	if (!strcmp (argv[1], "count"))
		mode = 0;
	else if (!strcmp (argv[1], "timer"))
		mode = 1;
	else
		usage (1);

	if (1 != sscanf (argv[2], "%x", &mask) || mask < 1 || mask > 15)
		usage (1);

	i = 3;
	if (!strcmp (argv[i], "q")) {
		quiet = 1;
		++i;
	} else
		quiet = 0;

	nread = 1;
	delay = 0;
	if (argc > i) {
		if (1 != sscanf (argv[i], "%d", &nread) || nread <= 0)
			usage (1);
		++i;
		if (argc > i
		    && (1 != sscanf (argv[i], "%d", &delay) || delay < 0))
			usage (1);
	}

	printf ("st>0 means some joystick channel is not operational.\n");
	printf ("If the reading is very large");
	printf (" then this channel is not connected.\n");
	printf ("If you do have a stick connected and get st>0");
	printf (" then that port is not functioning.\n");
	printf ("A reading of zero usually indicates a non-operational");
	printf (" channel.\n\n");

	if (mode)
		printf ("Using the PC's timer for timing.\n");
	else
		printf ("Using a loop count for timing.\n");
	printf ("Reading mask %x, %d times with a %d delay.\n",
		mask, nread, delay);

	printf ("\nHit any key to exit\n\n");
	if (quiet)
		printf ("Running in quiet mode.\n");

	for (testno = 1; !kbhit (); ++testno) {
		i = readjoy (s, mode, mask, nread, delay);
		if (!quiet)
printf ("\rst=%x x1=%5u y1=%5u x2=%5u y2=%5u btn=%u%u%u%u n=%lu",
				i, s->a[0], s->a[1], s->a[2], s->a[3],
				s->b[0], s->b[1], s->b[2], s->b[3], testno);
	}
	if (quiet) {
		printf ("Stick was read %lu times.\n", testno);
		printf ("Remember that the time depends on the joystick");
		printf (" position.\n");
	}

	exit (0);
	return (0);
}