randr12: Intermediate commit.
[nouveau] / src / nv50_display.c
1 /*
2  * Copyright (c) 2007 NVIDIA, Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 #include <float.h>
25 #include <math.h>
26 #include <strings.h>
27 #include <unistd.h>
28
29 #include "nv_include.h"
30 #include "nv50_type.h"
31 #include "nv50_cursor.h"
32 #include "nv50_display.h"
33 #include "nv50_output.h"
34
35 static void NV50CrtcShowHideCursor(xf86CrtcPtr crtc, Bool show, Bool update);
36
37 /*
38  * PLL calculation.  pclk is in kHz.
39  */
40 static void
41 NV50CalcPLL(float pclk, int *pNA, int *pMA, int *pNB, int *pMB, int *pP)
42 {
43     const float refclk = 27000.0f;
44     const float minVcoA = 100000;
45     const float maxVcoA = 400000;
46     const float minVcoB = 600000;
47     float maxVcoB = 1400000;
48     const float minUA = 2000;
49     const float maxUA = 400000;
50     const float minUB = 50000;
51     const float maxUB = 200000;
52     const int minNA = 1, maxNA = 255;
53     const int minNB = 1, maxNB = 31;
54     const int minMA = 1, maxMA = 255;
55     const int minMB = 1, maxMB = 31;
56     const int minP = 0, maxP = 6;
57     int lowP, highP;
58     float vcoB;
59
60     int na, ma, nb, mb, p;
61     float bestError = FLT_MAX;
62
63     *pNA = *pMA = *pNB = *pMB = *pP = 0;
64
65     if(maxVcoB < pclk + pclk / 200)
66         maxVcoB = pclk + pclk / 200;
67     if(minVcoB / (1 << maxP) > pclk)
68         pclk = minVcoB / (1 << maxP);
69
70     vcoB = maxVcoB - maxVcoB / 200;
71     lowP = minP;
72     vcoB /= 1 << (lowP + 1);
73
74     while(pclk <= vcoB && lowP < maxP)
75     {
76         vcoB /= 2;
77         lowP++;
78     }
79
80     vcoB = maxVcoB + maxVcoB / 200;
81     highP = lowP;
82     vcoB /= 1 << (highP + 1);
83
84     while(pclk <= vcoB && highP < maxP)
85     {
86         vcoB /= 2;
87         highP++;
88     }
89
90     for(p = lowP; p <= highP; p++)
91     {
92         for(ma = minMA; ma <= maxMA; ma++)
93         {
94             if(refclk / ma < minUA)
95                 break;
96             else if(refclk / ma > maxUA)
97                 continue;
98
99             for(na = minNA; na <= maxNA; na++)
100             {
101                 if(refclk * na / ma < minVcoA || refclk * na / ma > maxVcoA)
102                     continue;
103
104                 for(mb = minMB; mb <= maxMB; mb++)
105                 {
106                     if(refclk * na / ma / mb < minUB)
107                         break;
108                     else if(refclk * na / ma / mb > maxUB)
109                         continue;
110
111                     nb = rint(pclk * (1 << p) * (ma / (float)na) * mb / refclk);
112
113                     if(nb > maxNB)
114                         break;
115                     else if(nb < minNB)
116                         continue;
117                     else
118                     {
119                         float freq = refclk * (na / (float)ma) * (nb / (float)mb) / (1 << p);
120                         float error = fabsf(pclk - freq);
121                         if(error < bestError) {
122                             *pNA = na;
123                             *pMA = ma;
124                             *pNB = nb;
125                             *pMB = mb;
126                             *pP = p;
127                             bestError = error;
128                         }
129                     }
130                 }
131             }
132         }
133     }
134 }
135
136 void NV50CrtcSetPClk(xf86CrtcPtr crtc)
137 {
138         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
139         xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn);
140         int lo_n, lo_m, hi_n, hi_m, p, i;
141         /* These clocks are probably rerouted from the 0x4000 range to the 0x610000 range */
142         /* NV50CrtcRead does the offset to VPLL2 if needed */
143         CARD32 lo = NV50CrtcRead(crtc, NV50_DISPLAY_VPLL1_A);
144         CARD32 hi = NV50CrtcRead(crtc, NV50_DISPLAY_VPLL1_B);
145
146         NV50CrtcWrite(crtc, 0x4100, 0x10000610);
147         lo &= 0xff00ff00;
148         hi &= 0x8000ff00;
149
150         NV50CalcPLL(nv_crtc->pclk, &lo_n, &lo_m, &hi_n, &hi_m, &p);
151
152         lo |= (lo_m << 16) | lo_n;
153         hi |= (p << 28) | (hi_m << 16) | hi_n;
154         NV50CrtcWrite(crtc, NV50_DISPLAY_VPLL1_A, lo);
155         NV50CrtcWrite(crtc, NV50_DISPLAY_VPLL1_B, hi);
156         NV50CrtcWrite(crtc, 0x4200, 0);
157
158         for(i = 0; i < xf86_config->num_output; i++) {
159                 xf86OutputPtr output = xf86_config->output[i];
160
161                 if(output->crtc != crtc)
162                         continue;
163                 NV50OutputSetPClk(output, nv_crtc->pclk);
164         }
165 }
166
167 Head
168 NV50CrtcGetHead(xf86CrtcPtr crtc)
169 {
170         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
171         return nv_crtc->head;
172 }
173
174 Bool
175 NV50DispPreInit(ScrnInfoPtr pScrn)
176 {
177         /* These labels are guesswork based on symmetry (2 SOR's and 3 DAC's exist)*/
178         NV50DisplayWrite(pScrn, 0x184, NV50DisplayRead(pScrn, 0x4004));
179         NV50DisplayWrite(pScrn, 0x190, NV50OrRead(pScrn, SOR0, 0x6100));
180         NV50DisplayWrite(pScrn, 0x1a0, NV50OrRead(pScrn, SOR1, 0x6100));
181         NV50DisplayWrite(pScrn, 0x194, NV50OrRead(pScrn, SOR0, 0x6104));
182         NV50DisplayWrite(pScrn, 0x1a4, NV50OrRead(pScrn, SOR1, 0x6104));
183         NV50DisplayWrite(pScrn, 0x198, NV50OrRead(pScrn, SOR0, 0x6108));
184         NV50DisplayWrite(pScrn, 0x1a8, NV50OrRead(pScrn, SOR1, 0x6108));
185         NV50DisplayWrite(pScrn, 0x19c, NV50OrRead(pScrn, SOR0, 0x610c));
186         NV50DisplayWrite(pScrn, 0x1ac, NV50OrRead(pScrn, SOR1, 0x610c));
187         NV50DisplayWrite(pScrn, 0x1d0, NV50OrRead(pScrn, DAC0, 0xa000));
188         NV50DisplayWrite(pScrn, 0x1d4, NV50OrRead(pScrn, DAC1, 0xa000));
189         NV50DisplayWrite(pScrn, 0x1d8, NV50OrRead(pScrn, DAC2, 0xa000));
190         NV50DisplayWrite(pScrn, 0x1e0, NV50OrRead(pScrn, SOR0, 0xc000));
191         NV50DisplayWrite(pScrn, 0x1e4, NV50OrRead(pScrn, SOR1, 0xc000));
192         NV50OrWrite(pScrn, DAC0, 0xa004, 0x80550000);
193         NV50OrWrite(pScrn, DAC0, 0xa010, 0x00000001);
194         NV50OrWrite(pScrn, DAC1, 0xa004, 0x80550000);
195         NV50OrWrite(pScrn, DAC1, 0xa010, 0x00000001);
196         NV50OrWrite(pScrn, DAC2, 0xa004, 0x80550000);
197         NV50OrWrite(pScrn, DAC2, 0xa010, 0x00000001);
198
199         return TRUE;
200 }
201
202 Bool
203 NV50DispInit(ScrnInfoPtr pScrn)
204 {
205         if (NV50DisplayRead(pScrn, 0x24) & 0x100) {
206                 NV50DisplayWrite(pScrn, 0x24, 0x100);
207                 NV50DisplayWrite(pScrn, 0x94e8, NV50DisplayRead(pScrn, 0x94e8) & ~1);
208                 while (NV50DisplayRead(pScrn, 0x94e8) & 2);
209         }
210
211         NV50DisplayWrite(pScrn, 0x200, 0x2b00);
212         /* A bugfix (#12637) from the nv driver, to unlock the driver if it's left in a poor state */
213         do {
214                 CARD32 val = NV50DisplayRead(pScrn, 0x200);
215                 if ((val & 0x9f0000) == 0x20000)
216                         NV50DisplayWrite(pScrn, 0x200, val | 0x800000);
217
218                 if ((val & 0x3f0000) == 0x30000)
219                         NV50DisplayWrite(pScrn, 0x200, val | 0x200000);
220         } while ((NV50DisplayRead(pScrn, 0x200) & 0x1e0000) != 0);
221         NV50DisplayWrite(pScrn, 0x300, 0x1);
222         NV50DisplayWrite(pScrn, 0x200, 0x1000b03);
223         while (!(NV50DisplayRead(pScrn, 0x200) & 0x40000000));
224
225         NV50DisplayCommand(pScrn, 0x84, 0);
226         NV50DisplayCommand(pScrn, 0x88, 0);
227         NV50DisplayCommand(pScrn, 0x874, 0);
228         NV50DisplayCommand(pScrn, 0x800, 0);
229         NV50DisplayCommand(pScrn, 0x810, 0);
230         NV50DisplayCommand(pScrn, 0x82c, 0);
231
232         return TRUE;
233 }
234
235 void
236 NV50DispShutdown(ScrnInfoPtr pScrn)
237 {
238         xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn);
239         int i;
240
241         for(i = 0; i < xf86_config->num_crtc; i++) {
242                 xf86CrtcPtr crtc = xf86_config->crtc[i];
243
244                 NV50CrtcBlankScreen(crtc, TRUE);
245         }
246
247         NV50DisplayCommand(pScrn, 0x80, 0);
248
249         for(i = 0; i < xf86_config->num_crtc; i++) {
250                 xf86CrtcPtr crtc = xf86_config->crtc[i];
251
252                 if(crtc->enabled) {
253                         const CARD32 mask = 4 << NV50CrtcGetHead(crtc);
254
255                         NV50DisplayWrite(pScrn, 0x24, mask);
256                         while(!(NV50DisplayRead(pScrn, 0x24) & mask));
257                 }
258         }
259
260         NV50DisplayWrite(pScrn, 0x200, 0x0);
261         NV50DisplayWrite(pScrn, 0x300, 0x0);
262         while ((NV50DisplayRead(pScrn, 0x200) & 0x1e0000) != 0);
263         while ((NV50OrRead(pScrn, SOR0, 0xc030) & 0x10000000));
264         while ((NV50OrRead(pScrn, SOR1, 0xc030) & 0x10000000));
265 }
266
267 void
268 NV50CrtcDoModeFixup(DisplayModePtr dst, const DisplayModePtr src)
269 {
270         /* Magic mode timing fudge factor */
271         const int fudge = ((src->Flags & V_INTERLACE) && (src->Flags & V_DBLSCAN)) ? 2 : 1;
272         const int interlaceDiv = (src->Flags & V_INTERLACE) ? 2 : 1;
273
274         /* Stash the src timings in the Crtc fields in dst */
275         dst->CrtcHBlankStart = src->CrtcVTotal << 16 | src->CrtcHTotal;
276         dst->CrtcHSyncEnd = ((src->CrtcVSyncEnd - src->CrtcVSyncStart) / interlaceDiv - 1) << 16 |
277                 (src->CrtcHSyncEnd - src->CrtcHSyncStart - 1);
278         dst->CrtcHBlankEnd = ((src->CrtcVBlankEnd - src->CrtcVSyncStart) / interlaceDiv - fudge) << 16 |
279                 (src->CrtcHBlankEnd - src->CrtcHSyncStart - 1);
280         dst->CrtcHTotal = ((src->CrtcVTotal - src->CrtcVSyncStart + src->CrtcVBlankStart) / interlaceDiv - fudge) << 16 |
281                 (src->CrtcHTotal - src->CrtcHSyncStart + src->CrtcHBlankStart - 1);
282         dst->CrtcHSkew = ((src->CrtcVTotal + src->CrtcVBlankEnd - src->CrtcVSyncStart) / 2 - 2) << 16 |
283                 ((2*src->CrtcVTotal - src->CrtcVSyncStart + src->CrtcVBlankStart) / 2 - 2);
284 }
285
286 Bool
287 NV50CrtcModeFixup(xf86CrtcPtr crtc, DisplayModePtr mode, DisplayModePtr adjusted_mode)
288 {
289         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
290
291         if (nv_crtc->skipModeFixup)
292                 return TRUE;
293
294         NV50CrtcDoModeFixup(adjusted_mode, mode);
295         return TRUE;
296 }
297
298 void
299 NV50CrtcModeSet(xf86CrtcPtr crtc, DisplayModePtr mode, DisplayModePtr adjusted_mode, int x, int y)
300 {
301         ScrnInfoPtr pScrn = crtc->scrn;
302         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
303         const int HDisplay = adjusted_mode->HDisplay;
304         const int VDisplay = adjusted_mode->VDisplay;
305
306         nv_crtc->pclk = adjusted_mode->Clock;
307
308         /* NV50CrtcCommand includes head offset */
309         NV50CrtcCommand(crtc, NV50_CRTC0_CLOCK, adjusted_mode->Clock | 0x800000);
310         NV50CrtcCommand(crtc, NV50_CRTC0_INTERLACE, (adjusted_mode->Flags & V_INTERLACE) ? 2 : 0);
311         NV50CrtcCommand(crtc, 0x810, 0);
312         NV50CrtcCommand(crtc, 0x82c, 0);
313         /* This confirms my suspicion that recent nvidia hardware does no vertical programming */
314         /* NV40 still has it as a legacy mode, and i don't know how to do the "new" way, but it definately exists */
315         NV50CrtcCommand(crtc, NV50_CRTC0_HBLANK_START, adjusted_mode->CrtcHBlankStart);
316         NV50CrtcCommand(crtc, NV50_CRTC0_HSYNC_END, adjusted_mode->CrtcHSyncEnd);
317         NV50CrtcCommand(crtc, NV50_CRTC0_HBLANK_END, adjusted_mode->CrtcHBlankEnd);
318         NV50CrtcCommand(crtc, NV50_CRTC0_HTOTAL, adjusted_mode->CrtcHTotal);
319         if(adjusted_mode->Flags & V_INTERLACE) {
320                 NV50CrtcCommand(crtc, 0x824, adjusted_mode->CrtcHSkew);
321         }
322         NV50CrtcCommand(crtc, NV50_CRTC0_FB_SIZE, pScrn->virtualY << 16 | pScrn->virtualX);
323         NV50CrtcCommand(crtc, NV50_CRTC0_PITCH, pScrn->displayWidth * (pScrn->bitsPerPixel / 8) | 0x100000);
324         switch(pScrn->depth) {
325                 case 8:
326                         NV50CrtcCommand(crtc, NV50_CRTC0_DEPTH, NV50_CRTC0_DEPTH_8BPP); 
327                         break;
328                 case 15:
329                         NV50CrtcCommand(crtc, NV50_CRTC0_DEPTH, NV50_CRTC0_DEPTH_15BPP);
330                         break;
331                 case 16:
332                         NV50CrtcCommand(crtc, NV50_CRTC0_DEPTH, NV50_CRTC0_DEPTH_16BPP);
333                         break;
334                 case 24:
335                         NV50CrtcCommand(crtc, NV50_CRTC0_DEPTH, NV50_CRTC0_DEPTH_24BPP); 
336                         break;
337         }
338         NV50CrtcSetDither(crtc, nv_crtc->dither, FALSE);
339         NV50CrtcCommand(crtc, 0x8a8, 0x40000);
340         NV50CrtcCommand(crtc, NV50_CRTC0_FB_POS, y << 16 | x);
341         NV50CrtcCommand(crtc, NV50_CRTC0_SCRN_SIZE, VDisplay << 16 | HDisplay);
342         NV50CrtcCommand(crtc, 0x8d4, 0);
343
344         NV50CrtcBlankScreen(crtc, FALSE);
345 }
346
347 void
348 NV50CrtcBlankScreen(xf86CrtcPtr crtc, Bool blank)
349 {
350         ScrnInfoPtr pScrn = crtc->scrn;
351         NVPtr pNv = NVPTR(pScrn);
352         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
353
354         if(blank) {
355                 NV50CrtcShowHideCursor(crtc, FALSE, FALSE);
356
357                 NV50CrtcCommand(crtc, NV50_CRTC0_CLUT_MODE, NV50_CRTC0_CLUT_MODE_BLANK);
358                 NV50CrtcCommand(crtc, NV50_CRTC0_CLUT_OFFSET, 0);
359                 if(pNv->NVArch != 0x50)
360                         NV50CrtcCommand(crtc, 0x85c, 0);
361                 NV50CrtcCommand(crtc, 0x874, 0);
362                 if(pNv->NVArch != 0x50)
363                         NV50CrtcCommand(crtc, 0x89c, 0);
364         } else {
365                 NV50CrtcCommand(crtc, NV50_CRTC0_FB_OFFSET, pNv->FB->offset >> 8);
366                 NV50CrtcCommand(crtc, 0x864, 0);
367                 NV50DisplayWrite(pScrn, 0x380, 0);
368                 /*XXX: in "nv" this is total vram size.  our RamAmountKBytes is clamped
369                 *     to 256MiB.
370                 */
371                 NV50DisplayWrite(pScrn, NV50_CRTC0_RAM_AMOUNT, pNv->RamAmountKBytes * 1024 - 1);
372                 NV50DisplayWrite(pScrn, 0x388, 0x150000);
373                 NV50DisplayWrite(pScrn, 0x38C, 0);
374                 NV50CrtcCommand(crtc, NV50_CRTC0_CURSOR_OFFSET, pNv->Cursor->offset >> 8);
375                 if(pNv->NVArch != 0x50)
376                         NV50CrtcCommand(crtc, 0x89c, 1);
377                 if(nv_crtc->cursorVisible)
378                         NV50CrtcShowHideCursor(crtc, TRUE, FALSE);
379                 NV50CrtcCommand(crtc, NV50_CRTC0_CLUT_MODE, 
380                         pScrn->depth == 8 ? NV50_CRTC0_CLUT_MODE_OFF : NV50_CRTC0_CLUT_MODE_ON);
381                 NV50CrtcCommand(crtc, NV50_CRTC0_CLUT_OFFSET, pNv->CLUT->offset >> 8);
382                 if(pNv->NVArch != 0x50)
383                         NV50CrtcCommand(crtc, 0x85c, 1);
384                 NV50CrtcCommand(crtc, 0x874, 1);
385      }
386 }
387
388 /******************************** Cursor stuff ********************************/
389 static void NV50CrtcShowHideCursor(xf86CrtcPtr crtc, Bool show, Bool update)
390 {
391         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
392
393         NV50CrtcCommand(crtc, NV50_CRTC0_CURSOR0, 
394                 show ? NV50_CRTC0_CURSOR0_SHOW : NV50_CRTC0_CURSOR0_HIDE);
395         if(update) {
396                 nv_crtc->cursorVisible = show;
397                 NV50CrtcCommand(crtc, 0x80, 0);
398         }
399 }
400
401 void NV50CrtcShowCursor(xf86CrtcPtr crtc)
402 {
403         NV50CrtcShowHideCursor(crtc, TRUE, TRUE);
404 }
405
406 void NV50CrtcHideCursor(xf86CrtcPtr crtc)
407 {
408         NV50CrtcShowHideCursor(crtc, FALSE, TRUE);
409 }
410
411 /******************************** CRTC stuff ********************************/
412
413 void
414 NV50CrtcPrepare(xf86CrtcPtr crtc)
415 {
416         ScrnInfoPtr pScrn = crtc->scrn;
417         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
418         xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn);
419         int i;
420
421         for(i = 0; i < xf86_config->num_output; i++) {
422                 xf86OutputPtr output = xf86_config->output[i];
423
424                 if(!output->crtc)
425                         output->funcs->mode_set(output, NULL, NULL);
426         }
427
428         nv_crtc->skipModeFixup = FALSE;
429 }
430
431 void
432 NV50CrtcSkipModeFixup(xf86CrtcPtr crtc)
433 {
434         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
435         nv_crtc->skipModeFixup = TRUE;
436 }
437
438 void
439 NV50CrtcSetDither(xf86CrtcPtr crtc, Bool dither, Bool update)
440 {
441         NV50CrtcPrivPtr nv_crtc = crtc->driver_private;
442
443         nv_crtc->dither = dither;
444
445         NV50CrtcCommand(crtc, 0x8a0, dither ? 0x11 : 0);
446         if(update) 
447                 NV50CrtcCommand(crtc, 0x80, 0);
448 }
449
450 static void ComputeAspectScale(DisplayModePtr mode, int *outX, int *outY)
451 {
452         float scaleX, scaleY, scale;
453
454         scaleX = mode->CrtcHDisplay / (float)mode->HDisplay;
455         scaleY = mode->CrtcVDisplay / (float)mode->VDisplay;
456
457         if(scaleX > scaleY)
458                 scale = scaleY;
459         else
460                 scale = scaleX;
461
462         *outX = mode->HDisplay * scale;
463         *outY = mode->VDisplay * scale;
464 }
465
466 void NV50CrtcSetScale(xf86CrtcPtr crtc, DisplayModePtr mode, enum NV50ScaleMode scale)
467 {
468         int outX = 0, outY = 0;
469
470         switch(scale) {
471                 case NV50_SCALE_ASPECT:
472                         ComputeAspectScale(mode, &outX, &outY);
473                         break;
474                 case NV50_SCALE_OFF:
475                 case NV50_SCALE_FILL:
476                         outX = mode->CrtcHDisplay;
477                         outY = mode->CrtcVDisplay;
478                         break;
479                 case NV50_SCALE_CENTER:
480                         outX = mode->HDisplay;
481                         outY = mode->VDisplay;
482                         break;
483         }
484
485         if ((mode->Flags & V_DBLSCAN) || (mode->Flags & V_INTERLACE) ||
486                 mode->HDisplay != outX || mode->VDisplay != outY) {
487                 NV50CrtcCommand(crtc, 0x8a4, 9);
488         } else {
489                 NV50CrtcCommand(crtc, 0x8a4, 0);
490         }
491         NV50CrtcCommand(crtc, 0x8d8, outY << 16 | outX);
492         NV50CrtcCommand(crtc, 0x8dc, outY << 16 | outX);
493 }
494
495 void
496 NV50CrtcCommit(xf86CrtcPtr crtc)
497 {
498         xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(crtc->scrn);
499         int i, crtc_mask = 0;
500
501         /* If any heads are unused, blank them */
502         for(i = 0; i < xf86_config->num_output; i++) {
503                 xf86OutputPtr output = xf86_config->output[i];
504
505                 if (output->crtc) {
506                         /* XXXagp: This assumes that xf86_config->crtc[i] is HEADi */
507                         crtc_mask |= 1 << NV50CrtcGetHead(output->crtc);
508                 }
509         }
510
511         for(i = 0; i < xf86_config->num_crtc; i++) {
512                 if(!((1 << i) & crtc_mask)) {
513                         NV50CrtcBlankScreen(xf86_config->crtc[i], TRUE);
514                 }
515         }
516
517         NV50CrtcCommand(crtc, 0x80, 0);
518 }
519