#include <stdarg.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>

#define VERSION "0.6.5beta"
#define CMD_LSPCI "lspci"

static const char *Opt_lcd = NULL;
static const char *Opt_crt = NULL;

struct i810_par {
	unsigned char *mmio;
	int mem;
	int flag;
};

#define i810_readb(mmio_base, where)                     \
        *((volatile char *) (mmio_base + where))

#define i810_readw(mmio_base, where)                     \
        *((volatile short *) (mmio_base + where))

#define i810_readl(mmio_base, where)                     \
        *((volatile int *) (mmio_base + where))

#define i810_writeb(mmio_base, where, val)                              \
	*((volatile char *) (mmio_base + where)) = (volatile char) val

#define i810_writew(mmio_base, where, val)                              \
	*((volatile short *) (mmio_base + where)) = (volatile short) val

#define i810_writel(mmio_base, where, val)                              \
	*((volatile int *) (mmio_base + where)) = (volatile int) val

#define I810_MEMOPEN		1
#define I810_MMIO		2

/* chip types */
#define I810			1
#define I830			2
#define I855                    3
#define I915                    4


/* PCI IDs */
#define I810STR			"8086:7121"
#define I810ESTR		"8086:7123"
#define I810_DC100STR_1		"8086:7125"
#define I810_DC100STR_2		"8086:1102"
#define I810_IGSTR		"8086:1112"
#define I810_CFCSTR		"8086:1132"
#define I830STR			"8086:3577"
#define I845STR			"8086:2562"
#define I855STR			"8086:3582"
//#define I865STR			"8086:2572"
#define I915STR			"8086:2592"
#define MEMSTR			"Memory at"
#define NONPRSTR		"32-bit, non-prefetchable"

/* I810 registers */
#define I810_HVSYNC		0x05000 

#define I810_DCLK_0D		0x06000
#define I810_DCLK_1D		0x06004
#define I810_DCLK_0DS		0x06010
#define I810_PWR_CLKC		0x06014

#define I810_HTOTAL		0x60000
#define I810_HBLANK		0x60004
#define I810_HSYNC		0x60008
#define I810_VTOTAL		0x6000c
#define I810_VBLANK		0x60010
#define I810_VSYNC		0x60014
#define I810_LCDTV_C		0x60018
#define I810_OVRACT		0x6001c
#define I810_BCLRPAT		0x60020

#define I810_PIXCONF		0x70008

/* I830 registers */
#define I830_HTOTAL_A		0x60000
#define I830_HBLANK_A		0x60004
#define I830_HSYNC_A		0x60008
#define I830_VTOTAL_A		0x6000c
#define I830_VBLANK_A		0x60010
#define I830_VSYNC_A		0x60014
#define I830_PIPEASRC		0x6001c
#define I830_BCLRPAT_A		0x60020

#define I830_HTOTAL_B		0x61000
#define I830_HBLANK_B		0x61004
#define I830_HSYNC_B		0x61008
#define I830_VTOTAL_B		0x6100c
#define I830_VBLANK_B		0x61010
#define I830_VSYNC_B		0x61014
#define I830_PIPEBSRC		0x6101c
#define I830_BCLRPAT_B		0x61020

#define I830_DPLL_A		0x06014
#define I830_DPLL_B		0x06018

#define I830_FPA0		0x06040
#define I830_FPA1		0x06044
#define I830_FPA2		0x06048
#define I830_FPA3		0x0604c

#define I830_ADPA		0x61100
#define I830_DVOA		0x61120
#define I830_DVOB		0x61140
#define I830_DVOC		0x61160
#define I830_LVDS		0x61180

#define I830_ADPA_SRCDIM	0x61104
#define I830_DVOA_SRCDIM	0x61124
#define I830_DVOB_SRCDIM	0x61144
#define I830_DVOC_SRCDIM	0x61164
#define I830_LVDS_SRCDIM	0x61184

#define I830_PIPEACONF		0x70008
#define I830_PIPEBCONF		0x71008

#define I830_DSPACTRL		0x70180
#define I830_DSPBCTRL		0x71180

#define I830_DSPABASE		0x70184
#define I830_DSPASTRIDE		0x70188
#define I830_DSPBBASE		0x71184
#define I830_DSPBSTRIDE		0x71188

#define I830_DCLK_0D		0x06000
#define I830_DCLK_1D		0x06004
#define I830_DCLK_0DS		0x06010

#define I830_SWF0		0x71410
#define I830_SWF1		0x71414
#define I830_SWF2		0x71418
#define I830_SWF3		0x7141c
#define I830_SWF4		0x71420
#define I830_SWF5		0x71424
#define I830_SWF6		0x71428

#define I830_LCDTV_C            0x06014
#define I855_LCDTV_C            0x61204

static void release_resource(struct i810_par *par)
{
	if (par->flag & I810_MEMOPEN)
		close(par->mem);
	if (par->flag & I810_MMIO)
		munmap(par->mmio, 512 * 1024);
}

void probe_card_I810(struct i810_par *par)
{
	printf("  DCLK 0D=%08lX 1D=%08lX 0DS=%08lX\n",
		i810_readl(par->mmio, I810_DCLK_0D), i810_readl(par->mmio, I810_DCLK_1D),
	        i810_readl(par->mmio, I810_DCLK_0DS));
	printf("  PWR_CLKC=%08lX HVSYNC=%08lX\n",
		i810_readl(par->mmio, I810_PWR_CLKC),
	        i810_readl(par->mmio, I810_HVSYNC));
	printf("  020cc=%08lX 020d8=%08lX 020dc=%08lX\n",
		i810_readl(par->mmio, 0x20cc), i810_readl(par->mmio, 0x20d8),
		i810_readl(par->mmio, 0x20dc));
	printf("  HTOTAL=%08lX HBLANK=%08lX HSYNC=%08lX\n",
		i810_readl(par->mmio, I810_HTOTAL),
		i810_readl(par->mmio, I810_HBLANK),
		i810_readl(par->mmio, I810_HSYNC));
	printf("  VTOTAL=%08lX VBLANK=%08lX VSYNC=%08lX\n",
		i810_readl(par->mmio, I810_VTOTAL),
		i810_readl(par->mmio, I810_VBLANK),
		i810_readl(par->mmio, I810_VSYNC));
	printf("  OVRACT=%08lX PIXCONF=%08lX 70080=%08lX\n",
		i810_readl(par->mmio, I810_OVRACT),
		i810_readl(par->mmio, I810_PIXCONF),
		i810_readl(par->mmio, 0x70080));
}

void probe_card_I830(struct i810_par *par)
{
	printf("  DCLK 0D=%08lX 1D=%08lX 0DS=%08lX DPLL A=%08lX B=%08lX\n",
		i810_readl(par->mmio, I830_DCLK_0D), i810_readl(par->mmio, I830_DCLK_1D),
		i810_readl(par->mmio, I830_DCLK_0DS),
		i810_readl(par->mmio, I830_DPLL_A), i810_readl(par->mmio, I830_DPLL_B));
	printf("  ADPA=%08lX  DVO A=%08lX B=%08lX C=%08lX  LVDS=%08lX\n",
		i810_readl(par->mmio, I830_ADPA), i810_readl(par->mmio, I830_DVOA),
		i810_readl(par->mmio, I830_DVOB), i810_readl(par->mmio, I830_DVOC),
		i810_readl(par->mmio, I830_LVDS));
	printf("  DIM =%08lX  DIM A=%08lX B=%08lX C=%08lX       %08lX\n",
		i810_readl(par->mmio, I830_ADPA_SRCDIM), i810_readl(par->mmio, I830_DVOA_SRCDIM),
		i810_readl(par->mmio, I830_DVOB_SRCDIM), i810_readl(par->mmio, I830_DVOC_SRCDIM),
		i810_readl(par->mmio, I830_LVDS_SRCDIM));
	printf("  FPA %08lX %08lX %08lX %08lX\n",
		i810_readl(par->mmio, I830_FPA0), i810_readl(par->mmio, I830_FPA1),
		i810_readl(par->mmio, I830_FPA2), i810_readl(par->mmio, I830_FPA3));
	printf("  020cc=%08lX 020d8=%08lX 020dc=%08lX\n",
		i810_readl(par->mmio, 0x20cc), i810_readl(par->mmio, 0x20d8),
		i810_readl(par->mmio, 0x20dc));
	printf("  71280=%08lX 71400=%08lX\n",
		i810_readl(par->mmio, 0x71280), i810_readl(par->mmio, 0x71400));
	printf("  Pipe A\n");
	printf("    HTOTAL=%08lX HBLANK=%08lX HSYNC=%08lX\n",
		i810_readl(par->mmio, I830_HTOTAL_A),
		i810_readl(par->mmio, I830_HBLANK_A),
		i810_readl(par->mmio, I830_HSYNC_A));
	printf("    VTOTAL=%08lX VBLANK=%08lX VSYNC=%08lX\n",
		i810_readl(par->mmio, I830_VTOTAL_A),
		i810_readl(par->mmio, I830_VBLANK_A),
		i810_readl(par->mmio, I830_VSYNC_A));
	printf("    SOURCE=%08lX CONFIG=%08lX 70030=%08lX 70080=%08lX\n",
		i810_readl(par->mmio, I830_PIPEASRC),
		i810_readl(par->mmio, I830_PIPEACONF),
		i810_readl(par->mmio, 0x70030),
		i810_readl(par->mmio, 0x70080));
	printf("  Pipe B\n");
	printf("    HTOTAL=%08lX HBLANK=%08lX HSYNC=%08lX\n",
		i810_readl(par->mmio, I830_HTOTAL_B),
		i810_readl(par->mmio, I830_HBLANK_B),
		i810_readl(par->mmio, I830_HSYNC_B));
	printf("    VTOTAL=%08lX VBLANK=%08lX VSYNC=%08lX\n",
		i810_readl(par->mmio, I830_VTOTAL_B),
		i810_readl(par->mmio, I830_VBLANK_B),
		i810_readl(par->mmio, I830_VSYNC_B));
	printf("    SOURCE=%08lX CONFIG=%08lX 71030=%08lX 71080=%08lX\n",
		i810_readl(par->mmio, I830_PIPEBSRC),
		i810_readl(par->mmio, I830_PIPEBCONF),
		i810_readl(par->mmio, 0x71030),
		i810_readl(par->mmio, 0x71080));
	printf("  Display plane A\n");
	printf("    CTRL=%08lX BASE=%08lX STRIDE=%08lX\n",
		i810_readl(par->mmio, I830_DSPACTRL),
		i810_readl(par->mmio, I830_DSPABASE),
		i810_readl(par->mmio, I830_DSPASTRIDE));
	printf("  Display plane B\n");
	printf("    CTRL=%08lX BASE=%08lX STRIDE=%08lX\n",
		i810_readl(par->mmio, I830_DSPBCTRL),
		i810_readl(par->mmio, I830_DSPBBASE),
		i810_readl(par->mmio, I830_DSPBSTRIDE));
}

char *i810_chip(char **buff_ptr, int *len_ptr, FILE *pci_f, int* chiptype)
{
	int i;
	char *p;

	while (getline(buff_ptr, len_ptr, pci_f) > 0) {
		i = (p = strstr(*buff_ptr, I810STR)) != NULL ||
			(p = strstr(*buff_ptr, I810ESTR)) != NULL ||
			(p = strstr(*buff_ptr, I810_DC100STR_1)) != NULL ||
			(p = strstr(*buff_ptr, I810_DC100STR_2)) != NULL ||
			(p = strstr(*buff_ptr, I810_IGSTR)) != NULL ||
			(p = strstr(*buff_ptr, I810_CFCSTR)) != NULL;
		if (i)
		{
			*chiptype = I810;
			return p;
		}

		i = (p = strstr(*buff_ptr, I830STR)) != NULL ||
#if defined (I865STR)
			(p = strstr(*buff_ptr, I865STR)) != NULL ||
#endif /*defined (I865STR)*/
			(p = strstr(*buff_ptr, I845STR)) != NULL;
		if (i)
		{
			*chiptype = I830;
			return p;
		}

		i = (p = strstr(*buff_ptr, I855STR)) != NULL;
		if (i)
		{
			*chiptype = I855;
			return p;
		}

		i = (p = strstr(*buff_ptr, I915STR)) != NULL;
		if (i)
		{
			*chiptype = I915;
			return p;
		}
	}
	return NULL;
}

unsigned long i810_addr(char **buff_ptr, int *len_ptr, FILE *pci_f)
{
	char *p;

	while (getline(buff_ptr, len_ptr, pci_f) > 0)
		if (strstr(*buff_ptr, NONPRSTR) != NULL) {
			p = strstr(*buff_ptr, MEMSTR);
			if (p != NULL)
				return strtoul(p+sizeof(MEMSTR), NULL, 16);
		}
	return 0;
}

int i810_usage ()
{
	fprintf(stderr, "usage: i810switch [crt on/off] [lcd on/off] [probe]\n");
	fprintf(stderr, "  crt: enables/disables the output to the CRT display\n");
	fprintf(stderr, "  lcd: enables/disables the output to the LCD\n");
	fprintf(stderr, "  probe: dumps the video chipset registers\n");
	fprintf(stderr, "  no options: displays the current output status\n");
	return 1;
}

int main (int argc, char *argv[])
{
	struct i810_par par;
	unsigned long addr;
	int i, crt = -1, lcd = -1, probe = 0, err = 0, count = 0, chiptype, len = 0;
	FILE *pci_f;
	char *buff = NULL;
	char lspcistr[] = CMD_LSPCI " -v -d xxxx:xxxx";
	char *chip;

	putenv("PATH=/sbin:/usr/sbin:/bin:/usr/bin");

	while (--argc > 0) {
		argv++;
		if (!strcmp(argv[0], "crt")) {
			if (--argc > 0) {
				argv++;
				if (!strcmp(argv[0], "on"))
					crt = 1;
				else if (!strcmp(argv[0], "off"))
					crt = 0;
				else
					exit(i810_usage());
			} else {
				exit(i810_usage());
			}
		} else if (!strcmp(argv[0], "lcd")) {
			argv++;
			if (--argc > 0) {
				if (!strcmp(argv[0], "on"))
					lcd = 1;
				else if (!strcmp(argv[0], "off"))
					lcd = 0;
				else
					exit(i810_usage());
			} else {
				exit(i810_usage());
			}
		} else if (!strcmp(argv[0], "probe")) {
			probe = 1;
		} else {
			exit(i810_usage());
		}
	}

	pci_f = popen(CMD_LSPCI " -n", "r");
	if (!pci_f) {
		fprintf(stderr, "Something is wrong with lspci.\n");
		exit(1);
	}
	chip = i810_chip(&buff, &len, pci_f, &chiptype);
	if (chip == NULL) {
		fprintf(stderr, "PCI id of i810 is not recognized.\n");
		exit(1);
	}
	pclose(pci_f);

	{
		char *p = strstr(lspcistr, "xxxx:xxxx");
		if (p == 0) {
			fprintf(stderr, "CMD_LSPCI is wrong.\n");
			exit(1);
		}
		memcpy(p, chip, 9);
	}

	pci_f = popen(lspcistr, "r");
	if (!pci_f) {
		fprintf(stderr, "Something is wrong with lspci.\n");
		exit(1);
	}
	addr = i810_addr(&buff, &len, pci_f);
	if (addr == 0) {
		fprintf(stderr, "Something is wrong with lspci.\n");
		exit(1);
	}
	pclose(pci_f);

	memset(&par, 0, sizeof(struct i810_par));
	par.mem = open("/dev/mem", (crt+lcd == -2) ? O_RDONLY : O_RDWR);
	if (par.mem == -1) {
    	  	i = errno;
		perror("/dev/mem");
		if (i == EACCES && geteuid() != 0)
			fprintf(stderr, "Must have access to `/dev/mem'.\n");
		release_resource(&par);
		exit(1);
	}
	par.flag |= I810_MEMOPEN;

	par.mmio = mmap(NULL, 512 * 1024, (crt+lcd == -2) ? PROT_READ : PROT_WRITE | PROT_READ,
			MAP_SHARED, par.mem, addr);
	if (!par.mmio) {
		release_resource(&par);
		exit(1);
	}

	par.flag |= I810_MMIO;

	if (probe)
	{
		char *chipname = (chiptype == I810) ? "i810" : "i830";
		printf("i810switch %s probe\n", VERSION);
		printf("%s (%s) io=0x%8lX\n", chipname, lspcistr + 12, addr);
		if (chiptype == I810)
		{
			probe_card_I810(&par);
		} else {
			probe_card_I830(&par);
		}
		release_resource(&par);
		exit(0);
	}

	if (crt+lcd == -2) {
		if (chiptype == I810) {
			printf("CRT: ");
			if (i810_readl(par.mmio, I810_PWR_CLKC) & 1)
				printf("on\n");
			else
				printf("off\n");

			printf("LCD: ");
			if (i810_readl(par.mmio, I810_LCDTV_C) & 0xc00)
				printf("off\n");
			else
				printf("on\n");
		}
		else if (chiptype == I830 || chiptype == I915) {
			printf("CRT: ");
			if (i810_readl(par.mmio, I830_ADPA) & 0x80000000)
				printf("on\n");
			else
				printf("off\n");
			printf("LCD: ");
			if (i810_readl(par.mmio, I830_LCDTV_C) & 0x40000000)
				printf("on\n");
			else
				printf("off\n");
		}
		else if (chiptype == I855) {
			printf("CRT: ");
			if (i810_readl(par.mmio, I830_ADPA) & 0x80000000)
				printf("on\n");
			else
				printf("off\n");
			printf("LCD: ");
			if (i810_readl(par.mmio, I855_LCDTV_C) & 1)
				printf("on\n");
			else
				printf("off\n");
		}
	}

	if (crt != -1) {
		if (chiptype == I810) {
			int pwr_clkc = i810_readl(par.mmio, I810_PWR_CLKC);
			int hvsync = i810_readl(par.mmio, I810_HVSYNC);

			if (crt == 0) {
				printf("Disabling CRT display...\n");
				pwr_clkc &= ~1;
				hvsync = 0xa0000;
			} else {
				printf("Enabling CRT display...\n");
				pwr_clkc |= 1;
				hvsync = 0;
			}
			i810_writel(par.mmio, I810_PWR_CLKC, pwr_clkc);
			i810_writel(par.mmio, I810_HVSYNC, hvsync);
		}
		else if (chiptype == I830 || chiptype == I855 || chiptype == I915)
		{
			int i830_adpa = i810_readl(par.mmio, I830_ADPA);
			int dspactrl = i810_readl(par.mmio, I830_DSPACTRL);
			int dspbctrl = i810_readl(par.mmio, I830_DSPBCTRL);
			int pipe = 0;

			/* Find active display plane, and use the same pipe it's using */
			if (dspactrl & (1<<31))
				pipe = (dspactrl & (1<<24)) >> 24;
			else if (dspbctrl & (1<<31))
				pipe = (dspbctrl & (1<<24)) >> 24;
			else
				fprintf(stderr, "Warning: No active display plane detected.  Trying pipe A.\n");
			if (crt == 0) {
				printf("Disabling CRT display...\n");
				i830_adpa = (i830_adpa & ~0x80000000 & ~(pipe<<30)) | 0xc00;
			} else {
				printf("Enabling CRT display...\n");
				i830_adpa = (i830_adpa | 0x80000000 | (pipe<<30)) & ~0xc00;
			}
			i810_writel(par.mmio, I830_ADPA, i830_adpa);
		}
	}

	if (lcd != -1) {
		if (chiptype == I810) {
			int lcdtv_c = i810_readl(par.mmio, I810_LCDTV_C);
			if (lcd == 0) {
				printf("Disabling LCD...\n");
				lcdtv_c |= 0xc00;
			} else {
				printf("Enabling LCD...\n");
				lcdtv_c &= ~(0xc00); 
			}
			i810_writel(par.mmio, I810_LCDTV_C, lcdtv_c);
		}
		else if (chiptype == I830 || chiptype == I915) {
			int lcdtv_c;
			if (lcd == 0) {
				printf("Disabling LCD...\n");
				lcdtv_c = i810_readl(par.mmio, I830_LCDTV_C);
				lcdtv_c &= ~(0x40000000);
				i810_writel(par.mmio, I830_LCDTV_C, lcdtv_c);
			} else {
				printf("Enabling LCD...\n");
				lcdtv_c = i810_readl(par.mmio, I830_LCDTV_C);
				lcdtv_c |= 0x40000000;
				i810_writel(par.mmio, I830_LCDTV_C, lcdtv_c);
			}
		}
		else if (chiptype == I855) {
			int lcdtv_c;
			if (lcd == 0) {
				printf("Disabling LCD...\n");
				lcdtv_c = i810_readl(par.mmio, I855_LCDTV_C);
				lcdtv_c &= ~(1);
				i810_writel(par.mmio, I855_LCDTV_C, lcdtv_c);
			} else {
				printf("Enabling LCD...\n");
				lcdtv_c |= 1;
				i810_writel(par.mmio, I855_LCDTV_C, lcdtv_c);
			}
		}
		else fprintf(stderr, "No LCD support for this chipset.\n");
	}

	release_resource(&par);
	exit(0);
}
