/******************************************************************
*
* port_io.c	routines to communicate with IBM memory addressable ports.
*
* Author:	Gary Hoggard	1 Feb 1997
*
* The callable functions are:
*		portinit()	called once before the other functions
*		portout()	write to a specific port
*		portin()	read from a specific port
*		is_joystick()	check if a joystick is connected
*		gameport()	get status of game port (joystick)
*		portend()	de-initialise (called last)
*
******************************************************************/

#include "r4.h"

/* input pins on LPT ports */
#define PIN_15_OFF      0x08
#define PIN_13          0x10
#define PIN_12          0x20
#define PIN_11_OFF      0x80

static int portfd;	/* pointer to /dev/port */


/******************************************************************
* portout()
*
* Usage:	portout(port, byte, duration, reset)
* where:
*	int port		= port as defined in "r4.h"
*	char byte		= 8-bit pattern to send to port
*	unsigned duration	= msec before reset (0 = no reset)
*	int reset		= YES or NO
******************************************************************/
portout(int port, char byte, unsigned duration, int reset)
	{
	struct timeval timer;
	char zero = 0x00;
	int i;

	timer.tv_sec = duration / 1000;
	timer.tv_usec = (duration % 1000) * 1000;
	if (debug)
		{
		fprintf(dlog, "portout() using: port=0x%x, duration=%d.%03d sec, value=0x%02x %s\n",
			port, timer.tv_sec, timer.tv_usec/1000, 0xff & byte,
			reset ? "with reset" : "");
		fflush(dlog);
		}
	if (port)
		{
		lseek(portfd, port, SEEK_SET);
		write(portfd, &byte, 1);
		}
	select(0, NULL, NULL, NULL, &timer);
	if (reset)
		{
		lseek(portfd, port, SEEK_SET);
		write(portfd, &zero, 1);
		}
	}


/******************************************************************
* portin()
*
* Usage:	return_byte = portin(port)
* where:
*	int port		= port as defined in "r4.h"
******************************************************************/
portin(int port)
	{
	unsigned byte = 0x00;

	if (debug)
		{
		fprintf(dlog, "portin() using: port=0x%x\n", port);
		fflush(dlog);
		}
	if (port)
		{
		lseek(portfd, port, SEEK_SET);
		read(portfd, &byte, 1);
		}
	return(0xff & byte);
	}


/******************************************************************
* is_joystick()
*
* returns YES if joystick is connected, otherwise NO.  "joystick"
* will be 1 for "A" and 2 for "B".
******************************************************************/
is_joystick(int joystick)
	{
	unsigned int counter = 0;
	unsigned char byte, mask;

	switch(joystick)
		{
		case 1:
			mask = 0x03;
			break;
		case 2:
			mask = 0x0c;
			break;
		default:
			if (debug)
				{
				fprintf(dlog, "is_joystick(): invalid joystick: %d\n", joystick);
				fflush(dlog);
				}
			return(FAIL);
		}

	/* set gameport one-shot */
	byte = mask;
	lseek(portfd, GAME, SEEK_SET);
	write(portfd, &byte, 1);

	/* get X position */
	while (byte & mask)
		{
		if (++counter > 1000)		/* it should NEVER get this high */
			return(NO);
		lseek(portfd, GAME, SEEK_SET);
		read(portfd, &byte, 1);
		}
	return(YES);
	}


/******************************************************************
* gameport()
*
* Usage:	switches = gameport(&xpos, &ypos, joystick)
* where:
*	all vars are type int
*	"joystick" will be set to 1 for "A" for 2 for "B"
*	gameport() returns an int containing either FAIL (-1) or
*	lower 2 bits:
*		1 = joystick switch 1 on (pressed)
*		2 = joystick switch 2 on (pressed)
*		3 = joystick switches 1 & 2 on (pressed)
*
* The joystick status is obtained by writing (anything) to the port
* address 0x201.  This sets 4 one-shots (in the lower 4 bits of the
* port address byte).  The one-shots each reset at different times
* depending on the position of the X & Y potentiometers on each of
* joysticks A & B.  Common practice is to start counters when the
* one-shots are set and stop each counter as its respective one-shot
* resets.  The upper 4 bits of the port address byte contain the
* position of the joystick switches ("fire" buttons): 2 for each
* joystick.
******************************************************************/
gameport(int *xpos, int *ypos, int joystick)
	{
	unsigned char x_mask, y_mask;
	unsigned char byte;

	if (debug)
		{
		fprintf(dlog, "gameport(): checking joystick port '%c'\n",
			joystick == 1 ? 'A' : 'B');
		fflush(dlog);
		}

	switch(joystick)
		{
		case 1:
			x_mask = 0x01;
			y_mask = 0x02;
			break;
		case 2:
			x_mask = 0x04;
			y_mask = 0x08;
			break;
		default:
			if (debug)
				{
				fprintf(dlog, "gameport(): invalid joystick: %d\n", joystick);
				fflush(dlog);
				}
			return(FAIL);
		}

	/* set return values */
	*xpos = joystick_pos(x_mask);
	portout((int)NULL, 0, 10, NO);		/* 10ms delay to let port settle */
	*ypos = joystick_pos(y_mask);

	/* get switch status */
	byte = portin(GAME);
	if (joystick == 1)
		byte = (~byte & 0x30) >> 4;
	else
		byte = (~byte & 0xc0) >> 6;
	return(byte);
	}


/******************************************************************
* joystick_pos()
*
* Get X or Y joystick position.  Set "jstick_mask" to select which
* joystick and axis:
*		1 = joystick A, X-axis
*		2 = joystick A, Y-axis
*		4 = joystick B, X-axis
*		8 = joystick B, Y-axis
******************************************************************/
joystick_pos(unsigned int jstick_mask)
	{
	unsigned char byte;
	int counter = FAIL;

	/* set gameport one-shot */
	byte = jstick_mask;
	lseek(portfd, GAME, SEEK_SET);
	write(portfd, &byte, 1);

	/* get position */
	while (byte & jstick_mask)
		{
		++counter;
		lseek(portfd, GAME, SEEK_SET);
		read(portfd, &byte, 1);
		}
	return(counter);
	}


/******************************************************************
* portinit()
*
* Used to initialise IBM PC memory-mapped port I/O.  Returns FAIL
* if /dev/port cannot be opened.
******************************************************************/
portinit()
	{
	if ((portfd = open("/dev/port", O_RDWR)) < 0)
		{
		if (debug)
			{
			fprintf(dlog, "portinit(): cannot open /dev/port\n");
			fflush(dlog);
			}
		return(FAIL);
		}
	portout(IO_Control, 0x98,   0, NO);	/* IO_A=input, IO_B=output, IO_C=i/o */
	if (debug)
		{
		fprintf(dlog, "portinit(): /dev/port open read/write\n");
		fflush(dlog);
		}
	}


/******************************************************************
* portend()
*
* Used to deinitialise IBM PC memory-mapped port I/O.
******************************************************************/
portend()
	{
	close(portfd);
	if (debug)
		{
		fprintf(dlog, "portend(): /dev/port closed\n");
		fflush(dlog);
		}
	}

/*________________________end_of_port_io.c__________________________*/

