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