diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index cea6696b1906..5e830a585538 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -420,15 +420,21 @@ static int nouveau_connector_set_property(struct drm_connector *connector, struct drm_property *property, uint64_t value) { + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; struct nouveau_connector *nv_connector = nouveau_connector(connector); struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; struct drm_encoder *encoder = to_drm_encoder(nv_encoder); struct drm_device *dev = connector->dev; + struct nouveau_crtc *nv_crtc; int ret; + nv_crtc = NULL; + if (connector->encoder && connector->encoder->crtc) + nv_crtc = nouveau_crtc(connector->encoder->crtc); + /* Scaling mode */ if (property == dev->mode_config.scaling_mode_property) { - struct nouveau_crtc *nv_crtc = NULL; bool modeset = false; switch (value) { @@ -454,8 +460,6 @@ nouveau_connector_set_property(struct drm_connector *connector, modeset = true; nv_connector->scaling_mode = value; - if (connector->encoder && connector->encoder->crtc) - nv_crtc = nouveau_crtc(connector->encoder->crtc); if (!nv_crtc) return 0; @@ -475,18 +479,56 @@ nouveau_connector_set_property(struct drm_connector *connector, return 0; } + /* Underscan */ + if (property == disp->underscan_property) { + if (nv_connector->underscan != value) { + nv_connector->underscan = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, + nv_connector->scaling_mode, + true); + } + + return 0; + } + + if (property == disp->underscan_hborder_property) { + if (nv_connector->underscan_hborder != value) { + nv_connector->underscan_hborder = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, + nv_connector->scaling_mode, + true); + } + + return 0; + } + + if (property == disp->underscan_vborder_property) { + if (nv_connector->underscan_vborder != value) { + nv_connector->underscan_vborder = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, + nv_connector->scaling_mode, + true); + } + + return 0; + } + /* Dithering */ if (property == dev->mode_config.dithering_mode_property) { - struct nouveau_crtc *nv_crtc = NULL; - if (value == DRM_MODE_DITHERING_ON) nv_connector->use_dithering = true; else nv_connector->use_dithering = false; - if (connector->encoder && connector->encoder->crtc) - nv_crtc = nouveau_crtc(connector->encoder->crtc); - if (!nv_crtc || !nv_crtc->set_dither) return 0; @@ -773,6 +815,7 @@ nouveau_connector_create(struct drm_device *dev, int index) { const struct drm_connector_funcs *funcs = &nouveau_connector_funcs; struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; struct nouveau_connector *nv_connector = NULL; struct dcb_connector_table_entry *dcb = NULL; @@ -857,6 +900,25 @@ nouveau_connector_create(struct drm_device *dev, int index) drm_connector_attach_property(connector, dev->mode_config.dvi_i_select_subconnector_property, 0); } + /* Add overscan compensation options to digital outputs */ + if ((dev_priv->card_type == NV_50 || + dev_priv->card_type == NV_C0) && + (dcb->type == DCB_CONNECTOR_DVI_D || + dcb->type == DCB_CONNECTOR_DVI_I || + dcb->type == DCB_CONNECTOR_HDMI_0 || + dcb->type == DCB_CONNECTOR_HDMI_1 || + dcb->type == DCB_CONNECTOR_DP)) { + drm_connector_attach_property(connector, + disp->underscan_property, + UNDERSCAN_OFF); + drm_connector_attach_property(connector, + disp->underscan_hborder_property, + 0); + drm_connector_attach_property(connector, + disp->underscan_vborder_property, + 0); + } + switch (dcb->type) { case DCB_CONNECTOR_VGA: if (dev_priv->card_type >= NV_50) { diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h index 711b1e9203af..5bcaabbd375c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.h +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -35,8 +35,11 @@ struct nouveau_connector { struct dcb_connector_table_entry *dcb; - int scaling_mode; bool use_dithering; + int scaling_mode; + enum nouveau_underscan_type underscan; + u32 underscan_hborder; + u32 underscan_vborder; struct nouveau_encoder *detected_encoder; struct edid *edid; diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 017d4ea12b1e..9bc9d6463b4a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -152,17 +152,50 @@ static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { .output_poll_changed = nouveau_fbcon_output_poll_changed, }; + +struct drm_prop_enum_list { + int type; + char *name; +}; + +static struct drm_prop_enum_list nouveau_underscan_enum_list[] = { + { UNDERSCAN_OFF, "off" }, + { UNDERSCAN_ON, "on" }, + { UNDERSCAN_AUTO, "auto" }, +}; + int nouveau_display_create(struct drm_device *dev) { struct drm_nouveau_private *dev_priv = dev->dev_private; struct nouveau_display_engine *disp = &dev_priv->engine.display; - int ret; + int ret, cnt, i; drm_mode_config_init(dev); drm_mode_create_scaling_mode_property(dev); drm_mode_create_dithering_property(dev); + cnt = ARRAY_SIZE(nouveau_underscan_enum_list); + disp->underscan_property = drm_property_create(dev, DRM_MODE_PROP_ENUM, + "underscan", cnt); + for (i = 0; i < cnt; i++) { + drm_property_add_enum(disp->underscan_property, i, + nouveau_underscan_enum_list[i].type, + nouveau_underscan_enum_list[i].name); + } + + disp->underscan_hborder_property = + drm_property_create(dev, DRM_MODE_PROP_RANGE, + "underscan hborder", 2); + disp->underscan_hborder_property->values[0] = 0; + disp->underscan_hborder_property->values[1] = 128; + + disp->underscan_vborder_property = + drm_property_create(dev, DRM_MODE_PROP_RANGE, + "underscan vborder", 2); + disp->underscan_vborder_property->values[0] = 0; + disp->underscan_vborder_property->values[1] = 128; + dev->mode_config.funcs = (void *)&nouveau_mode_config_funcs; dev->mode_config.fb_base = pci_resource_start(dev->pdev, 1); diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index a55bae1e1e50..b4a5eba75998 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -391,6 +391,12 @@ struct nouveau_fifo_engine { void (*tlb_flush)(struct drm_device *dev); }; +enum nouveau_underscan_type { + UNDERSCAN_OFF, + UNDERSCAN_ON, + UNDERSCAN_AUTO, +}; + struct nouveau_display_engine { void *priv; int (*early_init)(struct drm_device *); @@ -398,6 +404,10 @@ struct nouveau_display_engine { int (*create)(struct drm_device *); int (*init)(struct drm_device *); void (*destroy)(struct drm_device *); + + struct drm_property *underscan_property; + struct drm_property *underscan_hborder_property; + struct drm_property *underscan_vborder_property; }; struct nouveau_gpio_engine { diff --git a/drivers/gpu/drm/nouveau/nv50_crtc.c b/drivers/gpu/drm/nouveau/nv50_crtc.c index 8eb108b28eb1..e426a9b9865a 100644 --- a/drivers/gpu/drm/nouveau/nv50_crtc.c +++ b/drivers/gpu/drm/nouveau/nv50_crtc.c @@ -215,74 +215,91 @@ nouveau_crtc_connector_get(struct nouveau_crtc *nv_crtc) static int nv50_crtc_set_scale(struct nouveau_crtc *nv_crtc, int scaling_mode, bool update) { - struct nouveau_connector *nv_connector = - nouveau_crtc_connector_get(nv_crtc); + struct nouveau_connector *nv_connector; struct drm_crtc *crtc = &nv_crtc->base; struct drm_device *dev = crtc->dev; struct nouveau_channel *evo = nv50_display(dev)->master; - struct drm_display_mode *native_mode = NULL; struct drm_display_mode *mode = &crtc->mode; - uint32_t outX, outY, horiz, vert; + u32 ctrl = 0, oX, oY; int ret; NV_DEBUG_KMS(dev, "\n"); - switch (scaling_mode) { - case DRM_MODE_SCALE_NONE: - break; - default: - if (!nv_connector || !nv_connector->native_mode) { - NV_ERROR(dev, "No native mode, forcing panel scaling\n"); - scaling_mode = DRM_MODE_SCALE_NONE; - } else { - native_mode = nv_connector->native_mode; - } - break; + nv_connector = nouveau_crtc_connector_get(nv_crtc); + if (!nv_connector || !nv_connector->native_mode) { + NV_ERROR(dev, "no native mode, forcing panel scaling\n"); + scaling_mode = DRM_MODE_SCALE_NONE; } - switch (scaling_mode) { - case DRM_MODE_SCALE_ASPECT: - horiz = (native_mode->hdisplay << 19) / mode->hdisplay; - vert = (native_mode->vdisplay << 19) / mode->vdisplay; + /* start off at the resolution we programmed the crtc for, this + * effectively handles NONE/FULL scaling + */ + if (scaling_mode != DRM_MODE_SCALE_NONE) { + oX = nv_connector->native_mode->hdisplay; + oY = nv_connector->native_mode->vdisplay; + } else { + oX = mode->hdisplay; + oY = mode->vdisplay; + } - if (vert > horiz) { - outX = (mode->hdisplay * horiz) >> 19; - outY = (mode->vdisplay * horiz) >> 19; + /* add overscan compensation if necessary, will keep the aspect + * ratio the same as the backend mode unless overridden by the + * user setting both hborder and vborder properties. + */ + if (nv_connector && ( nv_connector->underscan == UNDERSCAN_ON || + (nv_connector->underscan == UNDERSCAN_AUTO && + nv_connector->edid && + drm_detect_hdmi_monitor(nv_connector->edid)))) { + u32 bX = nv_connector->underscan_hborder; + u32 bY = nv_connector->underscan_vborder; + u32 aspect = (oY << 19) / oX; + + if (bX) { + oX -= (bX * 2); + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; } else { - outX = (mode->hdisplay * vert) >> 19; - outY = (mode->vdisplay * vert) >> 19; + oX -= (oX >> 4) + 32; + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; } - break; - case DRM_MODE_SCALE_FULLSCREEN: - outX = native_mode->hdisplay; - outY = native_mode->vdisplay; - break; + } + + /* handle CENTER/ASPECT scaling, taking into account the areas + * removed already for overscan compensation + */ + switch (scaling_mode) { case DRM_MODE_SCALE_CENTER: - case DRM_MODE_SCALE_NONE: + oX = min((u32)mode->hdisplay, oX); + oY = min((u32)mode->vdisplay, oY); + /* fall-through */ + case DRM_MODE_SCALE_ASPECT: + if (oY < oX) { + u32 aspect = (mode->hdisplay << 19) / mode->vdisplay; + oX = ((oY * aspect) + (aspect / 2)) >> 19; + } else { + u32 aspect = (mode->vdisplay << 19) / mode->hdisplay; + oY = ((oX * aspect) + (aspect / 2)) >> 19; + } + break; default: - outX = mode->hdisplay; - outY = mode->vdisplay; break; } + if (mode->hdisplay != oX || mode->vdisplay != oY || + mode->flags & DRM_MODE_FLAG_INTERLACE || + mode->flags & DRM_MODE_FLAG_DBLSCAN) + ctrl |= NV50_EVO_CRTC_SCALE_CTRL_ACTIVE; + ret = RING_SPACE(evo, 5); if (ret) return ret; - /* Got a better name for SCALER_ACTIVE? */ - /* One day i've got to really figure out why this is needed. */ BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_CTRL), 1); - if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) || - (mode->flags & DRM_MODE_FLAG_INTERLACE) || - mode->hdisplay != outX || mode->vdisplay != outY) { - OUT_RING(evo, NV50_EVO_CRTC_SCALE_CTRL_ACTIVE); - } else { - OUT_RING(evo, NV50_EVO_CRTC_SCALE_CTRL_INACTIVE); - } - + OUT_RING (evo, ctrl); BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_RES1), 2); - OUT_RING(evo, outY << 16 | outX); - OUT_RING(evo, outY << 16 | outX); + OUT_RING (evo, oY << 16 | oX); + OUT_RING (evo, oY << 16 | oX); if (update) { nv50_display_flip_stop(crtc);