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