smi_501.c revision 7104f784
1/*
2Copyright (C) 1994-1999 The XFree86 Project, Inc.  All Rights Reserved.
3Copyright (C) 2000 Silicon Motion, Inc.  All Rights Reserved.
4Copyright (C) 2008 Mandriva Linux.  All Rights Reserved.
5
6Permission is hereby granted, free of charge, to any person obtaining a copy of
7this software and associated documentation files (the "Software"), to deal in
8the Software without restriction, including without limitation the rights to
9use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10of the Software, and to permit persons to whom the Software is furnished to do
11so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT-
18NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
19XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
20AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23Except as contained in this notice, the names of The XFree86 Project and
24Silicon Motion shall not be used in advertising or otherwise to promote the
25sale, use or other dealings in this Software without prior written
26authorization from The XFree86 Project or Silicon Motion.
27*/
28
29#ifdef HAVE_CONFIG_H
30#include "config.h"
31#endif
32
33#include "smi.h"
34#include "smi_crtc.h"
35#include "smi_501.h"
36#include "regsmi.h"
37
38#define DPMS_SERVER
39#include <X11/extensions/dpms.h>
40
41/* Want to see register dumps for now */
42#undef VERBLEV
43#define VERBLEV		1
44
45
46/*
47 * Prototypes
48 */
49
50static char *format_integer_base2(int32_t word);
51static void SMI501_SetClock(SMIPtr pSmi, int32_t port,
52			    int32_t pll, int32_t value);
53
54
55/*
56 * Implemementation
57 */
58
59void
60SMI501_Save(ScrnInfoPtr pScrn)
61{
62    SMIPtr	pSmi = SMIPTR(pScrn);
63    MSOCRegPtr	save = pSmi->save;
64
65    xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
66		   "Register dump (Before Save)\n");
67    SMI501_PrintRegs(pScrn);
68
69    /* Used mainly for DPMS info */
70    save->system_ctl.value = READ_SCR(pSmi, SYSTEM_CTL);
71
72    /* Used basically to enable dac */
73    save->misc_ctl.value = READ_SCR(pSmi, MISC_CTL);
74
75    /* Read it first to know if current power mode */
76    save->power_ctl.value = READ_SCR(pSmi, POWER_CTL);
77
78    switch (save->power_ctl.f.mode) {
79	case 0:
80	    save->current_gate  = POWER0_GATE;
81	    save->current_clock = POWER0_CLOCK;
82	    break;
83	case 1:
84	    save->current_gate  = POWER1_GATE;
85	    save->current_clock = POWER1_CLOCK;
86	    break;
87	default:
88	    /* FIXME
89	     * Should be in sleep mode
90	     * TODO
91	     * select mode0 by default
92	     */
93	    save->current_gate = POWER0_GATE;
94	    save->current_clock = POWER0_CLOCK;
95	    break;
96    }
97
98    save->gate.value  = READ_SCR(pSmi, save->current_gate);
99    save->clock.value = READ_SCR(pSmi, save->current_clock);
100
101    /* FIXME Never changed */
102    save->timing_ctl.value = READ_SCR(pSmi, TIMING_CTL);
103
104    save->pll_ctl.value = READ_SCR(pSmi, PLL_CTL);
105    save->device_id.value = READ_SCR(pSmi, DEVICE_ID);
106    save->sleep_gate.value = READ_SCR(pSmi, SLEEP_GATE);
107
108    save->panel_display_ctl.value = READ_SCR(pSmi, PANEL_DISPLAY_CTL);
109    save->panel_fb_address.value = READ_SCR(pSmi, PANEL_FB_ADDRESS);
110    save->panel_fb_width.value = READ_SCR(pSmi, PANEL_FB_WIDTH);
111    save->panel_wwidth.value = READ_SCR(pSmi, PANEL_WWIDTH);
112    save->panel_wheight.value = READ_SCR(pSmi, PANEL_WHEIGHT);
113    save->panel_plane_tl.value = READ_SCR(pSmi, PANEL_PLANE_TL);
114    save->panel_plane_br.value = READ_SCR(pSmi, PANEL_PLANE_BR);
115    save->panel_htotal.value = READ_SCR(pSmi, PANEL_HTOTAL);
116    save->panel_hsync.value = READ_SCR(pSmi, PANEL_HSYNC);
117    save->panel_vtotal.value = READ_SCR(pSmi, PANEL_VTOTAL);
118    save->panel_vsync.value = READ_SCR(pSmi, PANEL_VSYNC);
119
120    save->crt_display_ctl.value = READ_SCR(pSmi, CRT_DISPLAY_CTL);
121    save->crt_fb_address.value = READ_SCR(pSmi, CRT_FB_ADDRESS);
122    save->crt_fb_width.value = READ_SCR(pSmi, CRT_FB_WIDTH);
123    save->crt_htotal.value = READ_SCR(pSmi, CRT_HTOTAL);
124    save->crt_hsync.value = READ_SCR(pSmi, CRT_HSYNC);
125    save->crt_vtotal.value = READ_SCR(pSmi, CRT_VTOTAL);
126    save->crt_vsync.value = READ_SCR(pSmi, CRT_VSYNC);
127
128    save->alpha_display_ctl.value = READ_SCR(pSmi, ALPHA_DISPLAY_CTL);
129    save->alpha_fb_address.value = READ_SCR(pSmi, ALPHA_FB_ADDRESS);
130    save->alpha_fb_width.value = READ_SCR(pSmi, ALPHA_FB_WIDTH);
131    save->alpha_plane_tl.value = READ_SCR(pSmi, ALPHA_PLANE_TL);
132    save->alpha_plane_br.value = READ_SCR(pSmi, ALPHA_PLANE_BR);
133    save->alpha_chroma_key.value = READ_SCR(pSmi, ALPHA_CHROMA_KEY);
134
135    /* Also save accel state to properly restore kernel framebuffer */
136    save->accel_src = READ_SCR(pSmi, ACCEL_SRC);
137    save->accel_dst = READ_SCR(pSmi, ACCEL_DST);
138    save->accel_dim = READ_SCR(pSmi, ACCEL_DIM);
139    save->accel_ctl = READ_SCR(pSmi, ACCEL_CTL);
140    save->accel_pitch = READ_SCR(pSmi, ACCEL_PITCH);
141    save->accel_fmt = READ_SCR(pSmi, ACCEL_FMT);
142    save->accel_clip_tl = READ_SCR(pSmi, ACCEL_CLIP_TL);
143    save->accel_clip_br = READ_SCR(pSmi, ACCEL_CLIP_BR);
144    save->accel_pat_lo = READ_SCR(pSmi, ACCEL_PAT_LO);
145    save->accel_pat_hi = READ_SCR(pSmi, ACCEL_PAT_HI);
146    save->accel_wwidth = READ_SCR(pSmi, ACCEL_WWIDTH);
147    save->accel_src_base = READ_SCR(pSmi, ACCEL_SRC_BASE);
148    save->accel_dst_base = READ_SCR(pSmi, ACCEL_DST_BASE);
149}
150
151void
152SMI501_DisplayPowerManagementSet(ScrnInfoPtr pScrn,
153				 int PowerManagementMode, int flags)
154{
155    SMIPtr		pSmi = SMIPTR(pScrn);
156
157    if (pSmi->CurrentDPMS != PowerManagementMode) {
158	/* Set the DPMS mode to every output and CRTC */
159	xf86DPMSSet(pScrn, PowerManagementMode, flags);
160
161	pSmi->CurrentDPMS = PowerManagementMode;
162    }
163}
164
165Bool
166SMI501_HWInit(ScrnInfoPtr pScrn)
167{
168    MSOCRegPtr	save;
169    MSOCRegPtr	mode;
170    SMIPtr	pSmi = SMIPTR(pScrn);
171    int32_t	x_select, x_divider, x_shift;
172
173    save = pSmi->save;
174    mode = pSmi->mode;
175
176    /* Start with a fresh copy of registers before any mode change */
177    memcpy(mode, save, sizeof(MSOCRegRec));
178
179    if (pSmi->UseFBDev)
180	return (TRUE);
181
182    /* Enable DAC -- 0: enable - 1: disable */
183    mode->misc_ctl.f.dac = 0;
184
185    /* Enable 2D engine */
186    mode->gate.f.engine = 1;
187    /* Color space conversion */
188    mode->gate.f.csc = 1;
189    /* ZV port */
190    mode->gate.f.zv = 1;
191    /* Gpio, Pwm, and I2c */
192    mode->gate.f.gpio = 1;
193
194    /* FIXME fixed at power mode 0 as in the smi sources */
195    mode->power_ctl.f.status = 0;
196    mode->power_ctl.f.mode = 0;
197
198    if (pSmi->MCLK) {
199	xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
200		       "MCLK request %d\n", pSmi->MCLK);
201	(void)SMI501_FindMemClock(pSmi->MCLK, &x_select, &x_divider, &x_shift);
202	mode->clock.f.m_select = x_select;
203	mode->clock.f.m_divider = x_divider;
204	mode->clock.f.m_shift = x_shift;
205    }
206    /* Else use what was configured by the kernel. */
207
208    if (pSmi->MXCLK) {
209	xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, VERBLEV,
210		       "MXCLK request %d\n", pSmi->MXCLK);
211	(void)SMI501_FindMemClock(pSmi->MXCLK, &x_select, &x_divider, &x_shift);
212	mode->clock.f.m1_select = x_select;
213	mode->clock.f.m1_divider = x_divider;
214	mode->clock.f.m1_shift = x_shift;
215    }
216    /* Else use what was configured by the kernel. */
217
218    if (!pSmi->Dualhead) {
219	/* crt clones panel */
220	mode->crt_display_ctl.f.enable = 0;
221	/* 0: select panel - 1: select crt */
222	mode->crt_display_ctl.f.select = 0;
223	mode->crt_display_ctl.f.timing = 0;
224    }
225
226    SMI501_WriteMode_common(pScrn, mode);
227
228    return (TRUE);
229}
230
231void
232SMI501_WriteMode_common(ScrnInfoPtr pScrn, MSOCRegPtr mode)
233{
234    int32_t		pll;
235    MSOCClockRec	clock;
236    SMIPtr		pSmi = SMIPTR(pScrn);
237
238    if (!pSmi->UseFBDev) {
239	/* Update gate first */
240	WRITE_SCR(pSmi, mode->current_gate, mode->gate.value);
241
242	clock.value = READ_SCR(pSmi, mode->current_clock);
243
244	if (pSmi->MCLK) {
245	    clock.f.m_select = mode->clock.f.m_select;
246	    pll = clock.value;
247	    clock.f.m_divider = mode->clock.f.m_divider;
248	    clock.f.m_shift = mode->clock.f.m_shift;
249	    SMI501_SetClock(pSmi, mode->current_clock, pll, clock.value);
250	}
251
252	if (pSmi->MXCLK) {
253	    clock.f.m1_select = mode->clock.f.m1_select;
254	    pll = clock.value;
255	    clock.f.m1_divider = mode->clock.f.m1_divider;
256	    clock.f.m1_shift = mode->clock.f.m1_shift;
257	    SMI501_SetClock(pSmi, mode->current_clock, pll, clock.value);
258	}
259
260	WRITE_SCR(pSmi, MISC_CTL, mode->misc_ctl.value);
261
262	WRITE_SCR(pSmi, POWER_CTL, mode->power_ctl.value);
263    }
264
265    /* Match configuration */
266    /* FIXME some other fields should also be set, otherwise, since
267     * neither kernel nor driver change it, a reboot is required to
268     * modify or reset to default */
269    mode->system_ctl.f.burst = mode->system_ctl.f.burst_read =
270	pSmi->PCIBurst != FALSE;
271    mode->system_ctl.f.retry = pSmi->PCIRetry != FALSE;
272    WRITE_SCR(pSmi, SYSTEM_CTL, mode->system_ctl.value);
273
274    if (!pSmi->Dualhead)
275	WRITE_SCR(pSmi, CRT_DISPLAY_CTL, mode->crt_display_ctl.value);
276}
277
278void
279SMI501_WriteMode_lcd(ScrnInfoPtr pScrn, MSOCRegPtr mode)
280{
281    int32_t		pll;
282    MSOCClockRec	clock;
283    SMIPtr		pSmi = SMIPTR(pScrn);
284
285    if (!pSmi->UseFBDev) {
286	clock.value = READ_SCR(pSmi, mode->current_clock);
287
288	/* Alternate pll_select is only available for the SMI 502,
289	 * and the bit should be only set in that case. */
290	if (mode->clock.f.pll_select)
291	    WRITE_SCR(pSmi, PLL_CTL, mode->pll_ctl.value);
292	clock.f.p2_select = mode->clock.f.p2_select;
293	pll = clock.value;
294	clock.f.p2_divider = mode->clock.f.p2_divider;
295	clock.f.p2_shift = mode->clock.f.p2_shift;
296	clock.f.pll_select = mode->clock.f.pll_select;
297	clock.f.p2_1xclck = mode->clock.f.p2_1xclck;
298	SMI501_SetClock(pSmi, mode->current_clock, pll, clock.value);
299
300	WRITE_SCR(pSmi, PANEL_FB_ADDRESS, mode->panel_fb_address.value);
301	WRITE_SCR(pSmi, PANEL_FB_WIDTH, mode->panel_fb_width.value);
302
303	WRITE_SCR(pSmi, PANEL_WWIDTH, mode->panel_wwidth.value);
304	WRITE_SCR(pSmi, PANEL_WHEIGHT, mode->panel_wheight.value);
305
306	WRITE_SCR(pSmi, PANEL_PLANE_TL, mode->panel_plane_tl.value);
307	WRITE_SCR(pSmi, PANEL_PLANE_BR, mode->panel_plane_br.value);
308
309	WRITE_SCR(pSmi, PANEL_HTOTAL, mode->panel_htotal.value);
310	WRITE_SCR(pSmi, PANEL_HSYNC, mode->panel_hsync.value);
311	WRITE_SCR(pSmi, PANEL_VTOTAL, mode->panel_vtotal.value);
312	WRITE_SCR(pSmi, PANEL_VSYNC, mode->panel_vsync.value);
313	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
314    }
315}
316
317void
318SMI501_WriteMode_crt(ScrnInfoPtr pScrn, MSOCRegPtr mode)
319{
320    int32_t		pll;
321    MSOCClockRec	clock;
322    SMIPtr		pSmi = SMIPTR(pScrn);
323
324    if (!pSmi->UseFBDev) {
325	clock.value = READ_SCR(pSmi, mode->current_clock);
326
327	clock.f.v2_select = mode->clock.f.v2_select;
328	pll = clock.value;
329	clock.f.v2_divider = mode->clock.f.v2_divider;
330	clock.f.v2_shift = mode->clock.f.v2_shift;
331	clock.f.v2_1xclck = mode->clock.f.v2_1xclck;
332	SMI501_SetClock(pSmi, mode->current_clock, pll, clock.value);
333
334	WRITE_SCR(pSmi, CRT_FB_ADDRESS, mode->crt_fb_address.value);
335	WRITE_SCR(pSmi, CRT_FB_WIDTH, mode->crt_fb_width.value);
336	WRITE_SCR(pSmi, CRT_HTOTAL, mode->crt_htotal.value);
337	WRITE_SCR(pSmi, CRT_HSYNC, mode->crt_hsync.value);
338	WRITE_SCR(pSmi, CRT_VTOTAL, mode->crt_vtotal.value);
339	WRITE_SCR(pSmi, CRT_VSYNC, mode->crt_vsync.value);
340	WRITE_SCR(pSmi, CRT_DISPLAY_CTL, mode->crt_display_ctl.value);
341    }
342}
343
344void
345SMI501_WriteMode_alpha(ScrnInfoPtr pScrn, MSOCRegPtr mode)
346{
347    SMIPtr	pSmi = SMIPTR(pScrn);
348
349    WRITE_SCR(pSmi, ALPHA_FB_ADDRESS, mode->alpha_fb_address.value);
350    WRITE_SCR(pSmi, ALPHA_FB_WIDTH, mode->alpha_fb_width.value);
351
352    WRITE_SCR(pSmi, ALPHA_PLANE_TL, mode->alpha_plane_tl.value);
353    WRITE_SCR(pSmi, ALPHA_PLANE_BR, mode->alpha_plane_br.value);
354
355    WRITE_SCR(pSmi, ALPHA_CHROMA_KEY, mode->alpha_chroma_key.value);
356
357    WRITE_SCR(pSmi, ALPHA_DISPLAY_CTL, mode->alpha_display_ctl.value);
358}
359
360void
361SMI501_WriteMode(ScrnInfoPtr pScrn, MSOCRegPtr restore)
362{
363    SMIPtr	pSmi = SMIPTR(pScrn);
364
365    SMI501_WriteMode_common(pScrn, restore);
366    SMI501_WriteMode_lcd(pScrn, restore);
367    SMI501_WriteMode_crt(pScrn, restore);
368#if SMI_CURSOR_ALPHA_PLANE
369    SMI501_WriteMode_alpha(pScrn, restore);
370#endif
371
372    /* This function should be called when switching to virtual console */
373    WRITE_SCR(pSmi, ACCEL_SRC, restore->accel_src);
374    WRITE_SCR(pSmi, ACCEL_DST, restore->accel_dst);
375    WRITE_SCR(pSmi, ACCEL_DIM, restore->accel_dim);
376    WRITE_SCR(pSmi, ACCEL_CTL, restore->accel_ctl);
377    WRITE_SCR(pSmi, ACCEL_PITCH, restore->accel_pitch);
378    WRITE_SCR(pSmi, ACCEL_FMT, restore->accel_fmt);
379    WRITE_SCR(pSmi, ACCEL_CLIP_TL, restore->accel_clip_tl);
380    WRITE_SCR(pSmi, ACCEL_CLIP_BR, restore->accel_clip_br);
381    WRITE_SCR(pSmi, ACCEL_PAT_LO, restore->accel_pat_lo);
382    WRITE_SCR(pSmi, ACCEL_PAT_HI, restore->accel_pat_hi);
383    WRITE_SCR(pSmi, ACCEL_WWIDTH, restore->accel_wwidth);
384    WRITE_SCR(pSmi, ACCEL_SRC_BASE, restore->accel_src_base);
385    WRITE_SCR(pSmi, ACCEL_DST_BASE, restore->accel_dst_base);
386}
387
388void
389SMI501_PowerPanel(ScrnInfoPtr pScrn, MSOCRegPtr mode, Bool on)
390{
391    SMIPtr	pSmi = SMIPTR(pScrn);
392
393    if (on != FALSE) {
394	mode->panel_display_ctl.f.vdd = 1;
395	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
396	SMI501_WaitVSync(pSmi, 4);
397
398	mode->panel_display_ctl.f.signal = 1;
399	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
400	SMI501_WaitVSync(pSmi, 4);
401
402	mode->panel_display_ctl.f.bias = 1;
403	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
404	SMI501_WaitVSync(pSmi, 4);
405
406	mode->panel_display_ctl.f.fp = 1;
407	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
408	SMI501_WaitVSync(pSmi, 4);
409    }
410    else {
411	mode->panel_display_ctl.f.fp = 0;
412	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
413	SMI501_WaitVSync(pSmi, 4);
414
415	mode->panel_display_ctl.f.bias = 0;
416	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
417	SMI501_WaitVSync(pSmi, 4);
418
419	mode->panel_display_ctl.f.signal = 0;
420	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
421	SMI501_WaitVSync(pSmi, 4);
422
423	mode->panel_display_ctl.f.vdd = 0;
424	WRITE_SCR(pSmi, PANEL_DISPLAY_CTL, mode->panel_display_ctl.value);
425	SMI501_WaitVSync(pSmi, 4);
426    }
427}
428
429static char *
430format_integer_base2(int32_t word)
431{
432    int		i;
433    static char	buffer[33];
434
435    for (i = 0; i < 32; i++) {
436	if (word & (1 << i))
437	    buffer[31 - i] = '1';
438	else
439	    buffer[31 - i] = '0';
440    }
441
442    return (buffer);
443}
444
445double
446SMI501_FindClock(double clock, int32_t max_divider, Bool has1xclck,
447		 int32_t *x2_1xclck,
448		 int32_t *x2_select, int32_t *x2_divider, int32_t *x2_shift)
449{
450    double	diff, best, mclk;
451    int32_t	multiplier, divider, shift, xclck;
452
453    /* The Crystal input frequency is 24Mhz, and can be multiplied
454     * by 12 or 14 (actually, there are other values, see TIMING_CTL,
455     * MMIO 0x068) */
456
457    /* Find clock best matching mode */
458    best = 0x7fffffff;
459    for (multiplier = 12, mclk  = multiplier * 24 * 1000.0;
460	 mclk <= 14 * 24 * 1000.0;
461	 multiplier += 2, mclk  = multiplier * 24 * 1000.0) {
462	for (divider = 1; divider <= max_divider; divider += 2) {
463	    for (shift = 0; shift < 8; shift++) {
464		/* Divider 1 not in specs for cards older then 502 */
465		for (xclck = 1; xclck >= !has1xclck; xclck--) {
466		    diff = (mclk / (divider << shift << xclck)) - clock;
467		    if (fabs(diff) < best) {
468			*x2_shift = shift;
469			*x2_divider = divider == 1 ? 0 : divider == 3 ? 1 : 2;
470			*x2_select = mclk == 12 * 24 * 1000.0 ? 0 : 1;
471			*x2_1xclck = xclck == 0;
472
473			/* Remember best diff */
474			best = fabs(diff);
475		    }
476		}
477	    }
478	}
479    }
480
481    xf86ErrorFVerb(VERBLEV,
482		   "\tMatching clock %5.2f, diff %5.2f (%d/%d/%d/%d)\n",
483		   ((*x2_select ? 14 : 12) * 24 * 1000.0) /
484		   ((*x2_divider == 0 ? 1 : *x2_divider == 1 ? 3 : 5) <<
485		    *x2_shift << (*x2_1xclck ? 0 : 1)),
486		   best, *x2_shift, *x2_divider, *x2_select, *x2_1xclck);
487
488    return (best);
489}
490
491double
492SMI501_FindMemClock(double clock, int32_t *x1_select,
493		    int32_t *x1_divider, int32_t *x1_shift)
494{
495    double	diff, best, mclk;
496    int32_t	multiplier, divider, shift;
497
498    best = 0x7fffffff;
499    for (multiplier = 12, mclk  = multiplier * 24 * 1000.0;
500	 mclk <= 14 * 24 * 1000.0;
501	 multiplier += 2, mclk = multiplier * 24 * 1000.0) {
502	for (divider = 1; divider <= 3; divider += 2) {
503	    for (shift = 0; shift < 8; shift++) {
504		diff = (mclk / (divider << shift)) - clock;
505		if (fabs(diff) < best) {
506		    *x1_shift = shift;
507		    *x1_divider = divider == 1 ? 0 : 1;
508		    *x1_select = mclk == 12 * 24 * 1000.0 ? 0 : 1;
509
510		    /* Remember best diff */
511		    best = fabs(diff);
512		}
513	    }
514	}
515    }
516
517    xf86ErrorFVerb(VERBLEV,
518		   "\tMatching clock %5.2f, diff %5.2f (%d/%d/%d)\n",
519		   ((*x1_select ? 14 : 12) * 24 * 1000.0) /
520		   ((*x1_divider == 0 ? 1 : 3) << *x1_shift),
521		    best, *x1_shift, *x1_divider, *x1_select);
522
523    return (best);
524}
525
526
527double
528SMI501_FindPLLClock(double clock, int32_t *m, int32_t *n, int32_t *xclck)
529{
530    int32_t	M, N, K;
531    double	diff, best;
532    double	frequency;
533
534    /*   This method, available only on the 502 is intended to cover the
535     * disadvantage of the other method where certain modes cannot be
536     * displayed correctly due to the big difference on the requested
537     * pixel clock, with the actual pixel clock that can be achieved by
538     * those divisions. In this method, N can be any integer between 2
539     * and 24, M can be any positive, 8 bits integer, and K is either 1
540     * or 2.
541     *   To calculate the programmable PLL, the following formula is
542     * used:
543     *
544     *	Requested Pixel Clock = Input Frequency * M / N
545     *
546     *   Input Frequency is the crystal input frequency value (24 MHz in
547     * the SMI VGX Demo Board).
548     *
549     *   K is a divisor, used by setting bit 15 of the PLL_CTL
550     * (PLL Output Divided by 2).
551     *
552     *   So, it should be requested_clock = input_frequency * M / N / K
553     */
554
555    /* That said, use what actually works, that is:
556     * requested_clock = input_frequency * K * M / N
557     *
558     * where requested_clock is modeline pixel clock,
559     * input_frequency is 12, K is either 1 or 2 (and sets bit15 accordingly),
560     * M is a non zero 8 bits unsigned integer, and N is a value from 2 to 24.
561     */
562
563    best = 0x7fffffff;
564    frequency = 12 * 1000.0;
565    for (N = 2; N <= 24; N++) {
566	for (K = 1; K <= 2; K++) {
567	    M = clock / frequency * K * N;
568	    diff = ((int32_t)(frequency / K * M) / N) - clock;
569	    /* Ensure M is larger then 0 and fits in 8 bits */
570	    if (M > 0 && M < 0x100 && fabs(diff) < best) {
571		*m = M;
572		*n = N;
573		*xclck = K == 1;
574
575		/* Remember best diff */
576		best = fabs(diff);
577	    }
578	}
579    }
580
581    xf86ErrorFVerb(VERBLEV,
582		   "\tMatching alternate clock %5.2f, diff %5.2f (%d/%d/%d)\n",
583		   frequency / (*xclck ? 1 : 2) * *m / *n, best,
584		   *m, *n, *xclck);
585
586    return (best);
587}
588
589void
590SMI501_PrintRegs(ScrnInfoPtr pScrn)
591{
592    int		i;
593    SMIPtr	pSmi = SMIPTR(pScrn);
594
595    xf86ErrorFVerb(VERBLEV, "    SMI501 System Setup:\n");
596    for (i = 0x00; i <= 0x74; i += 4)
597	xf86ErrorFVerb(VERBLEV, "\t%08x: %s\n", i,
598		       format_integer_base2(READ_SCR(pSmi, i)));
599    xf86ErrorFVerb(VERBLEV, "    SMI501 Display Setup:\n");
600    for (i = 0x80000; i < 0x80400; i += 4)
601	xf86ErrorFVerb(VERBLEV, "\t%08x: %s\n", i,
602		       format_integer_base2(READ_SCR(pSmi, i)));
603}
604
605void
606SMI501_WaitVSync(SMIPtr pSmi, int vsync_count)
607{
608    MSOCCmdStatusRec	status;
609    int32_t		timeout;
610
611    while (vsync_count-- > 0) {
612	/* Wait for end of vsync */
613	timeout = 0;
614	do {
615	    /* bit 11: vsync active *if set* */
616	    status.value = READ_SCR(pSmi, CMD_STATUS);
617	    if (++timeout == 10000)
618		break;
619	} while (status.f.pvsync);
620
621	/* Wait for start of vsync */
622	timeout = 0;
623	do {
624	    status.value = READ_SCR(pSmi, CMD_STATUS);
625	    if (++timeout == 10000)
626		break;
627	} while (!status.f.pvsync);
628    }
629}
630
631static void
632SMI501_SetClock(SMIPtr pSmi, int32_t port, int32_t pll, int32_t value)
633{
634    /*
635     *	Rules to Program the Power Mode Clock Registers for Clock Selection
636     *
637     *	1. There should be only one clock source changed at a time.
638     *	   To change clock source for P2XCLK, V2XCLK, MCLK, M2XCLK
639     *	   simultaneously may cause the internal logic normal operation
640     *	   to be disrupted. There should be a minimum of 16mS wait from
641     *	   change one clock source to another.
642     *	2. When adjusting the clock rate, the PLL selection bit should
643     *	   be programmed first before changing the divider value for each
644     *	   clock source. For example, to change the P2XCLK clock rate:
645     *		. bit 29 should be set first
646     *		. wait for a minimum of 16ms (about one Vsync time)
647     *		. adjust bits [28:24].
648     *	   The minimum 16 ms wait is necessary for logic to settle down
649     *	   before the clock rate is changed.
650     *	3. There should be a minimum 16 ms wait after a clock source is
651     *	   changed before any operation that could result in a bus
652     *	   transaction.
653     */
654
655    /* register contents selecting clock */
656    WRITE_SCR(pSmi, port, pll);
657    SMI501_WaitVSync(pSmi, 1);
658
659    /* full register contents */
660    WRITE_SCR(pSmi, port, value);
661    SMI501_WaitVSync(pSmi, 1);
662}
663