summaryrefslogtreecommitdiff
path: root/src/CirrusClk.c
blob: e3388df5fbf1d18f8925754b714f277afd86a8f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/cirrus/CirrusClk.c,v 1.8 1998/12/06 06:08:28 dawes Exp $ */

/*
 * Programming of the built-in Cirrus clock generator.
 * Harm Hanemaayer <hhanemaa@cs.ruu.nl>
 *
 * VCO stability criterion code added by Koen Gadeyne (koen.gadeyne@barco.com)
 * Max clock specification added by Harm Hanemaayer (H.Hanemaayer@inter.nl.net)
 *
 * Minor changes and cleanup Itai Nahshon.
 *
 * Made this completly chipset independent,  and moved chipset dependent parts
 * into the specific sub-drivers.  Derek Fawcus <derek@spider.com>
 */

#include "xf86.h"
#include "xf86_OSproc.h"
#include "xf86_ansic.h"
#include "xf86PciInfo.h"
#include "xf86Pci.h"

#include "cir.h"

/* CLOCK_FACTOR is double the osc freq in kHz (osc = 14.31818 MHz) */
#define CLOCK_FACTOR 28636

/* Stability constraints for internal VCO -- MAX_VCO also determines
 * the maximum Video pixel clock
 */
#define MIN_VCO CLOCK_FACTOR
#define MAX_VCO 111000

/* clock in kHz is (numer * CLOCK_FACTOR / (denom & 0x3E)) >> (denom & 1) */
#define VCOVAL(n, d) \
	((((n) & 0x7F) * CLOCK_FACTOR / ((d) & 0x3E)) )

#define CLOCKVAL(n, d) \
	(VCOVAL(n, d) >> ((d) & 1))

typedef struct {
	unsigned char numer;
	unsigned char denom;
} cirrusClockRec;

static cirrusClockRec cirrusClockTab[] = {
	{ 0x2C, 0x33 },		/* 12.599 */
	{ 0x4A, 0x2B },		/* 25.227 */
	{ 0x5B, 0x2F },		/* 28.325 */
	{ 0x45, 0x30 },		/* 41.164 */
	{ 0x7E, 0x33 },		/* 36.082 */
	{ 0x42, 0x1F },		/* 31.500 */
	{ 0x51, 0x3A },		/* 39.992 */
	{ 0x55, 0x36 },		/* 45.076 */
	{ 0x65, 0x3A },		/* 49.867 */
	{ 0x76, 0x34 },		/* 64.983 */
	{ 0x7E, 0x32 },		/* 72.163 */
	{ 0x6E, 0x2A },		/* 75.000 */
	{ 0x5F, 0x22 },		/* 80.013 */
	{ 0x7D, 0x2A },		/* 85.226 */
	{ 0x58, 0x1C },		/* 89.998 */
	{ 0x49, 0x16 },		/* 95.019 */
	{ 0x46, 0x14 },		/* 100.226 */
	{ 0x53, 0x16 },		/* 108.035 */
	{ 0x5C, 0x18 },		/* 110.248 */

	{ 0x6D, 0x1A },		/* 120.050 */
	{ 0x58, 0x14 },		/* 125.998 */
	{ 0x6D, 0x18 },		/* 130.055 */
	{ 0x42, 0x0E },		/* 134.998 */

	{ 0x69, 0x14 },		/* 150.341 */
	{ 0x5E, 0x10 },		/* 168.239 */
	{ 0x5C, 0x0E },		/* 188.182 */
	{ 0x67, 0x0E },		/* 210.682 */
	{ 0x60, 0x0C },		/* 229.091 */
};

#define NU_FIXED_CLOCKS (sizeof(cirrusClockTab)/sizeof(cirrusClockTab[0]))


/*
 * This function returns the 7-bit numerator and 6-bit denominator/post-scalar
 * value that corresponds to the closest clock found.
 * If a frequency close to one of the tested clock values is found,
 * use the tested clock since others can be unstable.
 */

Bool
CirrusFindClock(int *rfreq, int max_clock, int *num_out, int *den_out)
{
	int n, i;
	int num = 0, den = 0;
	int freq, ffreq = 0, mindiff = 0;

	freq = *rfreq;
	/* Prefer a tested value if it matches within 0.1%. */
	for (i = 0; i < NU_FIXED_CLOCKS; i++) {
		int diff;
		diff = abs(CLOCKVAL(cirrusClockTab[i].numer,
					cirrusClockTab[i].denom) - freq);
		if (diff < freq / 1000) {
			num = cirrusClockTab[i].numer;
			den = cirrusClockTab[i].denom;
			ffreq = CLOCKVAL(num, den);
			goto foundclock;
		}
	}

	/*
	 * If max_clock is greater than the MAX_VCO default, ignore
	 * MAX_VCO. On the other hand, if MAX_VCO is higher than max_clock,
	 * make use of the higher MAX_VCO value.
	 */
	if (MAX_VCO > max_clock)
		max_clock = MAX_VCO;

	mindiff = freq;
	for (n = 0x10; n < 0x7f; n++) {
		int d;
		for (d = 0x14; d < 0x3f; d++) {
			int c, diff;

			/* Avoid combinations that can be unstable. */
			if ((VCOVAL(n, d) < MIN_VCO) || (VCOVAL(n, d) > max_clock))
				continue;
			c = CLOCKVAL(n, d);
			diff = abs(c - freq);
			if (diff < mindiff) {
				mindiff = diff;
				num = n;
				den = d;
				ffreq = c;
			}
		}
	}

	if (0 == num || 0 == den)
		return FALSE;

foundclock:
	*num_out = num;
	*den_out = den;
	*rfreq = ffreq;

	return TRUE;
}