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