/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

/*
 * IC Designs ICD2061A Dual Programmable Graphics Clock Generator.
 */
#include <u.h>
#include <lib9.h>
#include <bio.h>

#include "pci.h"
#include "vga.h"

enum {
	Prescale	= 2,			/* P counter prescale (default) */
	NIndex		= 14,			/* number of index field values */
};

/*
 * For an index value of x, the appropriate VCO range
 * is >= index[x] && <= index[x+1]. The higher index is
 * prefered if VCO is on a boundary.
 */
static uint32_t index[NIndex] = {
	 50000000,
	 51000000,
	 53200000,
	 58500000,
	 60700000,
	 64400000,
	 66800000,
	 73500000,
	 75600000,
	 80900000,
	 83200000,
	 91500000,
	100000000,
	120000000,
};

static void
init(Vga* vga, Ctlr* ctlr)
{
	int f;
	uint32_t d, dmax, fmin, n;

	if(ctlr->flag & Finit)
		return;

	if(vga->f[0] == 0)
		vga->f[0] = vga->mode->frequency;

	if(vga->mode->z > 8)
		error("depth %d not supported\n", vga->mode->z);

	/*
	 * Post-VCO divisor. Constraint:
	 * 	50MHz <= vga->f <= 120MHz
	 */
	for(vga->p[0] = 0; vga->f[0] <= 50000000; vga->p[0]++)
		vga->f[0] <<= 1;

	/*
	 * Determine index.
	 */
	for(vga->i[0] = NIndex-1; vga->f[0] < index[vga->i[0]] && vga->i[0]; vga->i[0]--)
		;

	/*
	 * Denominator. Constraints:
	 *	200KHz <= RefFreq/d <= 1MHz
	 * and
	 *	3 <= d <= 129
	 *
	 * Numerator. Constraint:
	 *	4 <= n <= 130
	 */
	d = RefFreq/1000000 > 3 ? RefFreq/1000000: 3;
	dmax = RefFreq/200000 < 129 ? RefFreq/200000: 129;

	/*
	 * Now look for values of p and q that give
	 * the least error for
	 *	vga->f = (Prescale*RefFreq*n/d);
	 */
	vga->d[0] = d;
	vga->n[0] = 4;
	for(fmin = vga->f[0]; d <= dmax; d++){
		for(n = 4; n <= 130; n++){
			f = vga->f[0] - (Prescale*RefFreq*n/d);
			if(f < 0)
				f = -f;
			if(f < fmin){
				fmin = f;
				vga->d[0] = d;
				vga->n[0] = n;
			}
		}
	}

	/*
	 * The serial word to be loaded into the icd2061a is
	 *	(2<<21)|(vga->i<<17)|((vga->n)<<10)|(vga->p<<7)|vga->d
	 * Always select ICD2061A REG2.
	 */
	vga->f[0] = (Prescale*RefFreq*vga->n[0]/vga->d[0]);
	vga->d[0] -= 2;
	vga->n[0] -= 3;

	ctlr->flag |= Finit;
}

Ctlr icd2061a = {
	"icd2061a",
	0,				/* snarf */
	0,				/* options */
	init,				/* init */
	0,				/* load */
	0,				/* dump */
};
