g80_sor.c revision 9c32e6c9
1/*
2 * Copyright (c) 2007-2008 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#include <X11/extensions/dpmsconst.h>
29#include <X11/Xatom.h>
30
31#include "g80_type.h"
32#include "g80_display.h"
33#include "g80_output.h"
34
35static void
36G80SorSetPClk(xf86OutputPtr output, int pclk)
37{
38    G80Ptr pNv = G80PTR(output->scrn);
39    G80OutputPrivPtr pPriv = output->driver_private;
40    const int orOff = 0x800 * pPriv->or;
41    const int limit = 165000;
42
43    pNv->reg[(0x00614300+orOff)/4] = 0x70000 | (pclk > limit ? 0x101 : 0);
44}
45
46static void
47G80SorDPMSSet(xf86OutputPtr output, int mode)
48{
49    G80Ptr pNv = G80PTR(output->scrn);
50    G80OutputPrivPtr pPriv = output->driver_private;
51    const int off = 0x800 * pPriv->or;
52    CARD32 tmp;
53
54    while(pNv->reg[(0x0061C004+off)/4] & 0x80000000);
55
56    tmp = pNv->reg[(0x0061C004+off)/4];
57    tmp |= 0x80000000;
58
59    if(mode == DPMSModeOn)
60        tmp |= 1;
61    else
62        tmp &= ~1;
63
64    pNv->reg[(0x0061C004+off)/4] = tmp;
65    while((pNv->reg[(0x61C030+off)/4] & 0x10000000));
66}
67
68static int
69G80TMDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
70{
71    G80Ptr pNv = G80PTR(output->scrn);
72
73    // Disable dual-link modes unless enabled in the config file.
74    if (mode->Clock > 165000 && !pNv->AllowDualLink)
75        return MODE_CLOCK_HIGH;
76
77    return G80OutputModeValid(output, mode);
78}
79
80static int
81G80LVDSModeValid(xf86OutputPtr output, DisplayModePtr mode)
82{
83    G80OutputPrivPtr pPriv = output->driver_private;
84    DisplayModePtr native = pPriv->nativeMode;
85
86    // Ignore modes larger than the native res.
87    if (mode->HDisplay > native->HDisplay || mode->VDisplay > native->VDisplay)
88        return MODE_PANEL;
89
90    return G80OutputModeValid(output, mode);
91}
92
93static void
94G80SorModeSet(xf86OutputPtr output, DisplayModePtr mode,
95              DisplayModePtr adjusted_mode)
96{
97    ScrnInfoPtr pScrn = output->scrn;
98    G80OutputPrivPtr pPriv = output->driver_private;
99    const int sorOff = 0x40 * pPriv->or;
100    CARD32 type;
101
102    if(!adjusted_mode) {
103        /* Disconnect the SOR */
104        C(0x00000600 + sorOff, 0);
105        return;
106    }
107
108    if(pPriv->panelType == LVDS)
109        type = 0;
110    else if(adjusted_mode->Clock > 165000)
111        type = 0x500;
112    else
113        type = 0x100;
114
115    // This wouldn't be necessary, but the server is stupid and calls
116    // G80SorDPMSSet after the output is disconnected, even though the hardware
117    // turns it off automatically.
118    G80SorDPMSSet(output, DPMSModeOn);
119
120    C(0x00000600 + sorOff,
121        (G80CrtcGetHead(output->crtc) == HEAD0 ? 1 : 2) |
122        type |
123        ((adjusted_mode->Flags & V_NHSYNC) ? 0x1000 : 0) |
124        ((adjusted_mode->Flags & V_NVSYNC) ? 0x2000 : 0));
125
126    G80CrtcSetScale(output->crtc, adjusted_mode, pPriv->scale);
127}
128
129static xf86OutputStatus
130G80SorDetect(xf86OutputPtr output)
131{
132    G80OutputPrivPtr pPriv = output->driver_private;
133
134    /* Assume physical status isn't going to change before the BlockHandler */
135    if(pPriv->cached_status != XF86OutputStatusUnknown)
136        return pPriv->cached_status;
137
138    G80OutputPartnersDetect(pPriv->partner, output, pPriv->i2c);
139    return pPriv->cached_status;
140}
141
142static xf86OutputStatus
143G80SorLVDSDetect(xf86OutputPtr output)
144{
145    G80OutputPrivPtr pPriv = output->driver_private;
146
147    if(pPriv->i2c) {
148        /* If LVDS has an I2C port, use the normal probe routine to get the
149         * EDID, if possible. */
150        G80SorDetect(output);
151    }
152
153    /* Ignore G80SorDetect and assume LVDS is always connected */
154    return XF86OutputStatusConnected;
155}
156
157static void
158G80SorDestroy(xf86OutputPtr output)
159{
160    G80OutputPrivPtr pPriv = output->driver_private;
161
162    G80OutputDestroy(output);
163
164    xf86DeleteMode(&pPriv->nativeMode, pPriv->nativeMode);
165
166    xfree(output->driver_private);
167    output->driver_private = NULL;
168}
169
170static void G80SorSetModeBackend(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
192static Bool
193G80SorModeFixup(xf86OutputPtr output, DisplayModePtr mode,
194                DisplayModePtr adjusted_mode)
195{
196    G80OutputPrivPtr pPriv = output->driver_private;
197    DisplayModePtr native = pPriv->nativeMode;
198
199    if(native && pPriv->scale != G80_SCALE_OFF) {
200        G80SorSetModeBackend(adjusted_mode, native);
201        // This mode is already "fixed"
202        G80CrtcSkipModeFixup(output->crtc);
203    }
204
205    return TRUE;
206}
207
208static Bool
209G80SorTMDSModeFixup(xf86OutputPtr output, DisplayModePtr mode,
210                    DisplayModePtr adjusted_mode)
211{
212    int scrnIndex = output->scrn->scrnIndex;
213    G80OutputPrivPtr pPriv = output->driver_private;
214    DisplayModePtr modes = output->probed_modes;
215
216    xf86DeleteMode(&pPriv->nativeMode, pPriv->nativeMode);
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        pPriv->nativeMode = xf86DuplicateMode(mode);
242        G80CrtcDoModeFixup(pPriv->nativeMode, mode);
243    }
244
245    return G80SorModeFixup(output, mode, adjusted_mode);
246}
247
248static DisplayModePtr
249G80SorGetLVDSModes(xf86OutputPtr output)
250{
251    G80OutputPrivPtr pPriv = output->driver_private;
252
253    /* If an EDID was read during detection, use the modes from that. */
254    DisplayModePtr modes = G80OutputGetDDCModes(output);
255    if(modes)
256        return modes;
257
258    /* Otherwise, feed in the mode we read during initialization. */
259    return xf86DuplicateMode(pPriv->nativeMode);
260}
261
262#ifdef RANDR_12_INTERFACE
263#define MAKE_ATOM(a) MakeAtom((a), sizeof(a) - 1, TRUE);
264
265struct property {
266    Atom atom;
267    INT32 range[2];
268};
269
270static struct {
271    struct property dither;
272    struct property scale;
273} properties;
274
275static void
276G80SorCreateResources(xf86OutputPtr output)
277{
278    ScrnInfoPtr pScrn = output->scrn;
279    G80Ptr pNv = G80PTR(pScrn);
280    int data, err;
281    const char *s;
282
283    /******** dithering ********/
284    properties.dither.atom = MAKE_ATOM("dither");
285    properties.dither.range[0] = 0;
286    properties.dither.range[1] = 1;
287    err = RRConfigureOutputProperty(output->randr_output,
288                                    properties.dither.atom, FALSE, TRUE, FALSE,
289                                    2, properties.dither.range);
290    if(err)
291        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
292                   "Failed to configure dithering property for %s: error %d\n",
293                   output->name, err);
294
295    // Set the default value
296    data = pNv->Dither;
297    err = RRChangeOutputProperty(output->randr_output, properties.dither.atom,
298                                 XA_INTEGER, 32, PropModeReplace, 1, &data,
299                                 FALSE, FALSE);
300    if(err)
301        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
302                   "Failed to set dithering property for %s: error %d\n",
303                   output->name, err);
304
305    /******** scaling ********/
306    properties.scale.atom = MAKE_ATOM("scale");
307    err = RRConfigureOutputProperty(output->randr_output,
308                                    properties.scale.atom, FALSE, FALSE,
309                                    FALSE, 0, NULL);
310    if(err)
311        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
312                   "Failed to configure scaling property for %s: error %d\n",
313                   output->name, err);
314
315    // Set the default value
316    s = "aspect";
317    err = RRChangeOutputProperty(output->randr_output, properties.scale.atom,
318                                 XA_STRING, 8, PropModeReplace, strlen(s),
319                                 (pointer)s, FALSE, FALSE);
320    if(err)
321        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
322                   "Failed to set scaling property for %s: error %d\n",
323                   output->name, err);
324}
325
326static Bool
327G80SorSetProperty(xf86OutputPtr output, Atom prop, RRPropertyValuePtr val)
328{
329    G80OutputPrivPtr pPriv = output->driver_private;
330
331    if(prop == properties.dither.atom) {
332        INT32 i;
333
334        if(val->type != XA_INTEGER || val->format != 32 || val->size != 1)
335            return FALSE;
336
337        i = *(INT32*)val->data;
338        if(i < properties.dither.range[0] || i > properties.dither.range[1])
339            return FALSE;
340
341        G80CrtcSetDither(output->crtc, i, TRUE);
342    } else if(prop == properties.scale.atom) {
343        const char *s;
344        enum G80ScaleMode oldScale, scale;
345        int i;
346        const struct {
347            const char *name;
348            enum G80ScaleMode scale;
349        } modes[] = {
350            { "off",    G80_SCALE_OFF },
351            { "aspect", G80_SCALE_ASPECT },
352            { "fill",   G80_SCALE_FILL },
353            { "center", G80_SCALE_CENTER },
354            { NULL,     0 },
355        };
356
357        if(val->type != XA_STRING || val->format != 8)
358            return FALSE;
359        s = (char*)val->data;
360
361        for(i = 0; modes[i].name; i++) {
362            const char *name = modes[i].name;
363            const int len = strlen(name);
364
365            if(val->size == len && !strncmp(name, s, len)) {
366                scale = modes[i].scale;
367                break;
368            }
369        }
370        if(!modes[i].name)
371            return FALSE;
372        if(scale == G80_SCALE_OFF && pPriv->panelType == LVDS)
373            // LVDS requires scaling
374            return FALSE;
375
376        oldScale = pPriv->scale;
377        pPriv->scale = scale;
378        if(output->crtc) {
379            xf86CrtcPtr crtc = output->crtc;
380
381            if(!xf86CrtcSetMode(crtc, &crtc->desiredMode, crtc->desiredRotation,
382                                crtc->desiredX, crtc->desiredY)) {
383                xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
384                           "Failed to set scaling to %s for output %s\n",
385                           modes[i].name, output->name);
386
387                // Restore old scale and try again.
388                pPriv->scale = oldScale;
389                if(!xf86CrtcSetMode(crtc, &crtc->desiredMode,
390                                    crtc->desiredRotation, crtc->desiredX,
391                                    crtc->desiredY)) {
392                    xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
393                               "Failed to restore old scaling for output %s\n",
394                               output->name);
395                }
396
397                return FALSE;
398            }
399        }
400    }
401
402    return TRUE;
403}
404#endif // RANDR_12_INTERFACE
405
406static const xf86OutputFuncsRec G80SorTMDSOutputFuncs = {
407    .dpms = G80SorDPMSSet,
408    .save = NULL,
409    .restore = NULL,
410    .mode_valid = G80TMDSModeValid,
411    .mode_fixup = G80SorTMDSModeFixup,
412    .prepare = G80OutputPrepare,
413    .commit = G80OutputCommit,
414    .mode_set = G80SorModeSet,
415    .detect = G80SorDetect,
416    .get_modes = G80OutputGetDDCModes,
417#ifdef RANDR_12_INTERFACE
418    .create_resources = G80SorCreateResources,
419    .set_property = G80SorSetProperty,
420#endif
421    .destroy = G80SorDestroy,
422};
423
424static const xf86OutputFuncsRec G80SorLVDSOutputFuncs = {
425    .dpms = G80SorDPMSSet,
426    .save = NULL,
427    .restore = NULL,
428    .mode_valid = G80LVDSModeValid,
429    .mode_fixup = G80SorModeFixup,
430    .prepare = G80OutputPrepare,
431    .commit = G80OutputCommit,
432    .mode_set = G80SorModeSet,
433    .detect = G80SorLVDSDetect,
434    .get_modes = G80SorGetLVDSModes,
435#ifdef RANDR_12_INTERFACE
436    .create_resources = G80SorCreateResources,
437    .set_property = G80SorSetProperty,
438#endif
439    .destroy = G80SorDestroy,
440};
441
442static DisplayModePtr
443ReadLVDSNativeMode(G80Ptr pNv, const int off)
444{
445    DisplayModePtr mode = xnfcalloc(1, sizeof(DisplayModeRec));
446    const CARD32 size = pNv->reg[(0x00610B4C+off)/4];
447    const int width = size & 0x3fff;
448    const int height = (size >> 16) & 0x3fff;
449
450    mode->HDisplay = mode->CrtcHDisplay = width;
451    mode->VDisplay = mode->CrtcVDisplay = height;
452    mode->Clock           = pNv->reg[(0x610AD4+off)/4] & 0x3fffff;
453    mode->CrtcHBlankStart = pNv->reg[(0x610AFC+off)/4];
454    mode->CrtcHSyncEnd    = pNv->reg[(0x610B04+off)/4];
455    mode->CrtcHBlankEnd   = pNv->reg[(0x610AE8+off)/4];
456    mode->CrtcHTotal      = pNv->reg[(0x610AF4+off)/4];
457
458    mode->next = mode->prev = NULL;
459    mode->status = MODE_OK;
460    mode->type = M_T_DRIVER | M_T_PREFERRED;
461
462    xf86SetModeDefaultName(mode);
463
464    return mode;
465}
466
467static DisplayModePtr
468GetLVDSNativeMode(G80Ptr pNv)
469{
470    CARD32 val = pNv->reg[0x00610050/4];
471
472    if((val & 3) == 2)
473        return ReadLVDSNativeMode(pNv, 0);
474    else if((val & 0x300) == 0x200)
475        return ReadLVDSNativeMode(pNv, 0x540);
476
477    return NULL;
478}
479
480xf86OutputPtr
481G80CreateSor(ScrnInfoPtr pScrn, ORNum or, PanelType panelType)
482{
483    G80Ptr pNv = G80PTR(pScrn);
484    G80OutputPrivPtr pPriv = xnfcalloc(sizeof(*pPriv), 1);
485    const int off = 0x800 * or;
486    xf86OutputPtr output;
487    char orName[5];
488    const xf86OutputFuncsRec *funcs;
489
490    if(!pPriv)
491        return NULL;
492
493    if(panelType == LVDS) {
494        strcpy(orName, "LVDS");
495        funcs = &G80SorLVDSOutputFuncs;
496
497        pPriv->nativeMode = GetLVDSNativeMode(pNv);
498
499        if(!pPriv->nativeMode) {
500            xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
501                       "Failed to find LVDS native mode\n");
502            xfree(pPriv);
503            return NULL;
504        }
505
506        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "%s native size %dx%d\n",
507                   orName, pPriv->nativeMode->HDisplay,
508                   pPriv->nativeMode->VDisplay);
509    } else {
510        snprintf(orName, 5, "DVI%d", or);
511        pNv->reg[(0x61C00C+off)/4] = 0x03010700;
512        pNv->reg[(0x61C010+off)/4] = 0x0000152f;
513        pNv->reg[(0x61C014+off)/4] = 0x00000000;
514        pNv->reg[(0x61C018+off)/4] = 0x00245af8;
515        funcs = &G80SorTMDSOutputFuncs;
516    }
517
518    output = xf86OutputCreate(pScrn, funcs, orName);
519
520    pPriv->type = SOR;
521    pPriv->or = or;
522    pPriv->panelType = panelType;
523    pPriv->cached_status = XF86OutputStatusUnknown;
524    if(panelType == TMDS)
525        pPriv->set_pclk = G80SorSetPClk;
526    output->driver_private = pPriv;
527    output->interlaceAllowed = TRUE;
528    output->doubleScanAllowed = TRUE;
529
530    return output;
531}
532