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