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