/*
**  tseng3.c - get ET4000 graphics register values
**  Copyright (C) 1993  Tommy Frandsen, Harm Hanemaayer, Hartmut Schirmer
**
**  This program is free software;
**
**  Permission is granted to any individual or institution to use, copy,
**  or redistribute this executable so long as it is not modified and
**  that it is not sold for profit.
**
**  LIKE ANYTHING ELSE THAT'S FREE, TSENG3 IS PROVIDED AS IS AND
**  COMEs WITH NO WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED.
**  IN NO EVENT WILL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES
**  RESULTING FROM THE USE OF THIS SOFTWARE.
*/

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

/* VGA index register ports */
#define CRT_I   0x3D4   /* CRT Controller Index (mono: 0x3B4) */
#define ATT_IW  0x3C0   /* Attribute Controller Index & Data Write Register */
#define GRA_I   0x3CE   /* Graphics Controller Index */
#define SEQ_I   0x3C4   /* Sequencer Index */
#define PEL_IW  0x3C8   /* PEL Write Index */

/* VGA data register ports */
#define CRT_D   0x3D5   /* CRT Controller Data Register (mono: 0x3B5) */
#define ATT_R   0x3C1   /* Attribute Controller Data Read Register */
#define GRA_D   0x3CF   /* Graphics Controller Data Register */
#define SEQ_D   0x3C5   /* Sequencer Data Register */
#define MIS_R   0x3CC   /* Misc Output Read Register */
#define MIS_W   0x3C2   /* Misc Output Write Register */
#define IS1_R   0x3DA   /* Input Status Register 1 (mono: 0x3BA) */
#define PEL_D   0x3C9   /* PEL Data Register */

/* VGA indexes max counts */
#define CRT_C   24              /* 24 CRT Controller Registers */
#define ATT_C   21              /* 21 Attribute Controller Registers */
#define GRA_C   9               /* 9  Graphics Controller Registers */
#define SEQ_C   5               /* 5  Sequencer Registers */
#define MIS_C   1               /* 1  Misc Output Register */
#define EXT_C   13              /* 11 SVGA Extended Registers */

/* VGA registers saving indexes */
#define CRT     0               /* CRT Controller Registers start */
#define ATT     CRT+CRT_C       /* Attribute Controller Registers start */
#define GRA     ATT+ATT_C       /* Graphics Controller Registers start */
#define SEQ     GRA+GRA_C       /* Sequencer Registers */
#define MIS     SEQ+SEQ_C       /* General Registers */
#define EXT     MIS+MIS_C       /* SVGA Extended Registers */
#define TOTAL	EXT+EXT_C	/* # or registers values */

unsigned char vga_regs[TOTAL];
static int do_all = 0;
FILE *output = NULL;

void port_out(unsigned char value, unsigned short port)
{
   asm mov dx,port
   asm mov al,value
   asm out dx,al
}


unsigned char port_in(unsigned short port)
{
    asm mov dx,port
    asm in al,dx
    return (_AL);
}

int dactype(void)
{
	union REGS cpu_regs;

	cpu_regs.x.ax=0x10F1;
	cpu_regs.x.bx=0x2EFF;

	int86(0x10, &cpu_regs, &cpu_regs);
	if (cpu_regs.x.ax != 0x0010)
		return (-1);
	else
		return (cpu_regs.h.bl);
}

void puts2(char *s) {
  if (output != NULL)
    fprintf(output, "%s\n", s);
  while (*s != '\0') {
    if (*s == '\n') putch('\r');
    putch (*(s++));
  }
  putch('\r'); putch('\n');
}

int printf2(char *fmt, ...)
{
  va_list  argptr;
  char str[140], *s;
  int cnt;

  va_start( argptr, fmt );

  if (output != NULL)
    vfprintf(output, fmt, argptr);
  cnt = vsprintf( str, fmt, argptr );
  s = str;
  while (*s) {
    switch (*s) {
      case '\n' : putch('\r');
		  break;
      case '\t' : cprintf("%*s", 8-((wherex()-1)&7), "");
		  ++s;
		  continue;
    }
    putch(*(s++));
  }
  va_end( argptr );
  return( cnt );
}

void get_dac(void)
{
    int dac;

    dac = dactype();
    switch (dac)
    {
	case -1:
		printf2 ("/* Dac detection BIOS call returned an error */\n");
		break;
	case 0:
		printf2 ("/* Standard VGA dac detected */\n");
		printf2 ("#define DAC_TYPE 0\n");
		break;
	case 1:
		printf2 ("/* Sierra SC1148x HiColor dac detected */\n");
		printf2 ("#define DAC_TYPE 1\n");
		break;
	case 2:
		printf2 ("/* Diamond Speedstar 24 24bit dac or Sierra Mark2/Mark3 dac detected */\n");
		break;
	case 3:
		printf2 ("/* AT&T ATT20c491/2 15/16/24 bit dac detected */\n");
		printf2 ("#define DAC_TYPE 9\n");
		break;
	case 4:
		printf2 ("/* AcuMos ADAC1 15/16/24 bit dac detected */\n");
		break;
	case 8:
		printf2 ("/* Music 15/16/24 bit dac (AT&T compatible) detected */\n");
		printf2 ("#define DAC_TYPE 9\n");
		break;
	default:
		printf2 ("/* Unknown HiColor dac (%d) detected */\n", dac);
		break;
    }
}

#define TickTime (1.0/1193182.0)

#define init_timer() do { \
  asm mov al, 034h;       \
  asm out 043h, al;       \
  asm xor al, al;         \
  asm out 040h, al;       \
  asm out 040h, al;       \
} while(0)

#define ReadTimer(dst) do { \
       asm { mov   al, 4;   \
	     out   43h, al; \
	     in    al, 40h; \
	     mov   bl, al;  \
	     in    al, 40h; \
	     mov   ah, al;  \
	     mov   al, bl;  \
	     not   ax     } \
      (dst) = _AX;	    \
    } while (0);

#define _wait_(ID,VAL)  \
w_##ID##1:              \
  asm in    al, dx;     \
  asm test  al, VAL;    \
  asm jne   w_##ID##1;  \
w_##ID##2:              \
  asm in    al, dx;     \
  asm test  al, VAL;    \
  asm je    w_##ID##2

#define wait_horizontal(ID,port)  do { _DX=(port); _wait_(ID##ch,8);} while(0)
#define wait_vertical(ID,port)    do { _DX=(port); _wait_(ID##cv,1);} while(0)
#define __loop__(ID,port,loops,msk)     do { \
		register int cnt = (loops);  \
		_DX=(port);         	     \
		do {                         \
		  _wait_(ID,msk);            \
		} while (--cnt > 0);         \
	} while(0)
#define loop_vertical(ID,port,loops)    __loop__(ID##cv,(port),(loops),1)


int interlaced(void) {
  return (vga_regs[EXT+5] & 0x80) ? 1 : 0;
}

int calc_vtotal(void) {
  int total;

  total = (vga_regs[EXT+5]&2) ? 1024 : 0;
  switch (vga_regs[CRT+7]&0x21) {
    case 0x01 : total += 256; break;
    case 0x20 : total += 512; break;
    case 0x21 : total += 768; break;
  }
  total += vga_regs[CRT+6];
  return total + 2;
}

double measure_horizontal(void)
{
  short start, stop;
  long  diff;

  disable();
  init_timer();
  wait_horizontal(mv0,0x3da);
  wait_vertical(mv0,0x3da);
  ReadTimer(start);
  loop_vertical(mv, 0x3da, 200);
  ReadTimer(stop);
  enable();
  diff = stop-start;
  if (diff < 0) diff += 65536L;
  return 200/(TickTime*((double)diff));
}

#define ASK_CONT 0
#define ASK_SKIP 1
#define ASK_ALL  2

int ask(char *expl)
{
    int ch;

    cprintf("\r\n\n"
	    "ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»\r\n"
	    "º WARNING: Improper use of this program may destroy your monitor º\r\n"
	    "ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¼\r\n\n"
	    "%s\r\n\n"
	    "Type SPACE to skip, ESC to cancel program, RETURN to continue ... ",
	    expl);
    do {
      ch = getch();
      if (ch == 27) exit(1);
      switch (ch) {
	case 27  : exit(1);
	case ' ' : return ASK_SKIP;
	case 'A' : return ASK_ALL;
	case '\r': return ASK_CONT;
      }
    } while (1);
}


void get_clocks(void)
{
    union REGS cpu_regs;
    unsigned char temp;
    int i;
    double clock[8], horzbase, baseosc;

    if (!do_all)
      switch (ask("Measure clock frequencies")) {
	case ASK_SKIP : return;
	case ASK_ALL  : do_all = 1; break;
	default       : break;
      }

    cpu_regs.h.ah = 0x00;
    cpu_regs.h.al = 0x13;
    int86(0x10, &cpu_regs, &cpu_regs);
    port_out(0x03, 0x3BF);  /* Unprotect ET4000 registers */
    port_out(0xA0, 0x3D8);
    port_out(0x00, ATT_IW); /* disable video */
    port_out(7, SEQ_I);     /* devide by 4 */
    temp = port_in(SEQ_D);
    port_out(temp | 0x41, SEQ_D);

    baseosc = 25175;		/* Base frequency */
    switch (temp & 0x41) {
      case 0x41 : baseosc *= 4; break;
      case 0x40 : baseosc *= 2; break;
    }
    clock[0] = baseosc;
    clock[1] = baseosc * 28322.0 / 25175.0;
    horzbase = measure_horizontal();
    for (i=2; i < 8; ++i) {
      temp = (port_in(MIS_R) & 0xF3) | ((i&3)<<2);
      port_out(temp, MIS_W);
      port_out(0x34, CRT_I);
      temp = (port_in(CRT_D) & 0xFD) | ((i&4)>>1);
      port_out(temp, CRT_D);
      clock[i] = baseosc*measure_horizontal()/horzbase;
    }

    cpu_regs.h.ah = 0x00;
    cpu_regs.h.al = 0x03;
    int86(0x10, &cpu_regs, &cpu_regs);

    printf2("\n#define CLOCK_VALUES {\t\\\n");
    for (i = 0; i < 8; ++i)
      printf2("\t/* %d */ %5.0f%s\t\\\n", i, clock[i],(i==7?"":","));
    printf2("\t}\n");
}

void one_mode(int vesa_mode, int mode, int colbits, int funny_card)
{
    union REGS cpu_regs;
    int i, skip = 0, modeok = 0;
    int x=0, y, col, size;
    char resol[100], colors[100], *desc;
    char   *verttxt;
    double vertical, horizontal;

    switch (mode) {
	case 0x13: x =  320; y =  200; break;
	case 0x25: x =  640; y =  480; break;
	case 0x29: x =  800; y =  600; break;
	case 0x2d: x =  640; y =  350; break;
	case 0x2e: x =  640; y =  480; break;
	case 0x2f: x =  640; y =  400; break;
	case 0x30: x =  800; y =  600; break;
	case 0x37: x = 1024; y =  768; break;
	case 0x38: x = 1024; y =  768; break;
	case 0x3d: x = 1280; y = 1024; break;
	default  : switch (vesa_mode) {
		     case 0x107:
		     case 0x119:
		     case 0x11a:
		     case 0x11b: x = 1280; y = 1024; break;
		     default   : return;
		   }
		   break;
    }
    col = (1<<colbits);
    size = (int)((((unsigned long) x) * ((unsigned long) y)) >> 10);
    if (col == 16) size >>= 2;

    switch (colbits) {
	case 15: size*=2; strcpy(colors, "32K"); desc = "HiColor ";   break;
	case 16: size*=2; strcpy(colors, "64K"); desc = "HiColor ";   break;
	case 24: size*=3; strcpy(colors, "16M"); desc = "TrueColor "; break;
	default: sprintf(colors, "%d", col); desc = ""; break;
    }
    sprintf(resol, "%dx%dx%s", x, y, colors);

    if (!do_all) {
      char expl[100];
      sprintf(expl, "BIOS mode 0x%2X : %s", mode, resol);
      switch(ask(expl)) {
	case ASK_SKIP : skip = 1; break;
	case ASK_ALL  : do_all = 1; break;
	default       : break;
      }
    }

    if (!skip) {
      /* First try VESA mode */
      if (vesa_mode != 0) {
	cpu_regs.x.ax = 0x4f02;
	cpu_regs.x.bx = vesa_mode;
	int86(0x10, &cpu_regs, &cpu_regs);
	modeok = (cpu_regs.x.ax == 0x004f);
      }
      /* Try std ET4000 mode numbers if VESA failed */
      if (!modeok && mode != 0) {
	switch (colbits)
	{
	    case 15:
	    case 16:
		cpu_regs.x.ax=0x10F0;
		cpu_regs.h.bl=mode;
		break;
	    case 24:
		switch (funny_card)
		{
		    case 1:
			cpu_regs.x.ax=0x10E0;
			cpu_regs.h.bl=0x2e;
			break;
		    case 2:
			cpu_regs.x.ax=0x10f0;
			cpu_regs.h.bl=0x3e;
			break;
		    case 0:
		    default:
			cpu_regs.x.ax=0x10F0;
			cpu_regs.h.bh=mode;
			cpu_regs.h.bl=0xFF;
			break;
		}
		break;
	    case 0:
	    default:
		cpu_regs.h.ah = 0x00;
		cpu_regs.h.al = mode;
		break;
	}

	int86(0x10, &cpu_regs, &cpu_regs);
	modeok = !(  (colbits > 8 && cpu_regs.x.ax != 0x0010)
		   ||(colbits <=8 && cpu_regs.x.ax == mode  ) );
	vesa_mode = 0;
      }

      if (modeok) {
	/* get VGA register values */
	for (i = 0; i < CRT_C; i++) {
	    port_out(i, CRT_I);
	    vga_regs[CRT+i] = port_in(CRT_D);
	}
	for (i = 0; i < ATT_C; i++) {
	    if (col == 16 && i < 16)
	      vga_regs[ATT+i] = i;
	    else {
	      port_in(IS1_R);
	      port_out(i, ATT_IW);
	      vga_regs[ATT+i] = port_in(ATT_R);
	    }
	}
	for (i = 0; i < GRA_C; i++) {
	    if (i==1 && col == 16)
	      vga_regs[GRA+i] = 0x0F;
	    else {
	      port_out(i, GRA_I);
	      vga_regs[GRA+i] = port_in(GRA_D);
	    }
	}
	for (i = 0; i < SEQ_C; i++) {
	    port_out(i, SEQ_I);
	    vga_regs[SEQ+i] = port_in(SEQ_D);
	}
	vga_regs[MIS] = port_in(MIS_R);

	/* get extended CRT registers */
	for (i = 0; i < 8; i++) {
	     port_out(0x30+i, CRT_I);
	     vga_regs[EXT+i] = port_in(CRT_D);
	}
	port_out(0x3f	, CRT_I);
	vga_regs[EXT+8] = port_in(CRT_D);

	/* get extended sequencer register */
	port_out(7, SEQ_I);
	vga_regs[EXT+9] = port_in(SEQ_D);

	/* get some other ET4000 specific registers */
	vga_regs[EXT+10] = port_in(0x3c3);
	vga_regs[EXT+11] = port_in(0x3cd);

	/* get extended attribute register */
	port_in(IS1_R);   /* reset flip flop */
	port_out(0x16, ATT_IW);
	vga_regs[EXT+12] = port_in(ATT_R);

	/* Measure video timing */
	horizontal = measure_horizontal();
	/* switch back to text mode */
	cpu_regs.h.ah = 0x00;
	cpu_regs.h.al = 0x03;
	int86(0x10, &cpu_regs, &cpu_regs);
      }
    }

    if (vesa_mode)
      printf2("\n/* VESA %smode 0x%03X", desc, vesa_mode);
    else
      printf2("\n/* ET4000 %sBIOS mode 0x%02X", desc, mode);
    printf2(" -- %s", resol);
    if ( skip || !modeok) {
	printf2(" : NOT SUPPORTED */\n");
	if (size <= 1024)
	  printf2("#define g%s_regs DISABLE_MODE\n", resol);
	return;
    }
    printf2(" */\n");

    if (colbits == 16) {
      printf2("#define g%s_regs g%dx%dx32K_regs\n", resol, x, y);
      return;
    }

    verttxt = interlaced() ? " (interlaced)" : "";
    vertical   = horizontal / calc_vtotal();
    printf2("/* Video timing:\tVertical frequency   : %4.1fHz%s\n",
						vertical, verttxt);
    printf2("\t\t\tHorizontal frequency : %4.1fKHz  */\n", horizontal/1000.0);
    printf2("static unsigned char g%s_regs[%d] = {\n  ", resol, TOTAL);
    for (i = 0; i < CRT_C; i++)
	printf2("%s0x%02X,", (i==12?"\n  ":""), vga_regs[CRT+i]);
    printf2("\n  ");
    for (i = 0; i < ATT_C; i++)
	printf2("%s0x%02X,", (i==12?"\n  ":""), vga_regs[ATT+i]);
    printf2("\n  ");
    for (i = 0; i < GRA_C; i++)
	printf2("0x%02X,",vga_regs[GRA+i]);
    printf2("\n  ");
    for (i = 0; i < SEQ_C; i++)
	printf2("0x%02X,",vga_regs[SEQ+i]);
    printf2("\n  0x%02X,",vga_regs[MIS]);
    printf2("\n  0x%02X",vga_regs[EXT]);
    for (i = 1; i < EXT_C; i++)
	printf2(",%s0x%02X", (i==12?"\n  ":""), vga_regs[i+EXT]);
    printf2("\n};\n");
}

void usage(void) {
  cputs("tseng3 <output_file> [SS24]");
  exit(1);
}

void main(int argc, char* argv[])
{
    int funny_card = 0;

    if (argc >= 2)
      output = fopen(argv[1], "w");

    if (output != NULL)
      fputs("/*\n   ( File generated by tseng3.exe )\n\n", output);
    puts2("tseng3 v1.2, Copyright (C) 1993  Tommy Frandsen, Harm Hanemaayer");
    puts2("and Hartmut Schirmer\n");
    puts2("Permission is granted to any individual or institution to use, copy, or");
    puts2("redistribute this executable so long as it is not modified and that it is");
    puts2("not sold for profit.\n");
    puts2("LIKE ANYTHING ELSE THAT'S FREE, TSENG3 IS PROVIDED AS IS AND COMES WITH");
    puts2("NO WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED. IN NO EVENT WILL");
    puts2("THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF");
    puts2("THIS SOFTWARE.");
    if (output != NULL)
      fputs("*/\n", output);
    puts2("");

    if (argc >= 2 && output == NULL)
	usage();

    if (argc>=3)
	funny_card = (strcmp("SS24", argv[2]) == 0) ? 1 : 2;

    get_dac();
    get_clocks();

    one_mode(0x10d, 0x13, 15, funny_card);  /* 320x200x32K */
    one_mode(0x10e, 0x13, 16, funny_card);  /* 320x200x64K */
    one_mode(0x10f, 0x13, 24, funny_card);  /* 320x200x16M */
    one_mode(0x101, 0x2e,  8, funny_card);  /* 640x480x256 */
    one_mode(0x110, 0x2e, 15, funny_card);  /* 640x480x32K */
    one_mode(0x111, 0x2e, 16, funny_card);  /* 640x480x64K */
    one_mode(0x112, 0x2e, 24, funny_card);  /* 640x480x16M */
    one_mode(0x102, 0x29,  4, funny_card);  /* 800x600x16 */
    one_mode(0x103, 0x30,  8, funny_card);  /* 800x600x256 */
    one_mode(0x113, 0x30, 15, funny_card);  /* 800x600x32K */
    one_mode(0x114, 0x30, 16, funny_card);  /* 800x600x64K */
    one_mode(0x115, 0x30, 24, funny_card);  /* 800x600x16M */
    one_mode(0x104, 0x37,  4, funny_card);  /* 1024x768x16 */
    one_mode(0x105, 0x38,  8, funny_card);  /* 1024x768x256 */
    one_mode(0x116, 0x38, 15, funny_card);  /* 1024x768x32K */
    one_mode(0x117, 0x38, 16, funny_card);  /* 1024x768x64K */
    one_mode(0x118, 0x38, 24, funny_card);  /* 1024x768x16M */
    one_mode(0x106, 0x3D,  4, funny_card);  /* 1280x1024x16 */
    one_mode(0x107, 0x00,  8, funny_card);  /* 1280x1024x256 */
    one_mode(0x119, 0x00, 15, funny_card);  /* 1280x1024x32K */
    one_mode(0x11a, 0x00, 16, funny_card);  /* 1280x1024x64K */
    one_mode(0x11b, 0x00, 24, funny_card);  /* 1280x1024x16M */
    puts2("\n/* --- ET4000 specific modes */\n#ifdef _DYNAMIC_ONLY_");
    one_mode(0x000, 0x2d,  8, funny_card);  /* 640x350x256 */
    one_mode(0x000, 0x2d, 15, funny_card);  /* 640x350x32K */
    one_mode(0x000, 0x2d, 16, funny_card);  /* 640x350x64K */
    one_mode(0x000, 0x2d, 24, funny_card);  /* 640x350x16M */
    one_mode(0x100, 0x2f,  8, funny_card);  /* 640x400x256 */
    one_mode(0x000, 0x2f, 15, funny_card);  /* 640x400x32K */
    one_mode(0x000, 0x2f, 16, funny_card);  /* 640x400x64K */
    one_mode(0x000, 0x2f, 24, funny_card);  /* 640x400x16M */
    puts2("\n#endif /* defined(_DYNAMIC_ONLY_ALL_) */\n");
}

