Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/nouveau: add overscan compensation connector properties

Exposes the same connector properties as the Radeon implementation, however
their behaviour isn't exactly the same. The primary difference being that
unless both hborder/vborder have been defined by the user, the driver will
keep the aspect ratio of the overscanned area the same as the mode the
display is programmed for.

Enabled for digital outputs on GeForce 8 and up, excluding GF119.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>

+179 -54
+70 -8
drivers/gpu/drm/nouveau/nouveau_connector.c
··· 420 420 nouveau_connector_set_property(struct drm_connector *connector, 421 421 struct drm_property *property, uint64_t value) 422 422 { 423 + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; 424 + struct nouveau_display_engine *disp = &dev_priv->engine.display; 423 425 struct nouveau_connector *nv_connector = nouveau_connector(connector); 424 426 struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; 425 427 struct drm_encoder *encoder = to_drm_encoder(nv_encoder); 426 428 struct drm_device *dev = connector->dev; 429 + struct nouveau_crtc *nv_crtc; 427 430 int ret; 431 + 432 + nv_crtc = NULL; 433 + if (connector->encoder && connector->encoder->crtc) 434 + nv_crtc = nouveau_crtc(connector->encoder->crtc); 428 435 429 436 /* Scaling mode */ 430 437 if (property == dev->mode_config.scaling_mode_property) { 431 - struct nouveau_crtc *nv_crtc = NULL; 432 438 bool modeset = false; 433 439 434 440 switch (value) { ··· 460 454 modeset = true; 461 455 nv_connector->scaling_mode = value; 462 456 463 - if (connector->encoder && connector->encoder->crtc) 464 - nv_crtc = nouveau_crtc(connector->encoder->crtc); 465 457 if (!nv_crtc) 466 458 return 0; 467 459 ··· 479 475 return 0; 480 476 } 481 477 478 + /* Underscan */ 479 + if (property == disp->underscan_property) { 480 + if (nv_connector->underscan != value) { 481 + nv_connector->underscan = value; 482 + if (!nv_crtc || !nv_crtc->set_scale) 483 + return 0; 484 + 485 + return nv_crtc->set_scale(nv_crtc, 486 + nv_connector->scaling_mode, 487 + true); 488 + } 489 + 490 + return 0; 491 + } 492 + 493 + if (property == disp->underscan_hborder_property) { 494 + if (nv_connector->underscan_hborder != value) { 495 + nv_connector->underscan_hborder = value; 496 + if (!nv_crtc || !nv_crtc->set_scale) 497 + return 0; 498 + 499 + return nv_crtc->set_scale(nv_crtc, 500 + nv_connector->scaling_mode, 501 + true); 502 + } 503 + 504 + return 0; 505 + } 506 + 507 + if (property == disp->underscan_vborder_property) { 508 + if (nv_connector->underscan_vborder != value) { 509 + nv_connector->underscan_vborder = value; 510 + if (!nv_crtc || !nv_crtc->set_scale) 511 + return 0; 512 + 513 + return nv_crtc->set_scale(nv_crtc, 514 + nv_connector->scaling_mode, 515 + true); 516 + } 517 + 518 + return 0; 519 + } 520 + 482 521 /* Dithering */ 483 522 if (property == dev->mode_config.dithering_mode_property) { 484 - struct nouveau_crtc *nv_crtc = NULL; 485 - 486 523 if (value == DRM_MODE_DITHERING_ON) 487 524 nv_connector->use_dithering = true; 488 525 else 489 526 nv_connector->use_dithering = false; 490 - 491 - if (connector->encoder && connector->encoder->crtc) 492 - nv_crtc = nouveau_crtc(connector->encoder->crtc); 493 527 494 528 if (!nv_crtc || !nv_crtc->set_dither) 495 529 return 0; ··· 815 773 { 816 774 const struct drm_connector_funcs *funcs = &nouveau_connector_funcs; 817 775 struct drm_nouveau_private *dev_priv = dev->dev_private; 776 + struct nouveau_display_engine *disp = &dev_priv->engine.display; 818 777 struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; 819 778 struct nouveau_connector *nv_connector = NULL; 820 779 struct dcb_connector_table_entry *dcb = NULL; ··· 898 855 drm_mode_create_dvi_i_properties(dev); 899 856 drm_connector_attach_property(connector, dev->mode_config.dvi_i_subconnector_property, 0); 900 857 drm_connector_attach_property(connector, dev->mode_config.dvi_i_select_subconnector_property, 0); 858 + } 859 + 860 + /* Add overscan compensation options to digital outputs */ 861 + if ((dev_priv->card_type == NV_50 || 862 + dev_priv->card_type == NV_C0) && 863 + (dcb->type == DCB_CONNECTOR_DVI_D || 864 + dcb->type == DCB_CONNECTOR_DVI_I || 865 + dcb->type == DCB_CONNECTOR_HDMI_0 || 866 + dcb->type == DCB_CONNECTOR_HDMI_1 || 867 + dcb->type == DCB_CONNECTOR_DP)) { 868 + drm_connector_attach_property(connector, 869 + disp->underscan_property, 870 + UNDERSCAN_OFF); 871 + drm_connector_attach_property(connector, 872 + disp->underscan_hborder_property, 873 + 0); 874 + drm_connector_attach_property(connector, 875 + disp->underscan_vborder_property, 876 + 0); 901 877 } 902 878 903 879 switch (dcb->type) {
+4 -1
drivers/gpu/drm/nouveau/nouveau_connector.h
··· 35 35 36 36 struct dcb_connector_table_entry *dcb; 37 37 38 - int scaling_mode; 39 38 bool use_dithering; 39 + int scaling_mode; 40 + enum nouveau_underscan_type underscan; 41 + u32 underscan_hborder; 42 + u32 underscan_vborder; 40 43 41 44 struct nouveau_encoder *detected_encoder; 42 45 struct edid *edid;
+34 -1
drivers/gpu/drm/nouveau/nouveau_display.c
··· 152 152 .output_poll_changed = nouveau_fbcon_output_poll_changed, 153 153 }; 154 154 155 + 156 + struct drm_prop_enum_list { 157 + int type; 158 + char *name; 159 + }; 160 + 161 + static struct drm_prop_enum_list nouveau_underscan_enum_list[] = { 162 + { UNDERSCAN_OFF, "off" }, 163 + { UNDERSCAN_ON, "on" }, 164 + { UNDERSCAN_AUTO, "auto" }, 165 + }; 166 + 155 167 int 156 168 nouveau_display_create(struct drm_device *dev) 157 169 { 158 170 struct drm_nouveau_private *dev_priv = dev->dev_private; 159 171 struct nouveau_display_engine *disp = &dev_priv->engine.display; 160 - int ret; 172 + int ret, cnt, i; 161 173 162 174 drm_mode_config_init(dev); 163 175 drm_mode_create_scaling_mode_property(dev); 164 176 drm_mode_create_dithering_property(dev); 177 + 178 + cnt = ARRAY_SIZE(nouveau_underscan_enum_list); 179 + disp->underscan_property = drm_property_create(dev, DRM_MODE_PROP_ENUM, 180 + "underscan", cnt); 181 + for (i = 0; i < cnt; i++) { 182 + drm_property_add_enum(disp->underscan_property, i, 183 + nouveau_underscan_enum_list[i].type, 184 + nouveau_underscan_enum_list[i].name); 185 + } 186 + 187 + disp->underscan_hborder_property = 188 + drm_property_create(dev, DRM_MODE_PROP_RANGE, 189 + "underscan hborder", 2); 190 + disp->underscan_hborder_property->values[0] = 0; 191 + disp->underscan_hborder_property->values[1] = 128; 192 + 193 + disp->underscan_vborder_property = 194 + drm_property_create(dev, DRM_MODE_PROP_RANGE, 195 + "underscan vborder", 2); 196 + disp->underscan_vborder_property->values[0] = 0; 197 + disp->underscan_vborder_property->values[1] = 128; 165 198 166 199 dev->mode_config.funcs = (void *)&nouveau_mode_config_funcs; 167 200 dev->mode_config.fb_base = pci_resource_start(dev->pdev, 1);
+10
drivers/gpu/drm/nouveau/nouveau_drv.h
··· 391 391 void (*tlb_flush)(struct drm_device *dev); 392 392 }; 393 393 394 + enum nouveau_underscan_type { 395 + UNDERSCAN_OFF, 396 + UNDERSCAN_ON, 397 + UNDERSCAN_AUTO, 398 + }; 399 + 394 400 struct nouveau_display_engine { 395 401 void *priv; 396 402 int (*early_init)(struct drm_device *); ··· 404 398 int (*create)(struct drm_device *); 405 399 int (*init)(struct drm_device *); 406 400 void (*destroy)(struct drm_device *); 401 + 402 + struct drm_property *underscan_property; 403 + struct drm_property *underscan_hborder_property; 404 + struct drm_property *underscan_vborder_property; 407 405 }; 408 406 409 407 struct nouveau_gpio_engine {
+61 -44
drivers/gpu/drm/nouveau/nv50_crtc.c
··· 215 215 static int 216 216 nv50_crtc_set_scale(struct nouveau_crtc *nv_crtc, int scaling_mode, bool update) 217 217 { 218 - struct nouveau_connector *nv_connector = 219 - nouveau_crtc_connector_get(nv_crtc); 218 + struct nouveau_connector *nv_connector; 220 219 struct drm_crtc *crtc = &nv_crtc->base; 221 220 struct drm_device *dev = crtc->dev; 222 221 struct nouveau_channel *evo = nv50_display(dev)->master; 223 - struct drm_display_mode *native_mode = NULL; 224 222 struct drm_display_mode *mode = &crtc->mode; 225 - uint32_t outX, outY, horiz, vert; 223 + u32 ctrl = 0, oX, oY; 226 224 int ret; 227 225 228 226 NV_DEBUG_KMS(dev, "\n"); 229 227 230 - switch (scaling_mode) { 231 - case DRM_MODE_SCALE_NONE: 232 - break; 233 - default: 234 - if (!nv_connector || !nv_connector->native_mode) { 235 - NV_ERROR(dev, "No native mode, forcing panel scaling\n"); 236 - scaling_mode = DRM_MODE_SCALE_NONE; 237 - } else { 238 - native_mode = nv_connector->native_mode; 239 - } 240 - break; 228 + nv_connector = nouveau_crtc_connector_get(nv_crtc); 229 + if (!nv_connector || !nv_connector->native_mode) { 230 + NV_ERROR(dev, "no native mode, forcing panel scaling\n"); 231 + scaling_mode = DRM_MODE_SCALE_NONE; 241 232 } 242 233 243 - switch (scaling_mode) { 244 - case DRM_MODE_SCALE_ASPECT: 245 - horiz = (native_mode->hdisplay << 19) / mode->hdisplay; 246 - vert = (native_mode->vdisplay << 19) / mode->vdisplay; 234 + /* start off at the resolution we programmed the crtc for, this 235 + * effectively handles NONE/FULL scaling 236 + */ 237 + if (scaling_mode != DRM_MODE_SCALE_NONE) { 238 + oX = nv_connector->native_mode->hdisplay; 239 + oY = nv_connector->native_mode->vdisplay; 240 + } else { 241 + oX = mode->hdisplay; 242 + oY = mode->vdisplay; 243 + } 247 244 248 - if (vert > horiz) { 249 - outX = (mode->hdisplay * horiz) >> 19; 250 - outY = (mode->vdisplay * horiz) >> 19; 245 + /* add overscan compensation if necessary, will keep the aspect 246 + * ratio the same as the backend mode unless overridden by the 247 + * user setting both hborder and vborder properties. 248 + */ 249 + if (nv_connector && ( nv_connector->underscan == UNDERSCAN_ON || 250 + (nv_connector->underscan == UNDERSCAN_AUTO && 251 + nv_connector->edid && 252 + drm_detect_hdmi_monitor(nv_connector->edid)))) { 253 + u32 bX = nv_connector->underscan_hborder; 254 + u32 bY = nv_connector->underscan_vborder; 255 + u32 aspect = (oY << 19) / oX; 256 + 257 + if (bX) { 258 + oX -= (bX * 2); 259 + if (bY) oY -= (bY * 2); 260 + else oY = ((oX * aspect) + (aspect / 2)) >> 19; 251 261 } else { 252 - outX = (mode->hdisplay * vert) >> 19; 253 - outY = (mode->vdisplay * vert) >> 19; 262 + oX -= (oX >> 4) + 32; 263 + if (bY) oY -= (bY * 2); 264 + else oY = ((oX * aspect) + (aspect / 2)) >> 19; 254 265 } 255 - break; 256 - case DRM_MODE_SCALE_FULLSCREEN: 257 - outX = native_mode->hdisplay; 258 - outY = native_mode->vdisplay; 259 - break; 266 + } 267 + 268 + /* handle CENTER/ASPECT scaling, taking into account the areas 269 + * removed already for overscan compensation 270 + */ 271 + switch (scaling_mode) { 260 272 case DRM_MODE_SCALE_CENTER: 261 - case DRM_MODE_SCALE_NONE: 273 + oX = min((u32)mode->hdisplay, oX); 274 + oY = min((u32)mode->vdisplay, oY); 275 + /* fall-through */ 276 + case DRM_MODE_SCALE_ASPECT: 277 + if (oY < oX) { 278 + u32 aspect = (mode->hdisplay << 19) / mode->vdisplay; 279 + oX = ((oY * aspect) + (aspect / 2)) >> 19; 280 + } else { 281 + u32 aspect = (mode->vdisplay << 19) / mode->hdisplay; 282 + oY = ((oX * aspect) + (aspect / 2)) >> 19; 283 + } 284 + break; 262 285 default: 263 - outX = mode->hdisplay; 264 - outY = mode->vdisplay; 265 286 break; 266 287 } 288 + 289 + if (mode->hdisplay != oX || mode->vdisplay != oY || 290 + mode->flags & DRM_MODE_FLAG_INTERLACE || 291 + mode->flags & DRM_MODE_FLAG_DBLSCAN) 292 + ctrl |= NV50_EVO_CRTC_SCALE_CTRL_ACTIVE; 267 293 268 294 ret = RING_SPACE(evo, 5); 269 295 if (ret) 270 296 return ret; 271 297 272 - /* Got a better name for SCALER_ACTIVE? */ 273 - /* One day i've got to really figure out why this is needed. */ 274 298 BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_CTRL), 1); 275 - if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) || 276 - (mode->flags & DRM_MODE_FLAG_INTERLACE) || 277 - mode->hdisplay != outX || mode->vdisplay != outY) { 278 - OUT_RING(evo, NV50_EVO_CRTC_SCALE_CTRL_ACTIVE); 279 - } else { 280 - OUT_RING(evo, NV50_EVO_CRTC_SCALE_CTRL_INACTIVE); 281 - } 282 - 299 + OUT_RING (evo, ctrl); 283 300 BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_RES1), 2); 284 - OUT_RING(evo, outY << 16 | outX); 285 - OUT_RING(evo, outY << 16 | outX); 301 + OUT_RING (evo, oY << 16 | oX); 302 + OUT_RING (evo, oY << 16 | oX); 286 303 287 304 if (update) { 288 305 nv50_display_flip_stop(crtc);