Merge tag 'topic/drm-misc-2016-03-22' of git://anongit.freedesktop.org/drm-intel...
[linux-2.6-block.git] / drivers / gpu / drm / drm_atomic_helper.c
index 32f6f57fc66ea79c718eea093f8f31c5b804ff2b..4befe25c81c7b1d89aa5a47dc87a46542136b381 100644 (file)
@@ -67,7 +67,8 @@ drm_atomic_helper_plane_changed(struct drm_atomic_state *state,
        struct drm_crtc_state *crtc_state;
 
        if (plane->state->crtc) {
-               crtc_state = state->crtc_states[drm_crtc_index(plane->state->crtc)];
+               crtc_state = drm_atomic_get_existing_crtc_state(state,
+                                                               plane->state->crtc);
 
                if (WARN_ON(!crtc_state))
                        return;
@@ -76,8 +77,8 @@ drm_atomic_helper_plane_changed(struct drm_atomic_state *state,
        }
 
        if (plane_state->crtc) {
-               crtc_state =
-                       state->crtc_states[drm_crtc_index(plane_state->crtc)];
+               crtc_state = drm_atomic_get_existing_crtc_state(state,
+                                                               plane_state->crtc);
 
                if (WARN_ON(!crtc_state))
                        return;
@@ -86,24 +87,104 @@ drm_atomic_helper_plane_changed(struct drm_atomic_state *state,
        }
 }
 
-static bool
-check_pending_encoder_assignment(struct drm_atomic_state *state,
-                                struct drm_encoder *new_encoder)
+static int handle_conflicting_encoders(struct drm_atomic_state *state,
+                                      bool disable_conflicting_encoders)
 {
-       struct drm_connector *connector;
        struct drm_connector_state *conn_state;
-       int i;
+       struct drm_connector *connector;
+       struct drm_encoder *encoder;
+       unsigned encoder_mask = 0;
+       int i, ret;
 
+       /*
+        * First loop, find all newly assigned encoders from the connectors
+        * part of the state. If the same encoder is assigned to multiple
+        * connectors bail out.
+        */
        for_each_connector_in_state(state, connector, conn_state, i) {
-               if (conn_state->best_encoder != new_encoder)
+               const struct drm_connector_helper_funcs *funcs = connector->helper_private;
+               struct drm_encoder *new_encoder;
+
+               if (!conn_state->crtc)
                        continue;
 
-               /* encoder already assigned and we're trying to re-steal it! */
-               if (connector->state->best_encoder != conn_state->best_encoder)
-                       return false;
+               if (funcs->atomic_best_encoder)
+                       new_encoder = funcs->atomic_best_encoder(connector, conn_state);
+               else
+                       new_encoder = funcs->best_encoder(connector);
+
+               if (new_encoder) {
+                       if (encoder_mask & (1 << drm_encoder_index(new_encoder))) {
+                               DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] on [CONNECTOR:%d:%s] already assigned\n",
+                                       new_encoder->base.id, new_encoder->name,
+                                       connector->base.id, connector->name);
+
+                               return -EINVAL;
+                       }
+
+                       encoder_mask |= 1 << drm_encoder_index(new_encoder);
+               }
        }
 
-       return true;
+       if (!encoder_mask)
+               return 0;
+
+       /*
+        * Second loop, iterate over all connectors not part of the state.
+        *
+        * If a conflicting encoder is found and disable_conflicting_encoders
+        * is not set, an error is returned. Userspace can provide a solution
+        * through the atomic ioctl.
+        *
+        * If the flag is set conflicting connectors are removed from the crtc
+        * and the crtc is disabled if no encoder is left. This preserves
+        * compatibility with the legacy set_config behavior.
+        */
+       drm_for_each_connector(connector, state->dev) {
+               struct drm_crtc_state *crtc_state;
+
+               if (drm_atomic_get_existing_connector_state(state, connector))
+                       continue;
+
+               encoder = connector->state->best_encoder;
+               if (!encoder || !(encoder_mask & (1 << drm_encoder_index(encoder))))
+                       continue;
+
+               if (!disable_conflicting_encoders) {
+                       DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] in use on [CRTC:%d:%s] by [CONNECTOR:%d:%s]\n",
+                                        encoder->base.id, encoder->name,
+                                        connector->state->crtc->base.id,
+                                        connector->state->crtc->name,
+                                        connector->base.id, connector->name);
+                       return -EINVAL;
+               }
+
+               conn_state = drm_atomic_get_connector_state(state, connector);
+               if (IS_ERR(conn_state))
+                       return PTR_ERR(conn_state);
+
+               DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] in use on [CRTC:%d:%s], disabling [CONNECTOR:%d:%s]\n",
+                                encoder->base.id, encoder->name,
+                                conn_state->crtc->base.id, conn_state->crtc->name,
+                                connector->base.id, connector->name);
+
+               crtc_state = drm_atomic_get_existing_crtc_state(state, conn_state->crtc);
+
+               ret = drm_atomic_set_crtc_for_connector(conn_state, NULL);
+               if (ret)
+                       return ret;
+
+               if (!crtc_state->connector_mask) {
+                       ret = drm_atomic_set_mode_prop_for_crtc(crtc_state,
+                                                               NULL);
+                       if (ret < 0)
+                               return ret;
+
+                       crtc_state->active = false;
+               }
+       }
+
+       return 0;
 }
 
 static void
@@ -147,25 +228,18 @@ set_best_encoder(struct drm_atomic_state *state,
        conn_state->best_encoder = encoder;
 }
 
-static int
+static void
 steal_encoder(struct drm_atomic_state *state,
              struct drm_encoder *encoder)
 {
        struct drm_crtc_state *crtc_state;
        struct drm_connector *connector;
        struct drm_connector_state *connector_state;
+       int i;
 
-       drm_for_each_connector(connector, state->dev) {
+       for_each_connector_in_state(state, connector, connector_state, i) {
                struct drm_crtc *encoder_crtc;
 
-               if (connector->state->best_encoder != encoder)
-                       continue;
-
-               connector_state = drm_atomic_get_connector_state(state,
-                                                                connector);
-               if (IS_ERR(connector_state))
-                       return PTR_ERR(connector_state);
-
                if (connector_state->best_encoder != encoder)
                        continue;
 
@@ -180,10 +254,8 @@ steal_encoder(struct drm_atomic_state *state,
                crtc_state = drm_atomic_get_existing_crtc_state(state, encoder_crtc);
                crtc_state->connectors_changed = true;
 
-               return 0;
+               return;
        }
-
-       return 0;
 }
 
 static int
@@ -194,7 +266,6 @@ update_connector_routing(struct drm_atomic_state *state,
        const struct drm_connector_helper_funcs *funcs;
        struct drm_encoder *new_encoder;
        struct drm_crtc_state *crtc_state;
-       int idx, ret;
 
        DRM_DEBUG_ATOMIC("Updating routing for [CONNECTOR:%d:%s]\n",
                         connector->base.id,
@@ -202,16 +273,12 @@ update_connector_routing(struct drm_atomic_state *state,
 
        if (connector->state->crtc != connector_state->crtc) {
                if (connector->state->crtc) {
-                       idx = drm_crtc_index(connector->state->crtc);
-
-                       crtc_state = state->crtc_states[idx];
+                       crtc_state = drm_atomic_get_existing_crtc_state(state, connector->state->crtc);
                        crtc_state->connectors_changed = true;
                }
 
                if (connector_state->crtc) {
-                       idx = drm_crtc_index(connector_state->crtc);
-
-                       crtc_state = state->crtc_states[idx];
+                       crtc_state = drm_atomic_get_existing_crtc_state(state, connector_state->crtc);
                        crtc_state->connectors_changed = true;
                }
        }
@@ -263,29 +330,11 @@ update_connector_routing(struct drm_atomic_state *state,
                return 0;
        }
 
-       if (!check_pending_encoder_assignment(state, new_encoder)) {
-               DRM_DEBUG_ATOMIC("Encoder for [CONNECTOR:%d:%s] already assigned\n",
-                                connector->base.id,
-                                connector->name);
-               return -EINVAL;
-       }
-
-       ret = steal_encoder(state, new_encoder);
-       if (ret) {
-               DRM_DEBUG_ATOMIC("Encoder stealing failed for [CONNECTOR:%d:%s]\n",
-                                connector->base.id,
-                                connector->name);
-               return ret;
-       }
-
-       if (WARN_ON(!connector_state->crtc))
-               return -EINVAL;
+       steal_encoder(state, new_encoder);
 
        set_best_encoder(state, connector_state, new_encoder);
 
-       idx = drm_crtc_index(connector_state->crtc);
-
-       crtc_state = state->crtc_states[idx];
+       crtc_state = drm_atomic_get_existing_crtc_state(state, connector_state->crtc);
        crtc_state->connectors_changed = true;
 
        DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] using [ENCODER:%d:%s] on [CRTC:%d:%s]\n",
@@ -326,8 +375,8 @@ mode_fixup(struct drm_atomic_state *state)
                if (!conn_state->crtc || !conn_state->best_encoder)
                        continue;
 
-               crtc_state =
-                       state->crtc_states[drm_crtc_index(conn_state->crtc)];
+               crtc_state = drm_atomic_get_existing_crtc_state(state,
+                                                               conn_state->crtc);
 
                /*
                 * Each encoder has at most one connector (since we always steal
@@ -448,6 +497,10 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
                }
        }
 
+       ret = handle_conflicting_encoders(state, state->legacy_set_config);
+       if (ret)
+               return ret;
+
        for_each_connector_in_state(state, connector, connector_state, i) {
                /*
                 * This only sets crtc->mode_changed for routing changes,
@@ -627,7 +680,8 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
                if (!old_conn_state->crtc)
                        continue;
 
-               old_crtc_state = old_state->crtc_states[drm_crtc_index(old_conn_state->crtc)];
+               old_crtc_state = drm_atomic_get_existing_crtc_state(old_state,
+                                                                   old_conn_state->crtc);
 
                if (!old_crtc_state->active ||
                    !drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
@@ -1496,7 +1550,7 @@ void drm_atomic_helper_swap_state(struct drm_device *dev,
 {
        int i;
 
-       for (i = 0; i < dev->mode_config.num_connector; i++) {
+       for (i = 0; i < state->num_connector; i++) {
                struct drm_connector *connector = state->connectors[i];
 
                if (!connector)
@@ -1796,6 +1850,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set)
        if (!state)
                return -ENOMEM;
 
+       state->legacy_set_config = true;
        state->acquire_ctx = drm_modeset_legacy_acquire_ctx(crtc);
 retry:
        ret = __drm_atomic_helper_set_config(set, state);
@@ -2442,8 +2497,12 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_dpms);
  */
 void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc)
 {
-       if (crtc->state)
+       if (crtc->state) {
                drm_property_unreference_blob(crtc->state->mode_blob);
+               drm_property_unreference_blob(crtc->state->degamma_lut);
+               drm_property_unreference_blob(crtc->state->ctm);
+               drm_property_unreference_blob(crtc->state->gamma_lut);
+       }
        kfree(crtc->state);
        crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
 
@@ -2467,10 +2526,17 @@ void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
 
        if (state->mode_blob)
                drm_property_reference_blob(state->mode_blob);
+       if (state->degamma_lut)
+               drm_property_reference_blob(state->degamma_lut);
+       if (state->ctm)
+               drm_property_reference_blob(state->ctm);
+       if (state->gamma_lut)
+               drm_property_reference_blob(state->gamma_lut);
        state->mode_changed = false;
        state->active_changed = false;
        state->planes_changed = false;
        state->connectors_changed = false;
+       state->color_mgmt_changed = false;
        state->event = NULL;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_crtc_duplicate_state);
@@ -2511,6 +2577,9 @@ void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
                                            struct drm_crtc_state *state)
 {
        drm_property_unreference_blob(state->mode_blob);
+       drm_property_unreference_blob(state->degamma_lut);
+       drm_property_unreference_blob(state->ctm);
+       drm_property_unreference_blob(state->gamma_lut);
 }
 EXPORT_SYMBOL(__drm_atomic_helper_crtc_destroy_state);
 
@@ -2824,3 +2893,98 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
        kfree(state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
+
+/**
+ * drm_atomic_helper_legacy_gamma_set - set the legacy gamma correction table
+ * @crtc: CRTC object
+ * @red: red correction table
+ * @green: green correction table
+ * @blue: green correction table
+ * @start:
+ * @size: size of the tables
+ *
+ * Implements support for legacy gamma correction table for drivers
+ * that support color management through the DEGAMMA_LUT/GAMMA_LUT
+ * properties.
+ */
+void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
+                                       u16 *red, u16 *green, u16 *blue,
+                                       uint32_t start, uint32_t size)
+{
+       struct drm_device *dev = crtc->dev;
+       struct drm_mode_config *config = &dev->mode_config;
+       struct drm_atomic_state *state;
+       struct drm_crtc_state *crtc_state;
+       struct drm_property_blob *blob = NULL;
+       struct drm_color_lut *blob_data;
+       int i, ret = 0;
+
+       state = drm_atomic_state_alloc(crtc->dev);
+       if (!state)
+               return;
+
+       blob = drm_property_create_blob(dev,
+                                       sizeof(struct drm_color_lut) * size,
+                                       NULL);
+       if (IS_ERR(blob)) {
+               ret = PTR_ERR(blob);
+               blob = NULL;
+               goto fail;
+       }
+
+       /* Prepare GAMMA_LUT with the legacy values. */
+       blob_data = (struct drm_color_lut *) blob->data;
+       for (i = 0; i < size; i++) {
+               blob_data[i].red = red[i];
+               blob_data[i].green = green[i];
+               blob_data[i].blue = blue[i];
+       }
+
+       state->acquire_ctx = crtc->dev->mode_config.acquire_ctx;
+retry:
+       crtc_state = drm_atomic_get_crtc_state(state, crtc);
+       if (IS_ERR(crtc_state)) {
+               ret = PTR_ERR(crtc_state);
+               goto fail;
+       }
+
+       /* Reset DEGAMMA_LUT and CTM properties. */
+       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+                       config->degamma_lut_property, 0);
+       if (ret)
+               goto fail;
+
+       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+                       config->ctm_property, 0);
+       if (ret)
+               goto fail;
+
+       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+                       config->gamma_lut_property, blob->base.id);
+       if (ret)
+               goto fail;
+
+       ret = drm_atomic_commit(state);
+       if (ret)
+               goto fail;
+
+       /* Driver takes ownership of state on successful commit. */
+
+       drm_property_unreference_blob(blob);
+
+       return;
+fail:
+       if (ret == -EDEADLK)
+               goto backoff;
+
+       drm_atomic_state_free(state);
+       drm_property_unreference_blob(blob);
+
+       return;
+backoff:
+       drm_atomic_state_clear(state);
+       drm_atomic_legacy_backoff(state);
+
+       goto retry;
+}
+EXPORT_SYMBOL(drm_atomic_helper_legacy_gamma_set);