/*  SVGATextMode -- An SVGA textmode manipulation/enhancement tool
 *
 *  Copyright (C) 1995  Koen Gadeyne
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/***
 *** modedata: routines for text/graphics mode grabbing
 *** Copyright (c) 1995 by Koen Gadeyne (kmg@barco.be)
 ***
 ***/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifndef DOS
#include <unistd.h>
#include <asm/io.h>
#endif

#include "misc.h"
#include "vga_prg.h"
#include "confdefs.h"
#include "messages.h"
#include "probe.h"
#include "modedata.h"

#ifdef DOS
#define get_VGA_io_perm(x)
#undef Renounce_SUID
#define Renounce_SUID
extern int optind;
#endif

int roundup2(int num)
 /* round num up to the nearest power of 2 */
{
  int cnt=0;
  while (num)
  {
    num >>=1;
    cnt++;
  }
  return(1<<cnt);
}

void getmode(modestruct* m, int probe_clock, int raw_mode)
{
  float ratio;
  int goldenratio=FALSE;
  int goldenratiox2=FALSE;
  int goldenratiox3=FALSE;

  get_VGA_io_perm(CS_VGA);
  unlock(CS_VGA); /* unlock standard VGA locked registers */

  m->activepix = Get_HOR_DISPL_END()*8;
  m->start_hsync = Get_HSYNC_START()*8;
  m->stop_hsync = Get_HSYNC_END()*8;
  m->totalh = Get_HOR_TOTAL()*8;
  m->activelines = Get_VERT_DISPL_END();
  m->start_vsync = Get_VRETRACE_START();
  m->stop_vsync = Get_VRETRACE_END();
  m->totalv = Get_VERT_TOTAL();
     
  m->font_width = get_charwidth();
  m->font_height = Get_MAX_SCANLINE();
     
  m->h_polarity = Get_HSYNC_POLARITY();
  m->v_polarity = Get_VSYNC_POLARITY();
     
  m->starthbl = Get_HBLANK_START()*8;
  m->endhbl = Get_HBLANK_END()*8;
  m->startvbl = Get_VBLANK_START();
  m->endvbl = Get_VBLANK_END();

  m->txt_gr_mode = Get_TX_GR_Mode();

  /* special mode flags */
  m->mode_flags = 0;
  if (Inb_CRTC(0x09) & 0x80) m->mode_flags |= DOUBLESCAN;
  /* MSL=2 is also used for doublescanning in graphics mode */
  if ((m->txt_gr_mode==MODE_GRAPHICS) && (m->font_height>1)) m->mode_flags |= MULTISCAN;
  if (Inb_SEQ(0x01) & 0x08) m->mode_flags |= CLOCKDIV2;  /* this has already been taken into account by clock probe */
  if (Inb_CRTC(0x17) & 0x40) m->mode_flags |= BYTEMODE; else m->mode_flags |= WORDMODE;
  if (Inb_CRTC(0x14) & 0x40) m->mode_flags |= DOUBLEWORDMODE;
  if (Inb_CRTC(0x17) & 0x04) m->mode_flags |= VERT_DOUBLE;
  if (inb_ATR_CTL(0x10) & 0x40) m->mode_flags |= PCS_DIV2;
  if (Inb_CRTC(0x17) & 0x08) m->mode_flags |= CNT_BY_2;
  if (Inb_GR_CTL(0x05) & 0x20) m->mode_flags |= SHIFT_CGA4C;
  if (Inb_GR_CTL(0x05) & 0x40) m->mode_flags |= SHIFT_CGA256C;
  PDEBUG(("Mode flags (mask) = 0x%08x", m->mode_flags));
  
  m->logical_width = Inb_CRTC(0x13)*8;
  if (m->mode_flags & DOUBLEWORDMODE) m->logical_width *=8;
  else if (m->mode_flags & WORDMODE) m->logical_width *=4;
  else m->logical_width *=2;

/* the "smart" code. This is where the mistakes will be made... */
  if (!raw_mode)
  {
     int wrap=FALSE;
     /* check for "wraparound" of the H and V values: this can be caused by some chipsets using 
     * their own, extended custom registers for setting H/V parameters.
     * A common example of this is the following V-parameters: 1024 5 7 25 instead of
     * 1024 1029 1031 1049
     */
    if (m->start_vsync < m->activelines)
      {
        m->start_vsync += roundup2(m->activelines - m->start_vsync);
        wrap = TRUE;
      }
       
    if (m->stop_vsync < m->start_vsync)
      {
        m->stop_vsync += roundup2(m->start_vsync - m->stop_vsync);
        wrap = TRUE;
      }
       
    if (m->totalv < m->stop_vsync)
      {
        m->totalv += roundup2(m->stop_vsync - m->totalv);
        wrap = TRUE;
      }
      
    if (wrap) printf("# Vertical timings were modified because they wrapped around (probably at 1024)\n");

    /* see if vert resolution was multiplied by 2 (normally for modes with >1024 lines) */
    if (m->mode_flags & VERT_DOUBLE)
    {
      m->activelines *= 2;
      m->start_vsync *= 2;
      m->stop_vsync *= 2;
      m->totalv *= 2;
      m->startvbl *= 2;
      m->endvbl *= 2;
    }
  }
  
  if (probe_clock)
  {
    m->pclock = pixclock((m->totalh/8)*m->font_width, m->totalv);
    m->hor_freq = (m->pclock/(m->totalh/8)/m->font_width)*1000;
    m->vert_refresh = (m->pclock/(m->totalh/8)/m->font_width/m->totalv)*1000000;
  }

/* this part of the smart stuff must be AFTER the clock probing, because it would cause wrong results */
  if (!raw_mode)
  {
    /* take double scanning into account. Must be done AFTER clock probe,
     * because it must not be influenced by this.
     * also not done in text mode, since there the font size _is_ the font size
     */
    if (m->mode_flags & (MULTISCAN|DOUBLESCAN|VERT_DOUBLE))
    {
      int div = ((m->mode_flags & DOUBLESCAN) ? 2 : 1 )*((m->txt_gr_mode==MODE_GRAPHICS) ? m->font_height : 1);
      m->activelines /= div;
      m->start_vsync /= div;
      m->stop_vsync /= div;
      m->totalv /= div;
      m->startvbl /= div;
      m->endvbl /= div;
    }

    ratio = (float)m->activepix / (float)m->activelines;
#if 0
    printf("# original X/Y ratio = %1.2f\n", ratio);
#endif
    goldenratio = (ratio > GOLDEN_LOW) && (ratio < GOLDEN_HIGH);
    if (!goldenratio) goldenratiox2 = (ratio > GOLDEN_LOW*2) && (ratio < GOLDEN_HIGH*2);
    if ((!goldenratio) && (!goldenratiox2)) goldenratiox3 = (ratio > GOLDEN_LOW*3) && (ratio < GOLDEN_HIGH*3);

    /* detect 320x200 -like 256 color modes: when this flag is set, the VGA chip outputs data only every 2
     * pixel clocks by assembling two consecutive (4-bit) "pixels" into one (8-bit) pixel. These are the so-called
     * "packed pixel" modes (is this true?).
     * some modes don't have this bit set, and are STILL 320x200. they will need another detection mechanism.
     *
     * There's a problem with this: many VGA cards have an extended register that overrides this setting, so 
     * that, even when the PCS_DIV2 bit is set, the card is STILL working at its full speed, and not the CGA
     * emulation mode. S3 for example has CRTC reg 0x32, where bit 4 enables 256 color modes. Setting the
     * PCS_DIV2 bit doesn't have any effect anymore. Worse even: XFree86 for some odd reason sets the PCS_DIV2
     * flag, although this bit doesn't do what it should do at that moment (XFree86 uses the S3 "256 color
     * enhanced mode" bit just mentionned instead). So that would cause a 640x480 mode on S3 to be detected as 
     * 320x480, since the PCS_DIV2 bit was set (but non-functional). *Sigh*.
     *
     * If you want to remain device-independent, then you're the sucker, since then the PCS_DIV2 is not a certain
     * indication for half-speed 256 color modes anymore...
     *
     * A quick and dirty workaround: only use PCS_DIV2 when there are <450 (for example) pixels per line.
     * Or maybe detect XWindows? Hmmm.
     */

    if ((m->txt_gr_mode == MODE_GRAPHICS) && (!goldenratio))
    {
      int div=1;
      if (goldenratiox2) div=2;
      if (goldenratiox3) div=3;
      m->activepix /= div;
      m->start_hsync /= div;
      m->stop_hsync /= div;
      m->totalh /= div;
      m->starthbl /= div;
      m->endhbl /= div;
      m->pclock /= div;
      if (div > 1)
      {
        printf("# Horizontal timings were divided by %d because ",div);
        if ((m->mode_flags & PCS_DIV2) && (m->mode_flags & SHIFT_CGA256C) && (m->mode_flags & (DOUBLESCAN|MULTISCAN)) && (div==2))
          printf("possible CGA-emulated mode suspected\n");
        else
          printf("the `golden ratio' (X/Y is around 4/3) rule suggested this\n# This could be a Hi/TrueColor mode\n");
      }
    }
  }

/*
 * todo: interlace detection
 *   two systems: V-timings div-by-2 or not.
 */
  
}
