NV50: Port an LVDS detection fix from nv.
[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 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #ifdef ENABLE_RANDR12
29
30 #define DPMS_SERVER
31 #include <X11/extensions/dpms.h>
32 #include <X11/Xatom.h>
33
34 #include "nv_include.h"
35 #include "nv50_type.h"
36 #include "nv50_display.h"
37 #include "nv50_output.h"
38
39 static void
40 NV50SorSetPClk(xf86OutputPtr output, int pclk)
41 {
42         const int limit = 165000;
43
44         NV50OutputWrite(output, 0x4300, (pclk > limit) ? 0x101 : 0);
45 }
46
47 static void
48 NV50SorDPMSSet(xf86OutputPtr output, int mode)
49 {
50         CARD32 tmp;
51
52         while((NV50OutputRead(output, 0xc004) & 0x80000000));
53
54         tmp = NV50OutputRead(output, 0xc004);
55         tmp |= 0x80000000;
56
57         if(mode == DPMSModeOn)
58                 tmp |= 1;
59         else
60                 tmp &= ~1;
61
62         NV50OutputWrite(output, 0xc004, tmp);
63         while((NV50OutputRead(output, 0xc030) & 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         NV50OutputPrivPtr nv_output = output->driver_private;
81         DisplayModePtr native = nv_output->nativeMode;
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         NV50OutputPrivPtr nv_output = output->driver_private;
96         const int sorOff = 0x40 * nv_output->or;
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->panelType == 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->scale);
125 }
126
127 static xf86OutputStatus
128 NV50SorDetect(xf86OutputPtr output)
129 {
130         NV50OutputPrivPtr nv_output = output->driver_private;
131
132         /* Assume physical status isn't going to change before the BlockHandler */
133         if(nv_output->cached_status != XF86OutputStatusUnknown)
134                 return nv_output->cached_status;
135
136         NV50OutputPartnersDetect(nv_output->partner, output, nv_output->i2c);
137         return nv_output->cached_status;
138 }
139
140 static xf86OutputStatus
141 NV50SorLVDSDetect(xf86OutputPtr output)
142 {
143         /* Assume LVDS is always connected */
144         return XF86OutputStatusConnected;
145 }
146
147 static void
148 NV50SorDestroy(xf86OutputPtr output)
149 {
150         NV50OutputPrivPtr nv_output = output->driver_private;
151
152         NV50OutputDestroy(output);
153
154         xf86DeleteMode(&nv_output->nativeMode, nv_output->nativeMode);
155
156         xfree(output->driver_private);
157         output->driver_private = NULL;
158 }
159
160 static void
161 NV50SorSetModeBackend(DisplayModePtr dst, const DisplayModePtr src)
162 {
163         // Stash the backend mode timings from src into dst
164         dst->Clock           = src->Clock;
165         dst->Flags           = src->Flags;
166         dst->CrtcHDisplay    = src->CrtcHDisplay;
167         dst->CrtcHBlankStart = src->CrtcHBlankStart;
168         dst->CrtcHSyncStart  = src->CrtcHSyncStart;
169         dst->CrtcHSyncEnd    = src->CrtcHSyncEnd;
170         dst->CrtcHBlankEnd   = src->CrtcHBlankEnd;
171         dst->CrtcHTotal      = src->CrtcHTotal;
172         dst->CrtcHSkew       = src->CrtcHSkew;
173         dst->CrtcVDisplay    = src->CrtcVDisplay;
174         dst->CrtcVBlankStart = src->CrtcVBlankStart;
175         dst->CrtcVSyncStart  = src->CrtcVSyncStart;
176         dst->CrtcVSyncEnd    = src->CrtcVSyncEnd;
177         dst->CrtcVBlankEnd   = src->CrtcVBlankEnd;
178         dst->CrtcVTotal      = src->CrtcVTotal;
179         dst->CrtcHAdjusted   = src->CrtcHAdjusted;
180         dst->CrtcVAdjusted   = src->CrtcVAdjusted;
181 }
182
183 static Bool
184 NV50SorModeFixup(xf86OutputPtr output, DisplayModePtr mode,
185                  DisplayModePtr adjusted_mode)
186 {
187         NV50OutputPrivPtr nv_output = output->driver_private;
188         DisplayModePtr native = nv_output->nativeMode;
189
190         if(native && nv_output->scale != NV50_SCALE_OFF) {
191                 NV50SorSetModeBackend(adjusted_mode, native);
192                 // This mode is already "fixed"
193                 NV50CrtcSkipModeFixup(output->crtc);
194         }
195
196         return TRUE;
197 }
198
199 static Bool
200 NV50SorTMDSModeFixup(xf86OutputPtr output, DisplayModePtr mode,
201                         DisplayModePtr adjusted_mode)
202 {
203         int scrnIndex = output->scrn->scrnIndex;
204         NV50OutputPrivPtr nv_output = output->driver_private;
205         DisplayModePtr modes = output->probed_modes;
206
207         xf86DeleteMode(&nv_output->nativeMode, nv_output->nativeMode);
208
209         if(modes) {
210                 // Find the preferred mode and use that as the "native" mode.
211                 // If no preferred mode is available, use the first one.
212                 DisplayModePtr mode;
213
214                 // Find the preferred mode.
215                 for(mode = modes; mode; mode = mode->next) {
216                         if(mode->type & M_T_PREFERRED) {
217                                 xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
218                                                 "%s: preferred mode is %s\n",
219                                                 output->name, mode->name);
220                                 break;
221                         }
222                 }
223
224                 // XXX: May not want to allow scaling if no preferred mode is found.
225                 if(!mode) {
226                         mode = modes;
227                         xf86DrvMsgVerb(scrnIndex, X_INFO, 5,
228                                 "%s: no preferred mode found, using %s\n",
229                                 output->name, mode->name);
230                 }
231
232                 nv_output->nativeMode = xf86DuplicateMode(mode);
233                 NV50CrtcDoModeFixup(nv_output->nativeMode, mode);
234         }
235
236         return NV50SorModeFixup(output, mode, adjusted_mode);
237 }
238
239 static DisplayModePtr
240 NV50SorGetLVDSModes(xf86OutputPtr output)
241 {
242         NV50OutputPrivPtr nv_output = output->driver_private;
243         return xf86DuplicateMode(nv_output->nativeMode);
244 }
245
246 #ifdef RANDR_12_INTERFACE
247 #define MAKE_ATOM(a) MakeAtom((a), sizeof(a) - 1, TRUE);
248
249 struct property {
250         Atom atom;
251         INT32 range[2];
252 };
253
254 static struct {
255         struct property dither;
256         struct property scale;
257 } properties;
258
259 static void
260 NV50SorCreateResources(xf86OutputPtr output)
261 {
262         ScrnInfoPtr pScrn = output->scrn;
263         NVPtr pNv = NVPTR(pScrn);
264         int data, err;
265         const char *s;
266
267         /******** dithering ********/
268         properties.dither.atom = MAKE_ATOM("dither");
269         properties.dither.range[0] = 0;
270         properties.dither.range[1] = 1;
271         err = RRConfigureOutputProperty(output->randr_output,
272                                         properties.dither.atom, FALSE, TRUE, FALSE,
273                                         2, properties.dither.range);
274         if(err)
275                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
276                         "Failed to configure dithering property for %s: error %d\n",
277                         output->name, err);
278
279         // Set the default value
280         data = pNv->FPDither;
281         err = RRChangeOutputProperty(output->randr_output, properties.dither.atom,
282                                         XA_INTEGER, 32, PropModeReplace, 1, &data,
283                                         FALSE, FALSE);
284         if(err)
285                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
286                         "Failed to set dithering property for %s: error %d\n",
287                         output->name, err);
288
289         /******** scaling ********/
290         properties.scale.atom = MAKE_ATOM("scale");
291         err = RRConfigureOutputProperty(output->randr_output,
292                                         properties.scale.atom, FALSE, FALSE,
293                                         FALSE, 0, NULL);
294         if(err)
295                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
296                         "Failed to configure scaling property for %s: error %d\n",
297                         output->name, err);
298
299         // Set the default value
300         s = "aspect";
301         err = RRChangeOutputProperty(output->randr_output, properties.scale.atom,
302                                         XA_STRING, 8, PropModeReplace, strlen(s),
303                                         (pointer)s, FALSE, FALSE);
304         if(err)
305                 xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
306                         "Failed to set scaling property for %s: error %d\n",
307                         output->name, err);
308 }
309
310 static Bool
311 NV50SorSetProperty(xf86OutputPtr output, Atom prop, RRPropertyValuePtr val)
312 {
313         NV50OutputPrivPtr nv_output = output->driver_private;
314
315         if(prop == properties.dither.atom) {
316                 INT32 i;
317
318                 if (val->type != XA_INTEGER || val->format != 32 || val->size != 1)
319                         return FALSE;
320
321                 i = *(INT32*)val->data;
322                 if (i < properties.dither.range[0] || i > properties.dither.range[1])
323                         return FALSE;
324
325                 NV50CrtcSetDither(output->crtc, i, TRUE);
326                 return TRUE;
327         } else if (prop == properties.scale.atom) {
328                 const char *s;
329                 enum NV50ScaleMode oldScale, scale;
330                 int i;
331                 const struct {
332                         const char *name;
333                         enum NV50ScaleMode scale;
334                 } modes[] = {
335                         { "off",    NV50_SCALE_OFF },
336                         { "aspect", NV50_SCALE_ASPECT },
337                         { "fill",   NV50_SCALE_FILL },
338                         { "center", NV50_SCALE_CENTER },
339                         { NULL,     0 },
340                 };
341
342                 if (val->type != XA_STRING || val->format != 8)
343                         return FALSE;
344                 s = (char*)val->data;
345
346                 for (i = 0; modes[i].name; i++) {
347                         const char *name = modes[i].name;
348                         const int len = strlen(name);
349
350                         if(val->size == len && !strncmp(name, s, len)) {
351                                 scale = modes[i].scale;
352                                 break;
353                         }
354                 }
355                 if (!modes[i].name)
356                         return FALSE;
357                 if (scale == NV50_SCALE_OFF && nv_output->panelType == LVDS)
358                         // LVDS requires scaling
359                         return FALSE;
360
361                 oldScale = nv_output->scale;
362                 nv_output->scale = scale;
363                 if (output->crtc) {
364                         xf86CrtcPtr crtc = output->crtc;
365
366                         if (!xf86CrtcSetMode(crtc, &crtc->desiredMode, crtc->desiredRotation,
367                                         crtc->desiredX, crtc->desiredY)) {
368                                 xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
369                                                 "Failed to set scaling to %s for output %s\n",
370                                                 modes[i].name, output->name);
371
372                                 // Restore old scale and try again.
373                                 nv_output->scale = oldScale;
374                                 if (!xf86CrtcSetMode(crtc, &crtc->desiredMode,
375                                                         crtc->desiredRotation, crtc->desiredX,
376                                                         crtc->desiredY)) {
377                                         xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
378                                                 "Failed to restore old scaling for output %s\n",
379                                                 output->name);
380                                 }
381
382                                 return FALSE;
383                         }
384                 }
385                 return TRUE;
386         }
387
388         return FALSE;
389 }
390 #endif // RANDR_12_INTERFACE
391
392 static const xf86OutputFuncsRec NV50SorTMDSOutputFuncs = {
393         .dpms = NV50SorDPMSSet,
394         .save = NULL,
395         .restore = NULL,
396         .mode_valid = NV50TMDSModeValid,
397         .mode_fixup = NV50SorTMDSModeFixup,
398         .prepare = NV50OutputPrepare,
399         .commit = NV50OutputCommit,
400         .mode_set = NV50SorModeSet,
401         .detect = NV50SorDetect,
402         .get_modes = NV50OutputGetDDCModes,
403 #ifdef RANDR_12_INTERFACE
404         .create_resources = NV50SorCreateResources,
405         .set_property = NV50SorSetProperty,
406 #endif
407         .destroy = NV50SorDestroy,
408 };
409
410 static const xf86OutputFuncsRec NV50SorLVDSOutputFuncs = {
411         .dpms = NV50SorDPMSSet,
412         .save = NULL,
413         .restore = NULL,
414         .mode_valid = NV50LVDSModeValid,
415         .mode_fixup = NV50SorModeFixup,
416         .prepare = NV50OutputPrepare,
417         .commit = NV50OutputCommit,
418         .mode_set = NV50SorModeSet,
419         .detect = NV50SorLVDSDetect,
420         .get_modes = NV50SorGetLVDSModes,
421 #ifdef RANDR_12_INTERFACE
422         .create_resources = NV50SorCreateResources,
423         .set_property = NV50SorSetProperty,
424 #endif
425         .destroy = NV50SorDestroy,
426 };
427
428 static DisplayModePtr
429 ReadLVDSNativeMode(ScrnInfoPtr pScrn, const int off)
430 {
431         DisplayModePtr mode = xnfcalloc(1, sizeof(DisplayModeRec));
432         const CARD32 size = NV50DisplayRead(pScrn, 0xb4c + off);
433         const int width = size & 0x3fff;
434         const int height = (size >> 16) & 0x3fff;
435
436         mode->HDisplay = mode->CrtcHDisplay = width;
437         mode->VDisplay = mode->CrtcVDisplay = height;
438         mode->Clock = NV50DisplayRead(pScrn, 0xad4 + off) & 0x3fffff;
439         mode->CrtcHBlankStart = NV50DisplayRead(pScrn, 0xafc + off);
440         mode->CrtcHSyncEnd = NV50DisplayRead(pScrn, 0xb04 + off);
441         mode->CrtcHBlankEnd = NV50DisplayRead(pScrn, 0xae8 + off);
442         mode->CrtcHTotal = NV50DisplayRead(pScrn, 0xaf4 + off);
443
444         mode->next = mode->prev = NULL;
445         mode->status = MODE_OK;
446         mode->type = M_T_DRIVER | M_T_PREFERRED;
447
448         xf86SetModeDefaultName(mode);
449
450         return mode;
451 }
452
453 static DisplayModePtr
454 GetLVDSNativeMode(ScrnInfoPtr pScrn)
455 {
456         CARD32 val = NV50DisplayRead(pScrn, 0x50);
457
458         if ((val & 0x3) == 0x2) {
459                 return ReadLVDSNativeMode(pScrn, 0);
460         } else if ((val & 0x300) == 0x200) {
461                 return ReadLVDSNativeMode(pScrn, 0x540);
462         }
463
464         return NULL;
465 }
466
467 xf86OutputPtr
468 NV50CreateSor(ScrnInfoPtr pScrn, ORNum or, PanelType panelType)
469 {
470         NV50OutputPrivPtr nv_output = xnfcalloc(sizeof(*nv_output), 1);
471         xf86OutputPtr output;
472         char orName[5];
473         const xf86OutputFuncsRec *funcs;
474
475         if(!nv_output)
476                 return FALSE;
477
478         if(panelType == LVDS) {
479                 strcpy(orName, "LVDS");
480                 funcs = &NV50SorLVDSOutputFuncs;
481
482                 nv_output->nativeMode = GetLVDSNativeMode(pScrn);
483
484                 if(!nv_output->nativeMode) {
485                         xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
486                                 "Failed to find LVDS native mode\n");
487                         xfree(nv_output);
488                         return FALSE;
489                 }
490
491                 xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s native size %dx%d\n",
492                         orName, nv_output->nativeMode->HDisplay,
493                         nv_output->nativeMode->VDisplay);
494         } else {
495                 snprintf(orName, 5, "DVI%d", or);
496                 funcs = &NV50SorTMDSOutputFuncs;
497         }
498
499         output = xf86OutputCreate(pScrn, funcs, orName);
500
501         nv_output->type = SOR;
502         nv_output->or = or;
503         nv_output->panelType = panelType;
504         nv_output->cached_status = XF86OutputStatusUnknown;
505         if (panelType == TMDS)
506                 nv_output->set_pclk = NV50SorSetPClk;
507         output->driver_private = nv_output;
508         output->interlaceAllowed = TRUE;
509         output->doubleScanAllowed = TRUE;
510
511         if (panelType != LVDS) {
512                 NV50OutputWrite(output, 0xc00c, 0x03010700);
513                 NV50OutputWrite(output, 0xc010, 0x0000152f);
514                 NV50OutputWrite(output, 0xc014, 0x00000000);
515                 NV50OutputWrite(output, 0xc018, 0x00245af8);
516         }
517
518         return output;
519 }
520
521 #endif /* ENABLE_RANDR12 */