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