mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 10:49:33 +00:00
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>
This commit is contained in:
parent
27d5030a23
commit
b29caa5885
5 changed files with 179 additions and 54 deletions
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue