/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/chips/util/modClock.c,v 1.5 2001/05/09 19:57:06 dbateman Exp $ */

#ifdef __NetBSD__
#  include <sys/types.h>
#  include <machine/pio.h>
#  include <machine/sysarch.h>
#else
#  if defined(SVR4) && defined(i386)
#    include <sys/types.h>
#    ifdef NCR
       /* broken NCR <sys/sysi86.h> */
#      define __STDC
#      include <sys/sysi86.h>
#      undef __STDC
#    else
#      include <sys/sysi86.h>
#    endif
#    ifdef SVR4
#      if !defined(sun)
#        include <sys/seg.h>
#      endif
#    endif
#    include <sys/v86.h>
#    if defined(sun)
#      include <sys/psw.h>
#    endif
#  endif
#  include "AsmMacros.h"
#endif /* NetBSD */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef Lynx
#include <fnmatch.h>
#endif

#ifdef __NetBSD__
#  define SET_IOPL() i386_iopl(3)
#  define RESET_IOPL() i386_iopl(0)
#else
#  if defined(SVR4) && defined(i386)
#    ifndef SI86IOPL
#      define SET_IOPL() sysi86(SI86V86,V86SC_IOPL,PS_IOPL)
#      define RESET_IOPL() sysi86(SI86V86,V86SC_IOPL,0)
#    else
#      define SET_IOPL() sysi86(SI86IOPL,3)
#      define RESET_IOPL() sysi86(SI86IOPL,0)
#    endif
#  else
#    ifdef linux
#      define SET_IOPL() iopl(3)
#      define RESET_IOPL() iopl(0)
#    else
#      define SET_IOPL() (void)0
#      define RESET_IOPL() (void)0
#    endif
#  endif
#endif

#define tolerance 0.01 /* +/- 1% */


#define CT65520 0x1
#define CT65525 0x2
#define CT65530 0x3
#define CT64200 0x4

#define CT65535 0x11
#define CT65540 0x12
#define CT65545 0x13
#define CT65546 0x14
#define CT65548 0x15
#define CT64300 0x16

#define CT65550 0x31
#define CT65554 0x32
#define CT65555 0x33
#define CT68554 0x34
#define CT69000 0x35
#define CT69030 0x36

#define IS_Programmable(X) X&0x10
#define IS_HiQV(X) X&0x20

#define DotClk 0
#define MemClk 1
#define IS_MemClk(X) X&0x1

int compute_clock (
		   unsigned int ChipType,
		   double target,
		   double Fref,
		   unsigned int ClkMaxN,
		   unsigned int ClkMaxM,
		   unsigned int *bestM,
		   unsigned int *bestN,
		   unsigned int *bestP,
		   unsigned int *bestPSN) {

  unsigned int M, N, P, PSN, PSNx;

  double bestError = 0, abest = 42, bestFout = 0;

  double Fvco, Fout;
  double error, aerror;

  unsigned int M_min = 3;
  unsigned int M_max = ClkMaxM;

  if (target < 1e6){
    fprintf (stderr, "MHz assumed, changed to %g MHz\n", target);
    target *= 1e6;
  }

  if (target > 220.0e6) {
    fprintf (stderr, "too large\n");
    return 1;
  }

  /* Other parameters available onthe 65548 but not the 65545, and
     not documented in the Clock Synthesizer doc in rev 1.0 of the
     65548 datasheet:

     + XR30[4] = 0, VCO divider loop uses divide by 4 (same as 65545)
		 1, VCO divider loop uses divide by 16

     + XR30[5] = 1, reference clock is divided by 5

     I haven't put in any support for those here.  For simplicity,
     they should be set to 0 on the 65548, and left untouched on
     earlier chips.  */

  for (PSNx = ((ChipType == CT69000) || (ChipType == CT69030)) ? 1 : 0; 
       PSNx <= 1; PSNx++) {
    unsigned int low_N, high_N;
    double Fref4PSN;

    PSN = PSNx ? 1 : 4;

    low_N = 3;
    high_N = ClkMaxN;

    while (Fref / (PSN * low_N) > (((ChipType == CT69000) || 
				    (ChipType == CT69030)) ? 5.0e6 : 2.0e6))
      low_N++;
    while (Fref / (PSN * high_N) < 150.0e3)
      high_N--;

    Fref4PSN = Fref * 4 / PSN;
    for (N = low_N; N <= high_N; N++) {
      double tmp = Fref4PSN / N;

      for (P = (IS_HiQV(ChipType) && (ChipType != CT69000) &&
		(ChipType != CT69030)) ? 1 : 0; P <= 5; P++) {
	double Fvco_desired = target * (1 << P);
	double M_desired = Fvco_desired / tmp;
	/* Which way will M_desired be rounded?  Do all three just to
	   be safe.  */
	unsigned int M_low = M_desired - 1;
	unsigned int M_hi = M_desired + 1;

	if (M_hi < M_min || M_low > M_max)
	  continue;

	if (M_low < M_min)
	  M_low = M_min;
	if (M_hi > M_max)
	  M_hi = M_max;

	for (M = M_low; M <= M_hi; M++) {
	  Fvco = tmp * M;
	  if (Fvco <= ((ChipType == CT69000) || (ChipType == CT69030) ?
		       100e6 : 48.0e6))
	    continue;
	  if (Fvco > 220.0e6)
	    break;

	  Fout = Fvco / (1 << P);

	  error = (target - Fout) / target;

	  aerror = (error < 0) ? -error : error;
	  if (aerror < abest) {
	    abest = aerror;
	    bestError = error;
	    *bestM = M;
	    *bestN = N;
	    *bestP = P;
	    *bestPSN = PSN;
	    bestFout = Fout;
	  }
	}
      }
    }
  }

  if (abest < tolerance) {
    printf ("best: M=%d N=%d P=%d PSN=%d\n", *bestM, *bestN, *bestP, *bestPSN);

    if (bestFout > 1.0e6)
      printf ("Fout = %g MHz", bestFout / 1.0e6);
    else if (bestFout > 1.0e3)
      printf ("Fout = %g kHz", bestFout / 1.0e3);
    else
      printf ("Fout = %g Hz", bestFout);
    printf (", error = %g\n", bestError);
    return 0;
  }
  printf ("can't do it with less than %g error\n", bestError);
  return 1;
}

int set_clock(
	      unsigned int ChipType,
	      unsigned int ClockType,
	      unsigned int ProgClock,
	      unsigned int M,
	      unsigned int N,
	      unsigned int P,
	      unsigned int PSN) {

  unsigned int tmp, idx;

  SET_IOPL();

  idx = inb(0x3D6);
  if (IS_HiQV(ChipType)) {
    if (IS_MemClk(ClockType)) {
      printf ("XRCC = 0x%02X\n", M - 2);
      printf ("XRCD = 0x%02X\n", N - 2);
      printf ("XRCE = 0x%02X\n", (0x80 | (P * 16 + (PSN == 1))));

      outb(0x3D6, 0xCE); /* Select Fix MClk before */
      tmp = inb(0x3D7);
      outb(0x3D7, tmp & 0x7F);
      outb(0x3D6, 0xCC);
      outb(0x3D7, (M - 2));
      outb(0x3D6, 0xCD);
      outb(0x3D7, (N - 2));
      outb(0x3D6, 0xCE);
      outb(0x3D7, (0x80 | (P * 16 + (PSN == 1))));
    } else {
      printf ("XR%X = 0x%02X\n", 0xC0 + 4 * ProgClock, M - 2);
      printf ("XR%X = 0x%02X\n",  0xC1 + 4 * ProgClock, N - 2);
      printf ("XR%X = 0x%02X\n",  0xC2 + 4 * ProgClock, 0);
      printf ("XR%X = 0x%02X\n",  0xC3 + 4 * ProgClock, P * 16 + (PSN == 1));

      outb(0x3D6, 0xC0 + 4 * ProgClock);
      outb(0x3D7, (M - 2));
      outb(0x3D6, 0xC1 + 4 * ProgClock);
      outb(0x3D7, (N - 2));
      outb(0x3D6, 0xC2 + 4 * ProgClock);
      outb(0x3D7, 0x0);
      outb(0x3D6, 0xC3 + 4 * ProgClock);
      outb(0x3D7, (P * 16 + (PSN == 1)));
    }
   } else {
    printf ("XR30 = 0x%02X\n", P * 2 + (PSN == 1));
    printf ("XR31 = 0x%02X\n", M - 2);
    printf ("XR32 = 0x%02X\n", N - 2);
    outb(0x3D6, 0x33);
    tmp = inb(0x3D7);
    if (IS_MemClk(ClockType)) {
      outb(0x3D7, tmp | 0x20);     
    } else {
      outb(0x3D7, tmp & ~0x20);
    }
    outb(0x3D6, 0x30);
    outb(0x3D7, (P * 2 + (PSN == 1)));
    outb(0x3D6, 0x31);
    outb(0x3D7, (M - 2));
    outb(0x3D6, 0x32);
    outb(0x3D7, (N - 2));
    outb(0x3D6, 0x33);
    outb(0x3D7, tmp);
  }
  outb(0x3D6, idx);
  RESET_IOPL();
  return 0;
}

unsigned int probe_chip(void) {

  unsigned int ChipType, temp;

  SET_IOPL();

  outb(0x3D6, 0x00);
  temp = inb(0x3D7);
  ChipType = 0;
  if (temp != 0xA5) {
    if ((temp & 0xF0) == 0x70) {
      ChipType = CT65520;
    }
    if ((temp & 0xF0) == 0x80) {  /* could also be a 65525 */
      ChipType = CT65530;
    }
    if ((temp & 0xF0) == 0xA0) {
      ChipType = CT64200;
    }
    if ((temp & 0xF0) == 0xB0) {
      ChipType = CT64300;
    }
    if ((temp & 0xF0) == 0xC0) {
      ChipType = CT65535;
    }
    if ((temp & 0xF8) == 0xD0) {
      ChipType = CT65540;
    }
    if ((temp & 0xF8) == 0xD8) {
      switch (temp & 0x07) {
      case 3:
	ChipType = CT65546;
	break;
      case 4:
	ChipType = CT65548;
	break;
      default:
	ChipType = CT65545;
      }
    }
  }
  /* At this point the chip could still be a HiQV, so check for
   * that. This test needs some looking at */
  if ((temp != 0) && (ChipType == 0)) {
    outb(0x3D6, 0x02);
    temp = inb(0x03D7);
    if (temp == 0xE0) {
      ChipType = CT65550;
    }
    if (temp == 0xE4) {
      ChipType = CT65554;
    }
    if (temp == 0xE5) {
      ChipType = CT65555;
    }
    if (temp == 0xF4) {
      ChipType = CT68554;
    }
    if (temp == 0xC0) {
      ChipType = CT69000;
    }
    if (temp == 0x30) {
      outb(0x3D6, 0x03);
      temp = inb(0x03D7);
      if (temp == 0x0C) ChipType = CT69030;
    }
  }
 
  RESET_IOPL();

  if (ChipType == 0) {      /* failure */
    fprintf(stderr, "Not a Chips and Technologies Chipset\n");
  }
  
  return ChipType;
}


int main (int argc, char *argv[]) {
  double target;
  double Fref = 14318180;
  unsigned int M, N, P, PSN, ChipType, ClockType, progclock;

  switch (argc) {
  case 2:
    progclock = 2;
    target = atof (argv[1]);
    break;
  case 3:
    progclock = abs(atof (argv[1]));
    target = atof (argv[2]);
    break;
  default:
    fprintf (stderr, "usage: %s [-0|-1|-2] freq\n", argv[0]);
    return 1;
  }

  ClockType = DotClk;
#ifndef Lynx
  if (! fnmatch("*memClock",argv[0],FNM_PATHNAME)) {
#else
  if (strstr("memClock",argv[0]) != NULL) {
#endif
    ClockType = MemClk;
  }

  ChipType = probe_chip();
  if (!ChipType) {
    return 1;
  }

  if (! IS_Programmable(ChipType)) {
    fprintf(stderr, "No programmable Clock!\n");
    return 1;
  }
  
  if (IS_HiQV(ChipType)) {
    if (! compute_clock(ChipType, target, Fref, 63, 127, &M, &N, &P, &PSN)) {
      return set_clock(ChipType, ClockType, progclock, M, N, P, PSN);
    } else {
      return 1;
    }
  } else {
    if (! compute_clock(ChipType, target, Fref, 127, 127, &M, &N, &P, &PSN)) {
      return set_clock(ChipType, ClockType, progclock, M, N, P, PSN);
    } else {
      return 1;
    }
  }
}
