text: /* Copyright (C) Charles Forsyth
   * See /doc/license/NOTICE.Plan9-9k.txt for details about the licensing.
   */
  /* Portions of this file are Copyright (C) 9front's team.
   * See /doc/license/9front-mit for details about the licensing.
   * See http://code.9front.org/hg/plan9front/ for a list of authors.
   */
  #include "u.h"
  #include "../port/lib.h"
  #include "mem.h"
  #include "dat.h"
  #include "fns.h"
  
  /*
   * 8254 Programmable Interval Timer and compatibles.
   */
  enum {					/* I/O ports */
  	Timer1		= 0x40,
  	Timer2		= 0x48,		/* Counter0 is watchdog (EISA) */
  
  	Counter0	= 0,		/* Counter 0 Access Port */
  	Counter1	= 1,		/* Counter 1 Access Port */
  	Counter2	= 2,		/* Counter 2 Access Port */
  	Control		= 3,		/* Timer Control Word */
  };
  
  enum {					/* Control */
  	Bcd		= 0x01,		/* Binary/BCD countdown select */
  
  	Mode0		= 0x00,		/* [3:1] interrupt on terminal count */
  	Mode1		= 0x02,		/* hardware re-triggerable one-shot */
  	Mode2		= 0x04,		/* rate generator */
  	Mode3		= 0x06,		/* square-wave generator */
  	Mode4		= 0x08,		/* sofware triggered strobe */
  	Mode5		= 0x0A,		/* hardware triggered strobe */
  
  	Clc		= 0x00,		/* [5:4] Counter Latch Command */
  	RWlsb		= 0x10,		/* R/W LSB */
  	RWmsb		= 0x20,		/* R/W MSB */
  	RW16		= 0x30,		/* R/W LSB then MSB */
  	Cs0		= 0x00,		/* [7:6] Counter 0 Select */
  	Cs1		= 0x40,		/* Counter 1 Select */
  	Cs2		= 0x80,		/* Counter 2 Select */
  
  	Rbc		= 0xC0,		/* Read-Back Command */
  	RbCnt0		= 0x02,		/* Select Counter 0 */
  	RbCnt1		= 0x04,		/* Select Counter 1 */
  	RbCnt2		= 0x08,		/* Select Counter 2 */
  	RbS		= 0x20,		/* Read-Back Status */
  	RbC		= 0x10,		/* Read-Back Count */
  	RbCS		= 0x00,		/* Read-Back Count and Status */
  
  	RbNULL		= 0x40,		/* NULL-Count Flag */
  	RbOUT		= 0x80,		/* OUT-pin */
  };
  
  enum {
  	Osc		= 1193182,	/* 14.318180MHz/12 */
  	Hz		= 82,		/* 2*41*14551 = 1193182 */
  };
  
  static void
  i8254set(int port, int hz)
  {
  	int counter, timeo;
  
  	/*
  	 * Initialise Counter0 to be the system clock if necessary,
  	 * it's normally connected to IRQ0 on an interrupt controller.
  	 * Use a periodic square wave (Mode3).
  	 */
  	counter = Osc/hz;
  	outb(port+Control, Cs0|RW16|Mode3);
  	outb(port+Counter0, counter);
  	outb(port+Counter0, counter>>8);
  
  	/*
  	 * Wait until the counting register has been loaded
  	 * into the counting element.
  	 */
  	for(timeo = 0; timeo < 100000; timeo++){
  		outb(port+Control, Rbc|RbS|RbCnt0);
  		if(!(inb(port+Counter0) & RbNULL))
  			break;
  	}
  }
  
  int64_t
  i8254hz(uint32_t info[2][4])
  {
  	uint32_t ax;
  	uint64_t a, b;
  	int aamcycles, incr, loops, x, y;
  
  	/*
  	 * Use the cpuid family info to get the
  	 * cycles for the AAM instruction.
  	 */
  	ax = info[1][0] & 0x00000f00;
  	if(jehanne_memcmp(&info[0][1], "GenuntelineI", 12) == 0){
  		switch(ax){
  		default:
  			return 0;
  		case 0x00000600:
  		case 0x00000f00:
  			aamcycles = 16;
  			break;
  		}
  	}
  	else if(jehanne_memcmp(&info[0][1], "AuthcAMDenti", 12) == 0){
  		switch(ax){
  		default:
  			return 0;
  		case 0x00000600:
  		case 0x00000f00:
  			aamcycles = 11;
  			break;
  		}
  	}
  	else if(jehanne_memcmp(&info[0][1], "CentaulsaurH", 12) == 0){
  		switch(ax){
  		default:
  			return 0;
  		case 0x00000600:
  			aamcycles = 23;
  			break;
  		}
  	}
  	else
  		return 0;
  
  	i8254set(Timer1, Hz);
  
  	/*
  	 * Find biggest loop that doesn't wrap.
  	 */
  	SET(a, b);
  	incr = 16000000/(aamcycles*Hz*2);
  	x = 2000;
  	for(loops = incr; loops < 64*1024; loops += incr) {
  		/*
  		 * Measure time for the loop
  		 *
  		 *		MOVL	loops,CX
  		 *	aaml1:
  		 *		AAM
  		 *		LOOP	aaml1
  		 *
  		 * The time for the loop should be independent of external
  		 * cache and memory system since it fits in the execution
  		 * prefetch buffer.
  		 * The AAM instruction is not available in 64-bit mode.
  		 */
  		outb(Timer1+Control, Cs0|Clc);
  
  		a = rdtsc();
  		x = inb(Timer1+Counter0);
  		x |= inb(Timer1+Counter0)<<8;
  		aamloop(loops);
  		outb(Timer1+Control, Cs0|Clc);
  		b = rdtsc();
  
  		y = inb(Timer1+Counter0);
  		y |= inb(Timer1+Counter0)<<8;
  		x -= y;
  
  		if(x < 0)
  			x += Osc/Hz;
  
  		if(x > Osc/(3*Hz))
  			break;
  	}
  
  	/*
   	 * Figure out clock frequency.
  	 */
  	b = (b-a)<<1;
  	b *= Osc;
  
  	return b/x;
  }
  