/*  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.
 */


/***
 *** main SVGA clock programming functions for SVGATextMode
 *** (c) 1995 Koen Gadeyne (kmg@barco.be)
 ***
 ***/

#include <stdio.h>
#include <unistd.h>
#ifndef DOS
#  include <asm/io.h>
#endif
#include <math.h>
#include "misc.h"
#include "vga_prg.h"
#include "wait_vsync.h"
#include "confdefs.h"
#include "setclock.h"
#include "std_clock.h"
#include "clockchip.h"
#include "messages.h"  
#include "parse_opt.h"  
#include "run_extprog.h"  


/*****************************************************************************************************************************/

void clock_check(int result)
{
  if (result < 0)
  {
    switch(result)
    {
      case CLKSEL_DONOTHING:     PWARNING(("Clock selection: Warning: Clock not changed"));
                                 break;
      case CLKSEL_ILLEGAL_NUM:   PERROR(("Clock selection: illegal clock number."));
                                 break;
      case CLKSEL_NULLCLOCK:     PERROR(("Clock selection: 0 MHz clock selected! It would lock up the video card. Aborting..."));
                                 break;
      case CLKSEL_OUT_OF_BOUNDS: PERROR(("Clock selection: Requested clock frequency out of bounds"));
                                 break;
      default: PERROR(("Clock selection: unknown error"));
    }
  }
}


int findclosestclock(float *gclocks, int num_gclocks, float req_clock, float *closest_clock, int optmask)
{
   /* returns closest clock NUMBER when one is found, error code otherwise */

   int i, closest=0;
   int num=num_gclocks;

   *closest_clock = req_clock;  /* start with both equal */

   if (req_clock == 0) return(CLKSEL_NULLCLOCK);
   
   /* create 1/2 clocks when option is set */
   if (optmask & OPT_CLOCKDIV2)
   {
     for (i=0; i<num; i++) gclocks[i+num] = gclocks[i]/2;
     num *= 2;
   }

   /* for (i=0; i<num; i++) PDEBUG(("CLOCK #%d = %f",i, gclocks[i])); */
   
   /* find closest clock frequency */
   for (i=0 ; i < num ; i ++)
   {
      if ( fabs(gclocks[i] - req_clock) < fabs(gclocks[closest] - req_clock) ) { closest = i; }
   }

   *closest_clock = gclocks[closest];
   PDEBUG(("findclosestclock: closest clock nr %d = %1.3f MHz.",closest, *closest_clock));
   if (closest>num_gclocks) PDEBUG((" (clock index #%d is actually index #%d divided by 2)", closest, closest-num_gclocks));
   
   if (closest < 0) return(CLKSEL_DONOTHING);
   if (closest > num) return(CLKSEL_ILLEGAL_NUM);
   if (*closest_clock == 0.0) return(CLKSEL_NULLCLOCK);
   
   return(closest);
}



int GetClock(int chipset, float* clocks, int num_clocks, float freq, float *closestfreq, int optmask, int report_error)
 /* look for correct clock index (for indexed clock chips), and check for out-of-bound clocks */
{
   int result;
   
   switch(chipset)
   {
     case CS_CIRRUS: /* should do some range checking here */
                     result = 0; /* suppose it can make any frequency */
                     break;
     default: result = findclosestclock(clocks, num_clocks, freq, closestfreq, optmask);
   }
   if (report_error) clock_check(result);
   return(result);
}



void SetClock(int chipset, float* clocks, int num_clocks, float freq, int optmask)
  /* "freq" is in MHz ! */
{
   int result, divby2=0;
   
   result = GetClock(chipset, clocks, num_clocks, freq, &freq, optmask, TRUE);
  /* clock number is supposed to be in the allowable range by now.
     error checking should have been done before.
     No error checking will be done in clock-setting routine! */
     
   if ((optmask & OPT_CLOCKDIV2) && (result > num_clocks-1))
   {
      divby2 = 1;
      result -= num_clocks; /* make clock selection routine pick the real clock (before division by 2) */
   }

   PDEBUG(("Setting Clock to %f MHz", freq));
   SYNCRESET_SEQ;
   switch(chipset)
   {
    case CS_VGA : SET_CLOCKBITS_0_1(result);
                  break;
    case CS_CIRRUS : CirrusClockSelect(freq, optmask);
                     break;
    case CS_S3     : s3ClockSelect(result, optmask);
                     break;
    case CS_ET4000 : ET4000ClockSelect(result,optmask);
                     break;
    case CS_TVGA8900 :
    case CS_TVGA9000 : TVGAClockSelect(chipset, num_clocks, result);
                       break;
    case CS_PVGA1:
    case CS_WDC90C0X:
    case CS_WDC90C1X:
    case CS_WDC90C2X:
    case CS_WDC90C3X: WDCClockSelect(chipset, num_clocks, result, optmask);
                      break;
    case CS_ATI:
    case CS_ATIMACH32: ATIClockSelect(chipset, result);
                       break;
    case CS_VIDEO7: Video7ClockSelect(result);
                    break;
    default: PERROR(("SetClock: unknown chip set #%d", chipset));
   }

   if (optmask & OPT_CLOCKDIV2)
   {
      Outbit_SEQ(1,3,divby2);
      if (divby2) PDEBUG(("Clock (%f) needed 'division by 2' feature.", freq));
   }

   ENDRESET_SEQ;
   usleep(50000); /* let PLL clock synthesizer stabilize */
   clock_check(result);
}


/*****************************************************************************************************************************/

void do_clock(int chipset, t_clockdef* ck, float clock, int clockindex, int optionmask)
{
  char tempstr[1024]="";

  /* program clocks in V-blanking only, avoiding video memory corruption and system hangs (?) */

  safe_wait_vsync(); /* wait for VSYNC... if there is one */
  SCREEN_OFF;

  /* MClk programming */
  if (ck->MClk != MCLK_NOT_DEFINED)
  {
    if (ck->type & CK_CLOCKCHIP)
    {
      switch(ck->clockchiptype)
      {
        case CLKCHIP_S3_SDAC:
        case CLKCHIP_S3GENDAC:
        case CLKCHIP_ICS5300:
        case CLKCHIP_ICS5342:
        case CLKCHIP_ICS5341:
             set_clockchip_Mclock(chipset, ck->clockchiptype, (long)(ck->MClk*1000000), optionmask);
             break;
        default: PERROR(("MClk programming not supported on selected Clock chip"));
      }
      PMESSAGE(("Setting MClk to %3.2f MHz", ck->MClk));
    }
    else PERROR(("MClk programming not supported for non-clockchip VGA cards (yet)"));
  }

  safe_wait_vsync(); /* wait for VSYNC... if there is one */
  /* PixClock programming */
  if (ck->type & CK_CLOCKPROG)
  {
     sprintf(tempstr,"%s %1.3f %d", ck->ck_prog_path, clock, clockindex);
     SYNCRESET_SEQ;
     Run_extern_Prog(tempstr);
     ENDRESET_SEQ;
  }
  else
    if (ck->type & CK_CLOCKCHIP)
      set_clockchip_clock(chipset, ck->clockchiptype, (long)(clock*1000000), optionmask);
  else
  {
    SetClock(chipset, ck->clocks, ck->num_clocks, clock, optionmask);
  }
  SCREEN_ON;
}

 
