/*
  >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  vmode - XF86Config Monitor Video Mode Generator, v.0.1

  Introduction
    If you've hacked on an Xconfig file to get your display working in X386
    or XFree86, then you probably know what this program is for.  It
    generates an Xconfig (or XF86Config) mode definition for your display.
    It's based entirely on "The Hitchhiker's Guide to X386/XFree86 Video
    Timing (or, Tweaking your Monitor for Fun and Profit)" by Eric Raymond,
    et. al.  My copy was in /var/X11R6/lib/doc/VideoModes.doc (slakware
    2.02) or something like that.  I would recommend reading it if you're
    interested in what this program does.

  Getting the Dot Clocks
    Before you use this program, it's best to know what dot clocks are
    available from your video card.  Fortunately, the X server should be
    able to probe your card to get these for you.  You can use one of the
    sample XF86Config files (e.g. XF86Config.eg), but make sure you comment
    out all of the "Clocks" lines.  Run "X -probeonly" 10 to 20 times and
    write down the clock frequencies that occur most frequently for each
    column of clock output.

  Horizontal Scan Frequency and Video Bandwidth
    If you have a manual for your video display, look in the specifications
    section.  Horizontal scan frequency is given as a range for multisync
    displays.  Write down the largest number in the range.  Also note the
    video bandwidth for your display.

  Refresh Rate
    This is how many times per second your screen is refreshed.  If the
    value is too low, your screen will appear to flicker (and probably give
    you a headache).  The VESA ergonomic standard is 72 Hz.  Most people
    can live with 60 Hz.  It's best to stay somewhere between these two
    values.

  Compiling vmode
    To compile vmode, use a command line like:  cc -o vmode vmode.c -lm

  Running vomde
    Now that you have an executable and you understand the command line
    arguments, here's the command synopsis:

      vmode <refresh rate> <max horizontal scan freq. (khz)> [dot clock (Mhz)]

    If You Know max horizontal scan freq:
      Leave off the dot clock argument for your first run of vmode.  The
      program will calculate a dot clock to you get started.  Pick the dot
      clock from the list you made above that is closest to the value vmode
      generated (must NOT be higher than your display's bandwidth).  Run
      the program again with the dot clock you picked.

    If You Don't:
      Pick a high value for max horizontal scan freq. (like 200).  Run
      vmode with dot clocks from the list you made above.  It's best to
      try numbers from the middle of the range first.

  The Output
    Put the output in your XF86Config file under the "Monitor" Section.
    For example:

      Section "Monitor"
          Identifier     "Generic Monitor"
          VendorName     "Unknown"
          ModelName      "Unknown"
          Bandwidth      30
          HorizSync      30-37
          VertRefresh    50-90
          Mode "712x534"    # Generated by vmode 60 37 30
              DotClock      30.0
              HTimings      712  744  864  896
              VTimings      534  536  541  561
          EndMode
          # HSF = 33.5 khz
          # VSF = 59.7 Hz
      EndSection

    My favorite way to do this is to edit XF86Config with vi(1), move the
    cursor down to the VertRefresh line (for example), and type:

       :r !vmode ...

  Tweaking
    Given favorable circumstances, vmode can usually generate a mode that
    will be stable if not optimal.  You may have to try a few runs to get
    something that works, however.  From there, try a program called
    vgaset to fine tune your screen.

    The more data you have on your display, the closer vmode can come to an
    optimal setting.  If, for example, your know your display's horizontal
    and vertical sync pulse durations and its horizontal and vertical guard
    times, you can compile them into vmode with a compile line like:

      cc -DHSP=0.0000031 -DHGT=29.0 -DVSP=0.000091 -DVGT=0.0 -o vmode ...

  Author:  <a href="mailto:dsnyder@mindspring.com">dsnyder@mindspring.com</a> (David C. Snyder) 11/10/94
  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
*/

#include <stdio.h>
#include <math.h>

#ifndef HSP
#define HSP    0.0000037   /* Horizontal Sync Pulse Duration in seconds */
#endif

#ifndef HGT
#define HGT   32.0         /* Horizontal Guard Time in clock ticks */
#endif

#ifndef VSP
#define VSP    0.00015     /* Vertical Sync Pulse Duration in seconds */
#endif

#ifndef VGT
#define VGT    2.0         /* Vertical Guard Time in clock ticks */
#endif

main(int argc, char *argv[])
{
  double hsfm,   /*  max horizontal sync frequency              */
         hsf,    /*  horizontal sync frequency                  */
         rr,     /*  refresh rate                               */
         dcf,    /*  driving clock frequency (or dot clock)     */
         hfl,    /*  horizontal frame length                    */
         hr,     /*  horizontal screen resolution               */
         hdif,   /*  a temporary variable                       */
         vr,     /*  vertical screen resolution                 */
         vfl,    /*  vertical frame length                      */
         mem,    /*  minimum video memory required for hr & vr  */
         hspt,   /*  horizontal sync pulse time (duration)      */
         hsp,    /*  horizontal sync pulse width                */
         hgt,    /*  horizontal guard time                      */
         vspt,   /*  vertical sync pulse time (duration)        */
         vsp,    /*  vertical sync pulse width                  */
         vgt,    /*  vertical guard time                        */
         sh1,    /*  start tick of horizontal sync pulse        */
         sh2,    /*  end tick of horizontal sync pulse          */
         sv1,    /*  start tick of vertical sync pulse          */
         sv2;    /*  end tick of vertical sync pulse            */

  if (argc < 3)
  {
    fprintf(stderr, "Usage: vmode <refresh rate> "
                    "<max horizontal scan freq. (khz)> "
                    "[dot clock (Mhz)]\n");
    exit(1);
  }

  sscanf(argv[1], "%lf", &rr);
  sscanf(argv[2], "%lf", &hsfm);
  hsfm *= 1000.0;

  if (argc > 3)
  {
    sscanf(argv[3], "%lf", &dcf);
    dcf *= 1000000.0;
    hsf = sqrt(dcf*rr*1.05*0.75/1.25);
    hsf = (hsf < hsfm) ? hsf : hsfm;
  }
  else
  {
    hsf = hsfm;
    dcf = 1.25*hsf*hsf/rr/1.05/0.75;
  }
  dcf = floor(dcf/100000.0+0.5)*100000.0;

  hspt = HSP;
  vspt = VSP;
  hgt  = HGT;
  vgt  = VGT;
  hfl  = ceil(dcf/hsf/8.0) * 8.0;
  hsp  = floor(dcf*hspt/8.0+0.5) * 8.0;
  hr   = floor(dcf/hsf*0.8/8.0+0.5) * 8.0;
  hdif = (hsp+2.0*hgt)-(hfl-hr);
  if (hdif > 0.0) /* not enough space between hfl and hr for hsp and 2 hgt's */
  {
    hfl = floor((hfl+hdif/2.0)/8.0+0.5) * 8.0;
    hsf = dcf/hfl;
    hr  = floor((hr-hdif/2.0)/8.0+0.5) *8.0;
  }
  vr   = 0.75*hr;
  vfl  = floor(1.05*vr+0.5);
  mem  = ceil(hr*vr/1024.0);
  vsp  = floor(vspt*dcf/hfl+0.5);
  sh1  = floor(hr+hgt+0.5);
  sh2  = floor(hfl-hgt+0.5);
  sv1  = floor(vr+vgt+0.5);
  sv2  = floor(vr+vgt+vsp+0.5);

  printf("    Mode \"%.0lfx%.0lf\"\n", hr, vr);
  printf("        DotClock      %.1lf\n", dcf/1000000.0);
  printf("        HTimings      %.0lf %4.0lf %4.0lf %4.0lf\n",hr,sh1,sh2,hfl);
  printf("        VTimings      %.0lf %4.0lf %4.0lf %4.0lf\n",vr,sv1,sv2,vfl);
  printf("    EndMode\n");
  printf("    # HSF = %.1lf khz\n", dcf/hfl/1000.0);
  printf("    # VSF = %.1lf Hz\n", dcf/hfl/vfl);

  return (0);
}


