/* vgaset.c: set VGA registers for X server.
 * Greg Lehey, 9 October 1992 */
/* Copyright 1992 LEMIS, Schellnhausen 2, W-6324 Feldatal, Germany
 * Telephone +49-6637-1488, fax +49-6637-1489
 * This software may be used for noncommercial purposes provided that this copyright notice
 * is not removed. Contact lemis (lemis%lemis@germany.eu.net or lemis@lemis.de) for
 * permission to distribute or use for commercial purposes.
 */

#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>
#include <termios.h>
#include "svga.h"
#define INLINE inline

float dotclock = 0;					    /* dot clock frequency */
char *crtc_reg_name [] =
{
  "HT", "HDE", "SHB", "EHB", "SHR", "EHR", "VT", "OVERFLOW", "PRS",
  "MSL", "CS", "CE", "SAH", "SAL", "CLH", "CLL", "VRS", "EVR", "VDE",
  "OFF", "UL", "VBS", "VBE", "Mode Control", "LC"
    };

/* These are the ``real'' values of the registers. They need to be collected from the
 * VGA registers or scattered to them */
struct crtc_regs
{
  short int hde,					    /* horizontal display end */
	    shr,					    /* start of horizontal retrace */
	    ehr,					    /* end of horizontal retrace */
	    ht,						    /* horizontal total */
	    vde,					    /* vertical display end */
	    vbs,					    /* vertical blanking start */
	    vbe,					    /* vertical blanking end */
	    vrs,					    /* vertical retrace start */
	    evr,					    /* end of vertical retrace */
	    vt,						    /* vertical total */
	    overflow,					    /* overflow register */
	    msl;					    /* and maximum scan line reg */
  }
current_regs,
  saved_regs;

int verbose = 0;					    /* set to show more info */

static INLINE void
  outb (short port, char val)
{
   ioperm(port, 1, 1);
  __asm__ volatile ("out%B0 %0,%1" : :"a" (val), "d" (port));
  }

static INLINE unsigned char
  inb (short port)
{
  unsigned int ret;
   ioperm(port, 1, 1);  
  __asm__ volatile ("in%B0 %1,%0" : "=a" (ret) : "d" (port));
  return ret;
  }

short int getreg (int address, int index)
{
  int val;
  outb (address, index);				    /* set the register number */
  val = inb (address + 1);
  return val;
  }

void setreg (int address, int index, short int value)
{
  outb (address, index);				    /* set the register number */
  outb (address + 1, value);
  }

void get_crtc_regs ()
{
  current_regs.hde = (getreg (CRTCL_INDEX, HDE) + 1) << 3;
  current_regs.shr = getreg (CRTCL_INDEX, SHR) << 3;
  current_regs.ehr = (getreg (CRTCL_INDEX, EHR) & 0x1f) << 3;
  if (current_regs.ehr <= (current_regs.shr & 0xff))	    /* wraparound, */
    current_regs.ehr += 0x100;				    /* adjust */
  current_regs.ehr += (current_regs.shr & ~0xff);	    /* this is relative to shr */
  current_regs.ht = (getreg (CRTCL_INDEX, HT) + 5) << 3;
  current_regs.overflow = getreg (CRTCL_INDEX, OVERFLOW);
  switch (current_regs.overflow & 0x21)			    /* get the high-order VT stuff */
    {
  case 0:
    current_regs.vt = 0; break;
  case 1:
    current_regs.vt = 256; break;
  case 0x20:
    current_regs.vt = 512; break;
  case 0x21:
    current_regs.vt = 768;
    }
  current_regs.vt += getreg (CRTCL_INDEX, VT) + 2;
  switch (current_regs.overflow & 0x84)			    /* get the high-order VRS stuff */
    {
  case 0:
    current_regs.vrs = 0; break;
  case 4:
    current_regs.vrs = 256; break;
  case 0x80:
    current_regs.vrs = 512; break;
  case 0x84:
    current_regs.vrs = 768;
    }
  current_regs.vrs += getreg (CRTCL_INDEX, VRS);
  if ((current_regs.evr = getreg (CRTCL_INDEX, EVR) & 0xf) <= (current_regs.vrs & 0xf)) /* wraparound */
    current_regs.evr += 0x10;
  current_regs.evr += (current_regs.vrs & ~0xf);	    /* get base */
  switch (current_regs.overflow & 0x42)			    /* get the high-order VDE stuff */
    {
  case 0:
    current_regs.vde = 0; break;
  case 2:
    current_regs.vde = 256; break;
  case 0x40:
    current_regs.vde = 512; break;
  case 0x42:
    current_regs.vde = 768;
    }
  current_regs.vde += getreg (CRTCL_INDEX, VDE) + 1;
  current_regs.vbs = getreg (CRTCL_INDEX, VBS);		    /* start of blanking register */
  if (current_regs.overflow & 8)			    /* bit 8 of VBS reg */
    current_regs.vbs += 256;				    /* add in */
  if (getreg (CRTCL_INDEX, MSL) & 0x20)			    /* bit 9 of VBS reg */
    current_regs.vbs += 512;				    /* add in */
  current_regs.vbe = getreg (CRTCL_INDEX, VBE) & 0x7f;	    /* end of blanking register */
  if (current_regs.vbe < (current_regs.vbs & 0x7f))	    /* wraparound */
    current_regs.vbe += 128;
  current_regs.vbe += current_regs.vbs & ~0x7f;		    /* high-order comes from vbs */
  }

void show_crtc_regs ()
{
  int reg;						    /* register number */
  short int val;					    /* value stored in register */

  get_crtc_regs ();
  printf ("%d %d %d %d\t%d %d %d %d\n",
	  current_regs.hde,
	  current_regs.shr,
	  current_regs.ehr,
	  current_regs.ht,
	  current_regs.vde,
	  current_regs.vrs,
	  current_regs.evr,
	  current_regs.vt);				    /* print clocks line */
  if (dotclock)						    /* show frequencies */
    {
    float horfreq = dotclock / current_regs.ht;
    printf ("Horizontal frequency: %5.0f Hz, vertical frequency: %3.1f Hz\n",
	    horfreq,
	    horfreq / current_regs.vt );
    printf ("Horizontal sync %5.2f us, vertical sync %5.2f us\n",
	    (current_regs.ehr - current_regs.shr) / dotclock * 1000000,
	    (current_regs.evr - current_regs.vrs) / horfreq * 1000000 );
    printf ("Horizontal retrace %5.2f us, vertical retrace %5.2f us\n",
	    (current_regs.ht - current_regs.hde) / dotclock * 1000000,
	    (current_regs.vt - current_regs.vde) / horfreq * 1000000 );
    }
  if (verbose)
    {
    for (reg = 0; reg < CRTC_REG_COUNT; reg++)
      {
      outb (CRTCL_INDEX, reg);				    /* set the register number */
      val = inb (CRTCL_DATA);
      if (! (reg & 3))
	printf ("\n");					    /* 4 to a line */
      printf ("%s\t%x\t", crtc_reg_name [reg], val);
      }
    printf ("\n");
    }
  }

/* set vertical blanking start */
void setvrs ()
{
  current_regs.overflow = getreg (CRTCL_INDEX, OVERFLOW) & ~8;
  if (current_regs.vrs & 0x100)
    current_regs.overflow |= 8;
  setreg (CRTCL_INDEX, OVERFLOW, current_regs.overflow);    /* take care of bit 8 */
  current_regs.msl = getreg (CRTCL_INDEX, MSL) & ~0x20;
  if (current_regs.vrs & 0x200)
    current_regs.msl |= 0x20;
  setreg (CRTCL_INDEX, MSL, current_regs.msl);		    /* take care of bit 9 */
  setreg (CRTCL_INDEX, VRS, current_regs.vrs & 0xff);	    /* and the first 8 bits */
  }

void setevr ()
{
  setreg (CRTCL_INDEX, EVR, (current_regs.evr & 0xf) | (getreg (CRTCL_INDEX, EVR) & ~0xf));
  }

void setvt ()
{
  short int myvt = current_regs.vt - 2;			    /* adjust for VGA format */
  current_regs.overflow = getreg (CRTCL_INDEX, OVERFLOW) & ~ 0x21;
  current_regs.overflow |= ((short int []) {0, 1, 0x20, 0x21}) [myvt >> 8]; /* put in high order bits */
  setreg (CRTCL_INDEX, OVERFLOW, current_regs.overflow);
  setreg (CRTCL_INDEX, VT, myvt & 0xff);		    /* and low-order 8 bits */
  }

void setshr ()
{
  setreg (CRTCL_INDEX, SHR, current_regs.shr >> 3);
  }

void setehr ()
{
  setreg (CRTCL_INDEX, EHR, (getreg (CRTCL_INDEX, EHR) & 0xe0) | ((current_regs.ehr >> 3) & 0x1f));
  }

void setht ()
{
  setreg (CRTCL_INDEX, HT, (current_regs.ht >> 3) - 5);
  }

void main (int argc, char *argv [])
{
  int i;
  struct termios term_status;


  if (tcgetattr (0, &term_status))
    {
    printf ("Can't get terminal attributes for stdin: %s\n", strerror (errno));
    exit (1);
    }
  term_status.c_lflag &= ~ICANON;
  if (tcsetattr (0, TCSAFLUSH, &term_status))
    {
    printf ("Can't set terminal attributes for stdin: %s\n", strerror (errno));
    exit (1);
    }
  get_crtc_regs ();					    /* save current values */
  memcpy (&saved_regs, &current_regs, sizeof (struct crtc_regs));
  for (i = 1; i < argc; i++)
    {
    if (*argv [i] == '-')				    /* flag */
      {
      switch (argv [i] [1])
	{
      case 'd':
	if ((dotclock = atof (&argv [i] [2])) < 200)	    /* get it */
	  dotclock *= 1000000;				    /* convert to MHz */
	break;
      case 's':						    /* set the registers first */
	if (argc < (i + 7))				    /* not enough args */
	  {
	  puts ("Not enough arguments to -s option");
	  exit (1);
	  }
	current_regs.shr = atoi (argv [++i]);		    /* get the values to set the regs to */
	current_regs.ehr = atoi (argv [++i]);
	current_regs.ht = atoi (argv [++i]);
	setshr ();
	setehr ();
	setht ();
	current_regs.vrs = atoi (argv [++i]);
	current_regs.evr = atoi (argv [++i]);
	current_regs.vt = atoi (argv [++i]);
	setvrs ();
	setevr ();
	setvt ();
	break;
      case 'v':
	verbose = 1;
	break;
	}
      }
    else
      {
      printf ("Usage:\n%s [-ddot clock] [-s shr ehr ht vrs evr vt] [-v]\n", argv [0]);
      exit (1);
      }
    }
  show_crtc_regs ();					    /* first time round */
  while (1)
    {
    char c = getchar ();
    switch (c)
      {
    case 'l':						    /* decrease left */
      puts ("eft margin decrease");
      if (current_regs.ht > current_regs.ehr)
	{
	current_regs.ht -= 8;
	setht ();
	}
      else
	printf ("Can't reduce left margin further\n\007");
      break;
    case 'L':						    /* increase left */
      puts ("eft margin increase");
      if (current_regs.ht < 2048)			    /* space to increase */
	{
	current_regs.ht += 8;
	setht ();
	}
      else
	printf ("Can't increase horizontal total further\007\n");
      break;
    case 'r':						    /* decrease right */
      puts ("ight margin decrease");
      if (current_regs.shr > current_regs.hde)		    /* can decrease */
	{
	current_regs.shr -= 8;
	current_regs.ehr -= 8;
	current_regs.ht -= 8;
	setshr ();
	setehr ();
	setht ();
	}
      else
	printf ("Can't decrease right margin further\007\n");
      break;
    case 'R':						    /* increase right */
      puts ("ight margin increase");
      if (current_regs.ht < 2048)			    /* space to increase */
	{
	current_regs.ht += 8;
	current_regs.ehr += 8;
	current_regs.shr += 8;
	setshr ();
	setehr ();
	setht ();
	}
      else
	printf ("Can't increase right margin further\007\n");
      break;
    case 'h':						    /* decrease horizontal sync */
      puts ("orizontal sync decrease");
      if (current_regs.ehr > (current_regs.shr + 8))	    /* can decrease */
	{
	current_regs.ehr -= 8;
	setehr ();
	}
      else
	printf ("Can't decrease horizontal sync further\007\n");
      break;
    case 'H':						    /* increase horizontal sync */
      puts ("orizontal sync increase");
      if (current_regs.ehr < current_regs.ht)		    /* space to increase */
	{
	current_regs.ehr += 8;
	setehr ();
	}
      else
	printf ("Can't increase horizontal sync further\007\n");
      break;
/* Vertical operations */
    case 't':						    /* decrease top */
      puts ("op margin decrease");
      if (current_regs.vt > current_regs.evr)
	{
	current_regs.vt -= 1;
	setvt ();
	}
      else
	printf ("Can't reduce top margin further\n\007");
      break;
    case 'T':						    /* increase top */
      puts ("op margin increase");
      if (current_regs.vt < 1024)			    /* space to increase */
	{
	current_regs.vt += 1;
	setvt ();
	}
      else
	printf ("Can't increase vertical total further\007\n");
      break;
    case 'b':						    /* decrease bottom */
      puts ("ottom margin decrease");
      if (current_regs.vrs > current_regs.vde)		    /* can decrease */
	{
	current_regs.vrs -= 1;
	current_regs.evr -= 1;
	current_regs.vt -= 1;
	setvrs ();
	setevr ();
	setvt ();
	}
      else
	printf ("Can't decrease bottom margin further\007\n");
      break;
    case 'B':						    /* increase bottom */
      puts ("ottom margin increase");
      if (current_regs.vt < 1024)			    /* space to increase */
	{
	current_regs.vt += 1;
	current_regs.evr += 1;
	current_regs.vrs += 1;
	setvrs ();
	setevr ();
	setvt ();
	}
      else
	printf ("Can't increase bottom margin further\007\n");
      break;
    case 'v':						    /* decrease vertical sync */
      puts ("ertical sync decrease");
      if (current_regs.evr > current_regs.vrs)		    /* can decrease */
	{
	current_regs.evr -= 1;
	setevr ();
	}
      else
	printf ("Can't decrease vertical sync further\007\n");
      break;
    case 'V':						    /* increase vertical sync */
      puts ("ertical sync increase");
      if (current_regs.evr < current_regs.vt)		    /* space to increase */
	{
	current_regs.evr += 1;
	setevr ();
	}
      else
	printf ("Can't increase vertical sync further\007\n");
      break;
    case '\n':						    /* do nothing */
      break;
    case '?':						    /* reset saved values */
      puts ("\bReset to standard values");
      memcpy (&current_regs, &saved_regs, sizeof (struct crtc_regs));
      setvrs ();
      setevr ();
      setvt ();
      setshr ();
      setehr ();
      setht ();
      break;
    case 'q':
      puts ("uit");
      /* FALLTHROUGH */
    case '\004':
      exit (0);
    default:
      printf ("Invalid command\007\n");
      }
    show_crtc_regs ();
    }
  }
