summaryrefslogtreecommitdiff
path: root/linux-core/nv50_kms_wrapper.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux-core/nv50_kms_wrapper.c')
-rw-r--r--linux-core/nv50_kms_wrapper.c247
1 files changed, 208 insertions, 39 deletions
diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c
index a7966e9a..355d25d6 100644
--- a/linux-core/nv50_kms_wrapper.c
+++ b/linux-core/nv50_kms_wrapper.c
@@ -386,7 +386,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
/* This is to ensure it knows the connector subtype. */
drm_connector->funcs->fill_modes(drm_connector, 0, 0);
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (!output) {
DRM_ERROR("No output\n");
goto out;
@@ -458,7 +458,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
goto out;
}
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (!output) {
DRM_ERROR("No output\n");
goto out;
@@ -635,11 +635,12 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set)
if (connector->output != output)
continue;
- crtc->scaling_mode = connector->scaling_mode;
+ crtc->requested_scaling_mode = connector->requested_scaling_mode;
+ crtc->use_dithering = connector->use_dithering;
break;
}
- if (crtc->scaling_mode == SCALE_PANEL)
+ if (crtc->requested_scaling_mode == SCALE_NON_GPU)
crtc->use_native_mode = false;
else
crtc->use_native_mode = true;
@@ -834,7 +835,9 @@ static int nv50_kms_encoders_init(struct drm_device *dev)
* Connector functions
*/
-bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector)
+
+/* These 2 functions wrap the connector properties that deal with multiple encoders per connector. */
+bool nv50_kms_connector_get_digital(struct drm_connector *drm_connector)
{
struct drm_device *dev = drm_connector->dev;
@@ -891,6 +894,33 @@ bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector)
return false;
}
+static void nv50_kms_connector_set_digital(struct drm_connector *drm_connector, int digital, bool force)
+{
+ struct drm_device *dev = drm_connector->dev;
+
+ if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) {
+ uint64_t cur_value, new_value;
+
+ int rval = drm_connector_property_get_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, &cur_value);
+ if (rval) {
+ DRM_ERROR("Unable to find subconnector property\n");
+ return;
+ }
+
+ /* Only set when unknown or when forced to do so. */
+ if (cur_value != DRM_MODE_SUBCONNECTOR_Unknown && !force)
+ return;
+
+ if (digital == 1)
+ new_value = DRM_MODE_SUBCONNECTOR_DVID;
+ else if (digital == 0)
+ new_value = DRM_MODE_SUBCONNECTOR_DVIA;
+ else
+ new_value = DRM_MODE_SUBCONNECTOR_Unknown;
+ drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, new_value);
+ }
+}
+
void nv50_kms_connector_detect_all(struct drm_device *dev)
{
struct drm_connector *drm_connector = NULL;
@@ -902,16 +932,32 @@ void nv50_kms_connector_detect_all(struct drm_device *dev)
static enum drm_connector_status nv50_kms_connector_detect(struct drm_connector *drm_connector)
{
- struct nv50_connector *connector = to_nv50_connector(drm_connector);
struct drm_device *dev = drm_connector->dev;
- bool connected;
- int old_status;
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ struct nv50_output *output = NULL;
+ int hpd_detect = 0, load_detect = 0, i2c_detect = 0;
+ int old_status = drm_connector->status;
- connected = connector->detect(connector);
+ /* hotplug detect */
+ hpd_detect = connector->hpd_detect(connector);
- old_status = drm_connector->status;
+ /* load detect */
+ output = connector->to_output(connector, FALSE); /* analog */
+ if (output && output->detect)
+ load_detect = output->detect(output);
- if (connected)
+ if (hpd_detect < 0 || load_detect < 0) /* did an error occur? */
+ i2c_detect = connector->i2c_detect(connector);
+
+ if (load_detect == 1) {
+ nv50_kms_connector_set_digital(drm_connector, 0, TRUE); /* analog, forced */
+ } else if (hpd_detect == 1 && load_detect == 0) {
+ nv50_kms_connector_set_digital(drm_connector, 1, TRUE); /* digital, forced */
+ } else {
+ nv50_kms_connector_set_digital(drm_connector, -1, TRUE); /* unknown, forced */
+ }
+
+ if (hpd_detect == 1 || load_detect == 1 || i2c_detect == 1)
drm_connector->status = connector_status_connected;
else
drm_connector->status = connector_status_disconnected;
@@ -955,7 +1001,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
struct nv50_connector *connector = to_nv50_connector(drm_connector);
struct drm_device *dev = drm_connector->dev;
int rval = 0;
- bool connected;
+ bool connected = false;
struct drm_display_mode *mode, *t;
struct edid *edid = NULL;
@@ -964,16 +1010,13 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
list_for_each_entry_safe(mode, t, &drm_connector->modes, head)
mode->status = MODE_UNVERIFIED;
- connected = connector->detect(connector);
+ if (nv50_kms_connector_detect(drm_connector) == connector_status_connected)
+ connected = true;
if (connected)
- drm_connector->status = connector_status_connected;
+ NV50_DEBUG("%s is connected\n", drm_get_connector_name(drm_connector));
else
- drm_connector->status = connector_status_disconnected;
-
- if (!connected) {
NV50_DEBUG("%s is disconnected\n", drm_get_connector_name(drm_connector));
- }
/* Not all connnectors have an i2c channel. */
if (connected && connector->i2c_chan)
@@ -985,16 +1028,8 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
if (edid) {
rval = drm_add_edid_modes(drm_connector, edid);
- /* 2 encoders per connector */
- /* eventually do this based on load detect and hot plug detect */
- if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) {
- uint64_t subtype = 0;
- if (edid->digital)
- subtype = DRM_MODE_SUBCONNECTOR_DVID;
- else
- subtype = DRM_MODE_SUBCONNECTOR_DVIA;
- drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, subtype);
- }
+ /* Only update when relevant and when detect couldn't determine type. */
+ nv50_kms_connector_set_digital(drm_connector, edid->digital ? 1 : 0, FALSE);
kfree(edid);
}
@@ -1008,7 +1043,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
list_for_each_entry_safe(mode, t, &drm_connector->modes, head) {
if (mode->status == MODE_OK) {
struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode);
- struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
mode->status = output->validate_mode(output, hw_mode);
/* find native mode, TODO: also check if we actually found one */
@@ -1024,7 +1059,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
list_for_each_entry_safe(mode, t, &drm_connector->modes, head) {
if (mode->status == MODE_OK) {
struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode);
- struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
mode->status = output->validate_mode(output, hw_mode);
kfree(hw_mode);
@@ -1044,6 +1079,10 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
NV50_DEBUG("No valid modes on %s\n", drm_get_connector_name(drm_connector));
+ /* Making up native modes for LVDS is a bad idea. */
+ if (drm_connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
+ return;
+
/* Should we do this here ???
* When no valid EDID modes are available we end up
* here and bailed in the past, now we add a standard
@@ -1056,7 +1095,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
/* also add it as native mode */
hw_mode = nv50_kms_to_hw_mode(mode);
- output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector));
+ output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector));
if (hw_mode)
*output->native_mode = *hw_mode;
@@ -1078,22 +1117,109 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u
}
}
-static bool nv50_kms_connector_set_property(struct drm_connector *connector,
+static int nv50_kms_connector_set_property(struct drm_connector *drm_connector,
struct drm_property *property,
uint64_t value)
{
- struct drm_device *dev = connector->dev;
+ struct drm_device *dev = drm_connector->dev;
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ int rval = 0;
+ bool delay_change = false;
- if (property == dev->mode_config.dpms_property && connector->encoder) {
- struct nv50_output *output = to_nv50_output(connector->encoder);
+ /* DPMS */
+ if (property == dev->mode_config.dpms_property && drm_connector->encoder) {
+ struct nv50_output *output = to_nv50_output(drm_connector->encoder);
- if (!output->set_power_mode(output, (int) value))
- return true;
+ rval = output->set_power_mode(output, (int) value);
+
+ return rval;
+ }
+
+ /* Scaling mode */
+ if (property == dev->mode_config.scaling_mode_property) {
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_display *display = nv50_get_display(dev);
+ int internal_value = 0;
+
+ switch (value) {
+ case DRM_MODE_SCALE_NON_GPU:
+ internal_value = SCALE_NON_GPU;
+ break;
+ case DRM_MODE_SCALE_FULLSCREEN:
+ internal_value = SCALE_FULLSCREEN;
+ break;
+ case DRM_MODE_SCALE_NO_SCALE:
+ internal_value = SCALE_NOSCALE;
+ break;
+ case DRM_MODE_SCALE_ASPECT:
+ internal_value = SCALE_ASPECT;
+ break;
+ default:
+ break;
+ }
+
+ /* LVDS always needs gpu scaling */
+ if (connector->type == CONNECTOR_LVDS && internal_value == SCALE_NON_GPU)
+ return -EINVAL;
+
+ connector->requested_scaling_mode = internal_value;
+
+ if (drm_connector->encoder && drm_connector->encoder->crtc)
+ crtc = to_nv50_crtc(drm_connector->encoder->crtc);
+
+ if (!crtc)
+ return 0;
+
+ crtc->requested_scaling_mode = connector->requested_scaling_mode;
+
+ /* going from and to a gpu scaled regime requires a modesetting, so wait until next modeset */
+ if (crtc->scaling_mode == SCALE_NON_GPU || internal_value == SCALE_NON_GPU) {
+ DRM_INFO("Moving from or to a non-gpu scaled mode, this will be processed upon next modeset.");
+ delay_change = true;
+ }
+
+ if (delay_change)
+ return 0;
+
+ rval = crtc->set_scale(crtc);
+ if (rval)
+ return rval;
+
+ /* process command buffer */
+ display->update(display);
+
+ return 0;
+ }
+
+ /* Dithering */
+ if (property == dev->mode_config.dithering_mode_property) {
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_display *display = nv50_get_display(dev);
+
+ if (value == DRM_MODE_DITHERING_ON)
+ connector->use_dithering = true;
else
- return false;
+ connector->use_dithering = false;
+
+ if (drm_connector->encoder && drm_connector->encoder->crtc)
+ crtc = to_nv50_crtc(drm_connector->encoder->crtc);
+
+ if (!crtc)
+ return 0;
+
+ /* update hw state */
+ crtc->use_dithering = connector->use_dithering;
+ rval = crtc->set_dither(crtc);
+ if (rval)
+ return rval;
+
+ /* process command buffer */
+ display->update(display);
+
+ return 0;
}
- return false;
+ return -EINVAL;
}
static const struct drm_connector_funcs nv50_kms_connector_funcs = {
@@ -1105,12 +1231,48 @@ static const struct drm_connector_funcs nv50_kms_connector_funcs = {
.set_property = nv50_kms_connector_set_property
};
+static int nv50_kms_get_scaling_mode(struct drm_connector *drm_connector)
+{
+ struct nv50_connector *connector = NULL;
+ int drm_mode = 0;
+
+ if (!drm_connector) {
+ DRM_ERROR("drm_connector is NULL\n");
+ return 0;
+ }
+
+ connector = to_nv50_connector(drm_connector);
+
+ switch (connector->requested_scaling_mode) {
+ case SCALE_NON_GPU:
+ drm_mode = DRM_MODE_SCALE_NON_GPU;
+ break;
+ case SCALE_FULLSCREEN:
+ drm_mode = DRM_MODE_SCALE_FULLSCREEN;
+ break;
+ case SCALE_NOSCALE:
+ drm_mode = DRM_MODE_SCALE_NO_SCALE;
+ break;
+ case SCALE_ASPECT:
+ drm_mode = DRM_MODE_SCALE_ASPECT;
+ break;
+ default:
+ break;
+ }
+
+ return drm_mode;
+}
+
static int nv50_kms_connectors_init(struct drm_device *dev)
{
struct nv50_display *display = nv50_get_display(dev);
struct nv50_connector *connector = NULL;
int i;
+ /* Initialise some optional connector properties. */
+ drm_mode_create_scaling_mode_property(dev);
+ drm_mode_create_dithering_property(dev);
+
list_for_each_entry(connector, &display->connectors, item) {
struct drm_connector *drm_connector = to_nv50_kms_connector(connector);
uint32_t type = DRM_MODE_CONNECTOR_Unknown;
@@ -1154,6 +1316,13 @@ static int nv50_kms_connectors_init(struct drm_device *dev)
drm_connector_attach_property(drm_connector, dev->mode_config.dvi_i_select_subconnector_property, 0);
}
+ /* If supported in the future, it will have to use the scalers internally and not expose them. */
+ if (type != DRM_MODE_CONNECTOR_SVIDEO) {
+ drm_connector_attach_property(drm_connector, dev->mode_config.scaling_mode_property, nv50_kms_get_scaling_mode(drm_connector));
+ }
+
+ drm_connector_attach_property(drm_connector, dev->mode_config.dithering_mode_property, connector->use_dithering ? DRM_MODE_DITHERING_ON : DRM_MODE_DITHERING_OFF);
+
/* attach encoders, possibilities are analog + digital */
for (i = 0; i < 2; i++) {
struct drm_encoder *drm_encoder = NULL;