/* mp-switch-video - The purpose of mp-switch-video is to switch
 * the first scart output of a MediaPortal (the output marked with
 * "TV") from the normal graphics card output to the full screen
 * direct DVB-output of the DVB-s or DVB-c card.
 * This programm is useless, when you don't have a MediaPortal or
 * some special hardware, that needs a static positive level on
 * the speaker output. It may even damage your hardware, when you
 * use it on a non-MediaPortal PC!!!!
 *
 * Try mp-switch-video -h for command line args. Currently there are 
 * none that make sense, but that may change :-)
 *
 * This code is copyright (C) Michael Dreher, 2002.
 *
 * This code may distributed only under the terms of the GNU Public License 
 * which can be found at http://www.gnu.org/copyleft or in the file COPYING 
 * supplied with this code.
 *
 * This code is not distributed with warranties of any kind, including implied
 * warranties of merchantability or fitness for a particular use or ability to 
 * breed pandas in captivity, it just can't be done.
 *
 * Bug me, I like it:	nospam2000@gmx.de
 */

#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/kd.h>
#include <sys/io.h>

#define VERSION_STRING "mp-switch-video-1.0.0"
char *copyright = 
"Copyright (C) Michael Dreher <nospam2000@gmx.de>, 2002.	"
"Use and Distribution subject to GPL.	"
"For information: http://www.gnu.org/copyleft/.";

typedef enum tvout_enum_t
{
	nothing = 0, // just read the current value
	toggle	= 1,
	vga_out = 2,
	dvb_out = 3
} tvout_enum;

typedef struct beep_parms_t
{
	tvout_enum tvout;
	int returnState;
} beep_parms_t;

/* If we get interrupted, it would be nice to not leave the speaker beeping in
	 perpetuity. */
void handle_signal(int signum)
{
	switch(signum)
	{
		case SIGINT:
			/* enable interrupts */
			//enable_interrupts();

			/* Just quit gracefully */
			exit(signum);
			break;
	}
}

/* print usage and exit */
void usage_bail(const char *executable_name)
{
	printf("Usage:\n%s [ -g, --vga | -d, --dvb | -n, --nop ] [ -r, --readstate ]\n", executable_name);
	printf("%s [-h] [--help]\n", executable_name);
	printf("%s [-v] [-V] [--version]\n", executable_name);
	printf("  -g, --vga : switch output to VGA card\n");
	printf("  -d, --dvb : switch output to DVB-S/DVB-C card\n");
	printf("  -n, --nop : let output in its current state, just return the current output\n");
	printf("  -r, --readstate : switch the output and return the resulting output\n");
	printf("Switch the TV-output of the MediaPortal between VGA and fullscreen DVB.\n");
	printf("When none on --vga, --dvb and --nop is specified, the default operation is to toggle\n");
	printf("the output between vga and dvb. This is the reason why --nop has been invented,\n");
	printf("to read the state without changing it.\n");
	exit(1);
}


/* Parse the command line.	argv should be untampered, as passed to main.
 * Beep parameters returned in result, subsequent parameters in argv will over-
 * ride previous ones.
 * 
 * Currently valid parameters:
 *	"-h/--help"
 *	"-v/-V/--version"
 *	"-d/--dvb"
 *	"-g/--vga"
 *	"-n/--nop"
 *	"-r/--readstate"
 *
 * March 29, 2002 - Daniel Eisenbud points out that c should be int, not char,
 * for correctness on platforms with unsigned chars.
 */
void parse_command_line(int argc, char **argv, beep_parms_t *result)
{
	int c;

	struct option opt_list[] =
	{
		{"dvb", 0, NULL, 'd'},
		{"vga", 0, NULL, 'g'},
		{"nop", 0, NULL, 'n'},
		{"readstate", 0, NULL, 'r'},
		{"help", 0, NULL, 'h'},
		{"version", 0, NULL, 'V'},
		{0,0,0,0}
	};
	while((c = getopt_long(argc, argv, "dgnrvVh", opt_list, NULL)) != EOF)
	{
		switch(c)
		{			
			case 'd':
				result->tvout = dvb_out;
				break;

			case 'g':
				result->tvout = vga_out;
				break;

			case 'n':
				result->tvout = nothing;
				result->returnState = 1;
				break;

			case 'r':
				result->returnState = 1;
				break;

			case 'v' :
			case 'V' : /* also --version */
				printf("%s\n",VERSION_STRING);
				exit(0);
				break;

			case 'h' : /* notice that this is also --help */
			default :
				usage_bail(argv[0]);
		}
	}
}	

int set_io_perms(int turn_on)
{
	/* needed for the inb_p() and outb_p() macros */
	if(ioperm(0x80, 1, turn_on) != 0)
		return -1;

	/* the 8254 chip, counter 2 and control register */
	if(ioperm(0x42, 2, turn_on) != 0)
		return -1;

	/* the 8255 chip, port B for the speaker
	 * (bits0=gate for counter and bit1=speaker on) */
	if(ioperm(0x61, 1, turn_on) != 0)
		return -1;

	return 0;
}


void set_speaker_vga()
{
	outb(inb_p(0x61)&0xFC, 0x61);
}


void set_speaker_dvb()
{
	/* this is just to ensure that the */
	/* output of the counter is 1 */
	/* binary,mode 0,LSB/MSB,ch 2*/
	outb_p(0xb0, 0x43); 
	outb_p(0x1, 0x42);
	outb_p(0x0, 0x42);
	
	/* set the gate of the counter2 and the output enable of the speaker to high */
	outb(inb_p(0x61) | 0x03, 0x61);
}


tvout_enum get_speaker_state()
{
	return (inb_p(0x61) & 0x02) ? dvb_out : vga_out;
}



int main(int argc, char **argv)
{
	beep_parms_t parms;
	tvout_enum newState = nothing;
	int returnCode = EXIT_SUCCESS;

	signal(SIGINT, handle_signal);
	parms.tvout = toggle;
	parms.returnState = 0;
	parse_command_line(argc, argv, &parms);

	if(set_io_perms(1) != 0)
	{
		returnCode = -1;
	}
	else
	{
		/* Interrupts should be disabled during modifying the ports, because we
		 * do read/modify/write and also special register access.
		 * I found no good way to do this in user mode, so the only way is to
		 * write a kernel moduly who does all the job.
		 */
		//disable_interrupts();

		switch(parms.tvout)
		{
			case toggle:
			{
				if(get_speaker_state() == vga_out)
				{
					set_speaker_dvb();
					newState = dvb_out;
				}
				else
				{
					set_speaker_vga();
					newState = vga_out;
				}
				break;
			}

			case vga_out:
			{
				set_speaker_vga();
				newState = vga_out;
				break;
			}

			case dvb_out:
			{
				set_speaker_dvb();
				newState = dvb_out;
				break;
			}

			default:
			{
				newState = get_speaker_state();
				break;
			}
		}

		//enable_interrupts();
		set_io_perms(0);
	}

	if(parms.returnState)
	{
		switch(newState)
		{
			case vga_out:
				printf("vga\n");
				break;

			case dvb_out:
				printf("dvb\n");
				break;

			default:
				break;
		}
	}

	return returnCode;
}


