summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_connector.c78
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_connector.h5
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_display.c35
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_drv.h10
-rw-r--r--drivers/gpu/drm/nouveau/nv50_crtc.c99
5 files changed, 176 insertions, 51 deletions
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;
+ 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);