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) {
                        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;
 
                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;
 
 {
        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;
                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) {
 
 
        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;
 
        .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);
 
 
        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 *);
        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 {
 
 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;
+       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;
+       }
+
+       /* 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;
+       }
+
+       /* 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 {
-                       native_mode = nv_connector->native_mode;
+                       oX -= (oX >> 4) + 32;
+                       if (bY) oY -= (bY * 2);
+                       else    oY  = ((oX * aspect) + (aspect / 2)) >> 19;
                }
-               break;
        }
 
+       /* handle CENTER/ASPECT scaling, taking into account the areas
+        * removed already for overscan compensation
+        */
        switch (scaling_mode) {
+       case DRM_MODE_SCALE_CENTER:
+               oX = min((u32)mode->hdisplay, oX);
+               oY = min((u32)mode->vdisplay, oY);
+               /* fall-through */
        case DRM_MODE_SCALE_ASPECT:
-               horiz = (native_mode->hdisplay << 19) / mode->hdisplay;
-               vert = (native_mode->vdisplay << 19) / mode->vdisplay;
-
-               if (vert > horiz) {
-                       outX = (mode->hdisplay * horiz) >> 19;
-                       outY = (mode->vdisplay * horiz) >> 19;
+               if (oY < oX) {
+                       u32 aspect = (mode->hdisplay << 19) / mode->vdisplay;
+                       oX = ((oY * aspect) + (aspect / 2)) >> 19;
                } else {
-                       outX = (mode->hdisplay * vert) >> 19;
-                       outY = (mode->vdisplay * vert) >> 19;
+                       u32 aspect = (mode->vdisplay << 19) / mode->hdisplay;
+                       oY = ((oX * aspect) + (aspect / 2)) >> 19;
                }
                break;
-       case DRM_MODE_SCALE_FULLSCREEN:
-               outX = native_mode->hdisplay;
-               outY = native_mode->vdisplay;
-               break;
-       case DRM_MODE_SCALE_CENTER:
-       case DRM_MODE_SCALE_NONE:
        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);