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


/***
 *** 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 "confdefs.h"
#include "std_clock.h"
#include "clockchip.h"
#include "parse_opt.h"
#include "messages.h"  


void LegendClockSelect(int no)
 /* used in both S3 and ET4000 cards (according to XFREE 3.1.1) */
{
  PDEBUG(("Using Sigma Legend clock selection"));
  outb((inb(VGA_MISC_R) & 0xF3) | ((no & 0x10) >> 1) | (no & 0x04), VGA_MISC_W);
  Outb_CRTC(0x34, 0x00);
  Outb_CRTC(0x34, 0x02);
  Outb_CRTC(0x34, (no & 0x08) >> 2);
  SET_CLOCKBITS_0_1(no);
}


void TVGAClockSelect(int chipset, int num_clocks, int no)
{
    PDEBUG(("TVGAClockSelect: clock no %d (of total %d) for chipset %d.", no, num_clocks, chipset));
    SET_CLOCKBITS_0_1(no);
    Outb_SEQ(0x0B, 0); Inb_SEQ(0x0B);  /* Select "new mode regs" for CS2 and TVGA9000 CS3. */
#if 0
    Outb_SEQ(0x0D, Inb_SEQ(0x0D) & 0xF9);  /* set clock dividers to no division */
#else
    Outbit_SEQ(0x0D, 1, 0);  /* set clock dividers (div 2) to no division */
    Outbit_SEQ(0x0D, 2, 0);  /* set clock dividers (div 4 ?) to no division */
#endif    
    Outbit_SEQ(0x0D, 0, no & 0x04);  /* bit 2 of clock no */
    if (chipset == CS_TVGA9000)
    {
       Outbit_SEQ(0x0D, 6, no & 0x08);  /* bit 3 of clock no */
    }
    if ((chipset == CS_TVGA8900) && (num_clocks > 7))
    {
       Outb_SEQ(0x0B, 0);    /* Switch to Old Mode for CS3 */
       Outbit_SEQ(0x0E, 4, no & 0x08);      /* bit 3 of clock no */
    }
}


void s3ClockSelect(int no, int optmask)
  /* clock number is supposed to be in the allowable range by now. error checking should have been done before */
{
   if (optmask & OPT_LEGEND) LegendClockSelect(no);
   else
   {
     SET_CLOCKBITS_0_1(3);    /* select clock #3 to allow extra clocks to be used */
     Outb_CRTC(0x42, no & 0x0f);
   }
}


void ATIClockSelect(int chipset, int no)
{
   /* for MACH32 chipsets, bits 2 and 3 of the clock selection number must be
      exchanged to comply with the XFREE MACH server clock ordering.
      Derived through experimentation */
   if (chipset==CS_ATIMACH32) no = ( no & 0xfffffff3) | ((no & 0x04)<<1) | ((no & 0x08)>>1);
   ATI_PUTEXTREG(0xBE, (ATI_GETEXTREG(0xBE) & 0xEF) | ((no << 2) & 0x10));  /* clock bit 2 */
   ATI_PUTEXTREG(0xB9, (ATI_GETEXTREG(0xB9) & 0xFD) | ((no >> 2) & 0x02));  /* clock bit 3 */
   ATI_PUTEXTREG(0xB8, (ATI_GETEXTREG(0xB8) & 0x3F) | ((no << 2) & 0xC0));  /* clock divider */
   SET_CLOCKBITS_0_1(no);    /* clock bits no 0 and 1 , must be set LAST */
 }


void WDCClockSelect(int chipset, int num_clocks, int no, int optmask)
/* almost literal copy of the WDC driver clock selection code from XFREE, (C) Thomas Roell */
{
  int MClkIndex = 8;
  int save_cs2 = 0;

  if ((chipset == CS_WDC90C1X) || (chipset == CS_WDC90C2X) || (chipset ==CS_WDC90C3X))
    if (optmask & OPT_SWAP_HIBIT) save_cs2 = 0; else save_cs2 = 4;
  else
    if (optmask & OPT_SWAP_HIBIT) save_cs2 = 4; else save_cs2 = 0;

  if (num_clocks > 8) MClkIndex = num_clocks - 1;

  if ((no == MClkIndex) && (chipset != CS_PVGA1))
  {
    /*
     * On all of the chipsets after PVGA1, you can feed MCLK as VCLK.
     */
    PDEBUG(("WDCClockSelect: Selecting MCLOCK as VCLOCK"));
    Outbit_CRTC(0x2E, 4, 1);
  }
  else
  {
    /*
     * Disable feeding MCLK to VCLK
     */
    if (chipset != CS_PVGA1) Outbit_CRTC(0x2E, 4, 0);

    SET_CLOCKBITS_0_1(no);
    
  if ((chipset != CS_PVGA1) && (chipset != CS_WDC90C0X))
    Outbit_GR_CTL(0x0C, 1, (no & 0x04) ^ save_cs2); /* bit 2 of clock no */ 
  if (chipset==CS_WDC90C3X)
    Outbit_SEQ(0x12, 2, (no & 0x08) ^ 0x08); /* inverse clock bit 3 */
  }
}


void ET4000ClockSelect(int no, int optmask)
{
   int hibit = 1;
   if (optmask & OPT_HIBIT_HIGH) hibit = 1;
   if (optmask & OPT_HIBIT_LOW) hibit = 0;
   PDEBUG(("ET4000 Clock selection: hibit option = hibit_%s", (hibit==0) ? "low" : "high"));
   /* program clock */
   if (optmask & OPT_LEGEND) LegendClockSelect(no);
   else
   {
     SET_CLOCKBITS_0_1(no);
     Outbit_CRTC(0x34, 1, no & 0x04);                                   /* bit 2 of clock no */
     if (!(optmask & OPT_ET4000_ALTCLK))
     {
       Outbit_SEQ(7, 6, (hibit == 0) ? (no & 0x08) : (no & 0x08)^0x08);   /* bit 3 of clock no */
     }
     else
     {
       PDEBUG(("ET4000 clock selection: Using alternate clock selection mechanism"));
       Outbit_SEQ(7, 6, 0); /* just to make sure */
       Outbit_CRTC(0x31, 6, (hibit == 0) ? (no & 0x08) : (no & 0x08)^0x08);   /* bit 3 of clock no */
       Outbit_CRTC(0x31, 7, no & 0x10);    /* bit 4 of clock no */
     }
   }
}



void CirrusClockSelect(float freq, int optmask)
{
  /* a problem with the Cirrus Clockchip code from XFREE causes 58 MHz to be 
     extremely unstable */
  if ((freq>57.9) && (freq<58.1)) freq = 57.9;

  /* set DRAM speed (MCLK) = (14318 * val / 8) */
  if (optmask & OPT_XFAST_DRAM) Outb_SEQ(0x1f,0x25);   /* 66.2 MHz */
  if (optmask & OPT_FAST_DRAM)  Outb_SEQ(0x1f,0x22);   /* 60.8 MHz */
  if (optmask & OPT_MED_DRAM)   Outb_SEQ(0x1f,0x1f);   /* 55.5 MHz */
  if (optmask & OPT_SLOW_DRAM)  Outb_SEQ(0x1f,0x1c);   /* 50.1 MHz */

  SET_CLOCKBITS_0_1(3);
  CirrusSetClock( (int) (freq * 1000));  /* must be AFTER MCLK setting, since CirrusSetClock() uses MCLK */
}



void Video7ClockSelect(int no)
/* from XFREE3.0 server */
{
  if (no < 2) SET_CLOCKBITS_0_1(no);
  else SET_CLOCKBITS_0_1(2);
  outw(0xA4 | (no << 10), 0x3C4);
}


