randr12: better handling for output types in nv_crtc
[nouveau] / src / nv50_sor.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 #define DPMS_SERVER
25 #include <X11/extensions/dpms.h>
26 #include <X11/Xatom.h>
27
28 #include "nv_include.h"
29 #include "nv50_type.h"
30 #include "nv50_display.h"
31 #include "nv50_output.h"
32
33 void
34 NV50SorSetPClk(xf86OutputPtr output, int pclk)
35 {
36         NVOutputPrivatePtr nv_output = output->driver_private;
37         ScrnInfoPtr pScrn = output->scrn;
38         NVPtr pNv = NVPTR(pScrn);
39         const int limit = 165000;
40
41         NVWrite(pNv, 0x00614300 + nv_output->output_resource * 0x800, (pclk > limit) ? 0x101 : 0);
42 }
43
44 static void
45 NV50SorDPMSSet(xf86OutputPtr output, int mode)
46 {
47         NVOutputPrivatePtr nv_output = output->driver_private;
48         ScrnInfoPtr pScrn = output->scrn;
49         NVPtr pNv = NVPTR(pScrn);
50         CARD32 tmp;
51
52         while((NVRead(pNv, 0x0061c004 + nv_output->output_resource * 0x800) & 0x80000000));
53
54         tmp = NVRead(pNv, 0x0061c004 + nv_output->output_resource * 0x800);
55         tmp |= 0x80000000;
56
57         if(mode == DPMSModeOn)
58                 tmp |= 1;
59         else
60                 tmp &= ~1;
61
62         NVWrite(pNv, 0x0061c004 + nv_output->output_resource * 0x800, tmp);
63         while((NVRead(pNv, 0x0061c030 + nv_output->output_resource * 0x800) & 0x10000000));
64 }
65
66 static int
67 NV50TMDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
68 {
69         // Disable dual-link modes until I can find a way to make them work
70         // reliably.
71         if (mode->Clock > 165000)
72                 return MODE_CLOCK_HIGH;
73
74         return NV50OutputModeValid(output, mode);
75 }
76
77 static int
78 NV50LVDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
79 {
80         NVOutputPrivatePtr nv_output = output->driver_private;
81         DisplayModePtr native = nv_output->native_mode;
82
83         // Ignore modes larger than the native res.
84         if (mode->HDisplay > native->HDisplay || mode->VDisplay > native->VDisplay)
85                 return MODE_PANEL;
86
87         return NV50OutputModeValid(output, mode);
88 }
89
90 static void
91 NV50SorModeSet(xf86OutputPtr output, DisplayModePtr mode,
92                 DisplayModePtr adjusted_mode)
93 {
94         ScrnInfoPtr pScrn = output->scrn;
95         NVOutputPrivatePtr nv_output = output->driver_private;
96         const int sorOff = 0x40 * nv_output->output_resource;
97         CARD32 type;
98
99         if(!adjusted_mode) {
100                 /* Disconnect the SOR */
101                 NV50DisplayCommand(pScrn, 0x600 + sorOff, 0);
102                 return;
103         }
104
105         if (nv_output->type == OUTPUT_LVDS) {
106                 type = 0;
107         } else
108         if (adjusted_mode->Clock > 165000) {
109                 type = 0x500;
110         } else {
111                 type = 0x100;
112         }
113
114         // This wouldn't be necessary, but the server is stupid and calls
115         // NV50SorDPMSSet after the output is disconnected, even though the hardware
116         // turns it off automatically.
117         NV50SorDPMSSet(output, DPMSModeOn);
118
119         NV50DisplayCommand(pScrn, 0x600 + sorOff,
120                 (NV50CrtcGetHead(output->crtc) == HEAD0 ? 1 : 2) | type |
121                 ((adjusted_mode->Flags & V_NHSYNC) ? 0x1000 : 0) |
122                 ((adjusted_mode->Flags & V_NVSYNC) ? 0x2000 : 0));
123
124         NV50CrtcSetScale(output->crtc, adjusted_mode, nv_output->scaling_mode);
125 }
126
127 static xf86OutputStatus
128 NV50SorDetect(xf86OutputPtr output)
129 {
130         NVOutputPrivatePtr nv_output = output->driver_private;
131         xf86MonPtr ddc_mon;
132
133         if (nv_output->pDDCBus == NULL)
134                 return XF86OutputStatusDisconnected;
135
136         ddc_mon = xf86OutputGetEDID(output, nv_output->pDDCBus);
137         if (!ddc_mon)
138                 return XF86OutputStatusDisconnected;
139
140         if (!ddc_mon->features.input_type) /* Analog? */
141                 return XF86OutputStatusDisconnected;
142
143         if (ddc_mon)
144                 xf86OutputSetEDID(output, ddc_mon);
145
146         return XF86OutputStatusConnected;
147 }
148
149 static xf86OutputStatus
150 NV50SorLVDSDetect(xf86OutputPtr output)
151 {
152         /* Assume LVDS is always connected */
153         return XF86OutputStatusConnected;
154 }
155
156 static void
157 NV50SorDestroy(xf86OutputPtr output)
158 {
159         NVOutputPrivatePtr nv_output = output->driver_private;
160
161         NV50OutputDestroy(output);
162
163         xf86DeleteMode(&nv_output->native_mode, nv_output->native_mode);
164
165         xfree(output->driver_private);
166         output->driver_private = NULL;
167 }
168
169 static void
170 NV50SorSetModeBackend(DisplayModePtr dst, const DisplayModePtr src)
171 {
172         // Stash the backend mode timings from src into dst
173         dst->Clock           = src->Clock;
174         dst->Flags           = src->Flags;
175         dst->CrtcHDisplay    = src->CrtcHDisplay;
176         dst->CrtcHBlankStart = src->CrtcHBlankStart;
177         dst->CrtcHSyncStart  = src->CrtcHSyncStart;
178         dst->CrtcHSyncEnd    = src->CrtcHSyncEnd;
179         dst->CrtcHBlankEnd   = src->CrtcHBlankEnd;
180         dst->CrtcHTotal      = src->CrtcHTotal;
181         dst->CrtcHSkew       = src->CrtcHSkew;
182         dst->CrtcVDisplay    = src->CrtcVDisplay;
183         dst->CrtcVBlankStart = src->CrtcVBlankStart;
184         dst->CrtcVSyncStart  = src->CrtcVSyncStart;
185         dst->CrtcVSyncEnd    = src->CrtcVSyncEnd;
186         dst->CrtcVBlankEnd   = src->CrtcVBlankEnd;
187         dst->CrtcVTotal      = src->CrtcVTotal;
188         dst->CrtcHAdjusted   = src->CrtcHAdjusted;
189         dst->CrtcVAdjusted   = src->CrtcVAdjusted;
190 }
191
192 static Bool
193 NV50SorModeFixup(xf86OutputPtr output, DisplayModePtr mode,
194                  DisplayModePtr adjusted_mode)
195 {
196         NVOutputPrivatePtr nv_output = output->driver_private;
197         DisplayModePtr native = nv_output->native_mode;
198
199         if(native && nv_output->scaling_mode != SCALE_PANEL) {
200                 NV50SorSetModeBackend(adjusted_mode, native);
201                 // This mode is already "fixed"
202                 NV50CrtcSkipModeFixup(output->crtc);
203         }
204
205         return TRUE;
206 }
207
208 static Bool
209 NV50SorTMDSModeFixup(xf86OutputPtr output, DisplayModePtr mode,
210                         DisplayModePtr adjusted_mode)
211 {
212         int scrnIndex = output->scrn->scrnIndex;
213         NVOutputPrivatePtr nv_output = output->driver_private;
214         DisplayModePtr modes = output->probed_modes;
215
216         xf86DeleteMode(&nv_output->native_mode, nv_output->native_mode);
217
218         if(modes) {
219                 // Find the preferred mode and use that as the "native" mode.
220                 // If no preferred mode is available, use the first one.
221                 DisplayModePtr mode;
222
223                 // Find the preferred mode.
224                 for(mode = modes; mode; mode = mode->next) {
225                         if(mode->type & M_T_PREFERRED) {
226                                 xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
227                                                 "%s: preferred mode is %s\n",
228                                                 output->name, mode->name);
229                                 break;
230                         }
231                 }
232
233                 // XXX: May not want to allow scaling if no preferred mode is found.
234                 if(!mode) {
235                         mode = modes;
236                         xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
237                                 "%s: no preferred mode found, using %s\n",
238                                 output->name, mode->name);
239                 }
240
241                 nv_output->native_mode = xf86DuplicateMode(mode);
242                 NV50CrtcDoModeFixup(nv_output->native_mode, mode);
243         }
244
245         return NV50SorModeFixup(output, mode, adjusted_mode);
246 }
247
248 static DisplayModePtr
249 NV50SorGetLVDSModes(xf86OutputPtr output)
250 {
251         NVOutputPrivatePtr nv_output = output->driver_private;
252         return xf86DuplicateMode(nv_output->native_mode);
253 }
254
255 #define MAKE_ATOM(a) MakeAtom((a), sizeof(a) - 1, TRUE);
256
257 struct property {
258         Atom atom;
259         INT32 range[2];
260 };
261
262 static struct {
263         struct property dither;
264         struct property scale;
265 } properties;
266
267 static void
268 NV50SorCreateResources(xf86OutputPtr output)
269 {
270         ScrnInfoPtr pScrn = output->scrn;
271         NVPtr pNv = NVPTR(pScrn);
272         int data, err;
273         const char *s;
274
275         /******** dithering ********/
276         properties.dither.atom = MAKE_ATOM("dither");
277         properties.dither.range[0] = 0;
278         properties.dither.range[1] = 1;
279         err = RRConfigureOutputProperty(output->randr_output,
280                                         properties.dither.atom, FALSE, TRUE, FALSE,
281                                         2, properties.dither.range);
282         if(err)
283                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
284                         "Failed to configure dithering property for %s: error %d\n",
285                         output->name, err);
286
287         // Set the default value
288         data = pNv->FPDither;
289         err = RRChangeOutputProperty(output->randr_output, properties.dither.atom,
290                                         XA_INTEGER, 32, PropModeReplace, 1, &data,
291                                         FALSE, FALSE);
292         if(err)
293                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
294                         "Failed to set dithering property for %s: error %d\n",
295                         output->name, err);
296
297         /******** scaling ********/
298         properties.scale.atom = MAKE_ATOM("scale");
299         err = RRConfigureOutputProperty(output->randr_output,
300                                         properties.scale.atom, FALSE, FALSE,
301                                         FALSE, 0, NULL);
302         if(err)
303                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
304                         "Failed to configure scaling property for %s: error %d\n",
305                         output->name, err);
306
307         // Set the default value
308         s = "aspect";
309         err = RRChangeOutputProperty(output->randr_output, properties.scale.atom,
310                                         XA_STRING, 8, PropModeReplace, strlen(s),
311                                         (pointer)s, FALSE, FALSE);
312         if(err)
313                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
314                         "Failed to set scaling property for %s: error %d\n",
315                         output->name, err);
316 }
317
318 static Bool
319 NV50SorSetProperty(xf86OutputPtr output, Atom prop, RRPropertyValuePtr val)
320 {
321         NVOutputPrivatePtr nv_output = output->driver_private;
322
323         if(prop == properties.dither.atom) {
324                 INT32 i;
325
326                 if (val->type != XA_INTEGER || val->format != 32 || val->size != 1)
327                         return FALSE;
328
329                 i = *(INT32*)val->data;
330                 if (i < properties.dither.range[0] || i > properties.dither.range[1])
331                         return FALSE;
332
333                 NV50CrtcSetDither(output->crtc, i, TRUE);
334                 return TRUE;
335         } else if (prop == properties.scale.atom) {
336                 const char *s;
337                 enum scaling_modes oldScale, scale;
338                 int i;
339                 const struct {
340                         const char *name;
341                         enum scaling_modes scale;
342                 } modes[] = {
343                         { "panel", SCALE_PANEL },
344                         { "aspect", SCALE_ASPECT },
345                         { "fullscreen",   SCALE_FULLSCREEN },
346                         { "noscale", SCALE_NOSCALE },
347                         { NULL,     0 },
348                 };
349
350                 if (val->type != XA_STRING || val->format != 8)
351                         return FALSE;
352                 s = (char*)val->data;
353
354                 for (i = 0; modes[i].name; i++) {
355                         const char *name = modes[i].name;
356                         const int len = strlen(name);
357
358                         if(val->size == len && !strncmp(name, s, len)) {
359                                 scale = modes[i].scale;
360                                 break;
361                         }
362                 }
363                 if (!modes[i].name)
364                         return FALSE;
365                 if (scale == SCALE_PANEL && nv_output->type == OUTPUT_LVDS)
366                         // LVDS requires scaling
367                         return FALSE;
368
369                 oldScale = nv_output->scaling_mode;
370                 nv_output->scaling_mode = scale;
371                 if (output->crtc) {
372                         xf86CrtcPtr crtc = output->crtc;
373
374                         if (!xf86CrtcSetMode(crtc, &crtc->desiredMode, crtc->desiredRotation,
375                                         crtc->desiredX, crtc->desiredY)) {
376                                 xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
377                                                 "Failed to set scaling to %s for output %s\n",
378                                                 modes[i].name, output->name);
379
380                                 // Restore old scale and try again.
381                                 nv_output->scaling_mode = oldScale;
382                                 if (!xf86CrtcSetMode(crtc, &crtc->desiredMode,
383                                                         crtc->desiredRotation, crtc->desiredX,
384                                                         crtc->desiredY)) {
385                                         xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
386                                                 "Failed to restore old scaling for output %s\n",
387                                                 output->name);
388                                 }
389
390                                 return FALSE;
391                         }
392                 }
393                 return TRUE;
394         }
395
396         return FALSE;
397 }
398
399 static const xf86OutputFuncsRec NV50SorTMDSOutputFuncs = {
400         .dpms = NV50SorDPMSSet,
401         .save = NULL,
402         .restore = NULL,
403         .mode_valid = NV50TMDSModeValid,
404         .mode_fixup = NV50SorTMDSModeFixup,
405         .prepare = NV50OutputPrepare,
406         .commit = NV50OutputCommit,
407         .mode_set = NV50SorModeSet,
408         .detect = NV50SorDetect,
409         .get_modes = NV50OutputGetDDCModes,
410         .create_resources = NV50SorCreateResources,
411         .set_property = NV50SorSetProperty,
412         .destroy = NV50SorDestroy,
413 };
414
415 static const xf86OutputFuncsRec NV50SorLVDSOutputFuncs = {
416         .dpms = NV50SorDPMSSet,
417         .save = NULL,
418         .restore = NULL,
419         .mode_valid = NV50LVDSModeValid,
420         .mode_fixup = NV50SorModeFixup,
421         .prepare = NV50OutputPrepare,
422         .commit = NV50OutputCommit,
423         .mode_set = NV50SorModeSet,
424         .detect = NV50SorLVDSDetect,
425         .get_modes = NV50SorGetLVDSModes,
426         .create_resources = NV50SorCreateResources,
427         .set_property = NV50SorSetProperty,
428         .destroy = NV50SorDestroy,
429 };
430
431 static DisplayModePtr
432 ReadLVDSNativeMode(ScrnInfoPtr pScrn, const int off)
433 {
434         NVPtr pNv = NVPTR(pScrn);
435         DisplayModePtr mode = xnfcalloc(1, sizeof(DisplayModeRec));
436         const CARD32 size = NVRead(pNv, 0x00610b4c + off);
437         const int width = size & 0x3fff;
438         const int height = (size >> 16) & 0x3fff;
439
440         mode->HDisplay = mode->CrtcHDisplay = width;
441         mode->VDisplay = mode->CrtcVDisplay = height;
442         mode->Clock = NVRead(pNv, 0x00610ad4 + off) & 0x3fffff;
443         mode->CrtcHBlankStart = NVRead(pNv, 0x00610afc + off);
444         mode->CrtcHSyncEnd = NVRead(pNv, 0x00610b04 + off);
445         mode->CrtcHBlankEnd = NVRead(pNv, 0x00610ae8 + off);
446         mode->CrtcHTotal = NVRead(pNv, 0x00610af4 + off);
447
448         mode->next = mode->prev = NULL;
449         mode->status = MODE_OK;
450         mode->type = M_T_DRIVER | M_T_PREFERRED;
451
452         xf86SetModeDefaultName(mode);
453
454         return mode;
455 }
456
457 static DisplayModePtr
458 GetLVDSNativeMode(ScrnInfoPtr pScrn)
459 {
460         NVPtr pNv = NVPTR(pScrn);
461         CARD32 val = NVRead(pNv, 0x00610050);
462
463         if ((val & 0x3) == 0x2) {
464                 return ReadLVDSNativeMode(pScrn, 0);
465         } else if ((val & 0x300) == 0x200) {
466                 return ReadLVDSNativeMode(pScrn, 0x540);
467         }
468
469         return NULL;
470 }
471
472 xf86OutputPtr
473 NV50CreateSor(ScrnInfoPtr pScrn, ORNum or, NVOutputType type)
474 {
475         NVOutputPrivatePtr nv_output = xnfcalloc(sizeof(*nv_output), 1);
476         NVPtr pNv = NVPTR(pScrn);
477         xf86OutputPtr output;
478         char orName[5];
479         const xf86OutputFuncsRec *funcs;
480
481         if(!nv_output)
482                 return NULL;
483
484         if(type == OUTPUT_LVDS) {
485                 strcpy(orName, "LVDS");
486                 funcs = &NV50SorLVDSOutputFuncs;
487
488                 nv_output->native_mode = GetLVDSNativeMode(pScrn);
489
490                 if(!nv_output->native_mode) {
491                         xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
492                                 "Failed to find LVDS native mode\n");
493                         xfree(nv_output);
494                         return NULL;
495                 }
496
497                 xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s native size %dx%d\n",
498                         orName, nv_output->native_mode->HDisplay,
499                         nv_output->native_mode->VDisplay);
500         } else {
501                 snprintf(orName, 5, "DVI%d", or);
502                 funcs = &NV50SorTMDSOutputFuncs;
503         }
504
505         output = xf86OutputCreate(pScrn, funcs, orName);
506
507         nv_output->output_resource = or;
508         nv_output->type = type;
509         output->driver_private = nv_output;
510         output->interlaceAllowed = TRUE;
511         output->doubleScanAllowed = TRUE;
512
513         if (type != OUTPUT_LVDS) {
514                 NVWrite(pNv, 0x0061c00c + nv_output->output_resource * 0x800, 0x03010700);
515                 NVWrite(pNv, 0x0061c010 + nv_output->output_resource * 0x800, 0x0000152f);
516                 NVWrite(pNv, 0x0061c014 + nv_output->output_resource * 0x800, 0x00000000);
517                 NVWrite(pNv, 0x0061c018 + nv_output->output_resource * 0x800, 0x00245af8);
518         }
519
520         return output;
521 }
522