drm/atomic-helpers: Stall on the right commit
[cascardo/linux.git] / drivers / gpu / drm / drm_atomic_helper.c
index 9450945..716aa53 100644 (file)
@@ -300,8 +300,10 @@ update_connector_routing(struct drm_atomic_state *state,
        if (funcs->atomic_best_encoder)
                new_encoder = funcs->atomic_best_encoder(connector,
                                                         connector_state);
-       else
+       else if (funcs->best_encoder)
                new_encoder = funcs->best_encoder(connector);
+       else
+               new_encoder = drm_atomic_helper_best_encoder(connector);
 
        if (!new_encoder) {
                DRM_DEBUG_ATOMIC("No suitable encoder found for [CONNECTOR:%d:%s]\n",
@@ -463,7 +465,7 @@ mode_fixup(struct drm_atomic_state *state)
  * times for the same update, e.g. when the ->atomic_check functions depend upon
  * the adjusted dotclock for fifo space allocation and watermark computation.
  *
- * RETURNS
+ * RETURNS:
  * Zero for success or -errno
  */
 int
@@ -577,7 +579,7 @@ EXPORT_SYMBOL(drm_atomic_helper_check_modeset);
  * It also sets crtc_state->planes_changed to indicate that a crtc has
  * updated planes.
  *
- * RETURNS
+ * RETURNS:
  * Zero for success or -errno
  */
 int
@@ -645,7 +647,7 @@ EXPORT_SYMBOL(drm_atomic_helper_check_planes);
  * ->atomic_check functions depend upon an updated adjusted_mode.clock to
  * e.g. properly compute watermarks.
  *
- * RETURNS
+ * RETURNS:
  * Zero for success or -errno
  */
 int drm_atomic_helper_check(struct drm_device *dev,
@@ -1118,22 +1120,17 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev,
 EXPORT_SYMBOL(drm_atomic_helper_wait_for_vblanks);
 
 /**
- * drm_atomic_helper_commit - commit validated state object
- * @dev: DRM device
- * @state: the driver state object
- * @nonblocking: whether nonblocking behavior is requested.
+ * drm_atomic_helper_commit_tail - commit atomic update to hardware
+ * @state: new modeset state to be committed
  *
- * This function commits a with drm_atomic_helper_check() pre-validated state
- * object. This can still fail when e.g. the framebuffer reservation fails. For
- * now this doesn't implement nonblocking commits.
+ * This is the default implemenation for the ->atomic_commit_tail() hook of the
+ * &drm_mode_config_helper_funcs vtable.
  *
- * Note that right now this function does not support nonblocking commits, hence
- * driver writers must implement their own version for now. Also note that the
- * default ordering of how the various stages are called is to match the legacy
- * modeset helper library closest. One peculiarity of that is that it doesn't
- * mesh well with runtime PM at all.
+ * Note that the default ordering of how the various stages are called is to
+ * match the legacy modeset helper library closest. One peculiarity of that is
+ * that it doesn't mesh well with runtime PM at all.
  *
- * For drivers supporting runtime PM the recommended sequence is
+ * For drivers supporting runtime PM the recommended sequence is instead ::
  *
  *     drm_atomic_helper_commit_modeset_disables(dev, state);
  *
@@ -1141,9 +1138,75 @@ EXPORT_SYMBOL(drm_atomic_helper_wait_for_vblanks);
  *
  *     drm_atomic_helper_commit_planes(dev, state, true);
  *
- * See the kerneldoc entries for these three functions for more details.
+ * for committing the atomic update to hardware.  See the kerneldoc entries for
+ * these three functions for more details.
+ */
+void drm_atomic_helper_commit_tail(struct drm_atomic_state *state)
+{
+       struct drm_device *dev = state->dev;
+
+       drm_atomic_helper_commit_modeset_disables(dev, state);
+
+       drm_atomic_helper_commit_planes(dev, state, false);
+
+       drm_atomic_helper_commit_modeset_enables(dev, state);
+
+       drm_atomic_helper_commit_hw_done(state);
+
+       drm_atomic_helper_wait_for_vblanks(dev, state);
+
+       drm_atomic_helper_cleanup_planes(dev, state);
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_tail);
+
+static void commit_tail(struct drm_atomic_state *state)
+{
+       struct drm_device *dev = state->dev;
+       struct drm_mode_config_helper_funcs *funcs;
+
+       funcs = dev->mode_config.helper_private;
+
+       drm_atomic_helper_wait_for_fences(dev, state);
+
+       drm_atomic_helper_wait_for_dependencies(state);
+
+       if (funcs && funcs->atomic_commit_tail)
+               funcs->atomic_commit_tail(state);
+       else
+               drm_atomic_helper_commit_tail(state);
+
+       drm_atomic_helper_commit_cleanup_done(state);
+
+       drm_atomic_state_free(state);
+}
+
+static void commit_work(struct work_struct *work)
+{
+       struct drm_atomic_state *state = container_of(work,
+                                                     struct drm_atomic_state,
+                                                     commit_work);
+       commit_tail(state);
+}
+
+/**
+ * drm_atomic_helper_commit - commit validated state object
+ * @dev: DRM device
+ * @state: the driver state object
+ * @nonblock: whether nonblocking behavior is requested.
+ *
+ * This function commits a with drm_atomic_helper_check() pre-validated state
+ * object. This can still fail when e.g. the framebuffer reservation fails. This
+ * function implements nonblocking commits, using
+ * drm_atomic_helper_setup_commit() and related functions.
+ *
+ * Note that right now this function does not support nonblocking commits, hence
+ * driver writers must implement their own version for now.
+ *
+ * Committing the actual hardware state is done through the
+ * ->atomic_commit_tail() callback of the &drm_mode_config_helper_funcs vtable,
+ * or it's default implementation drm_atomic_helper_commit_tail().
  *
- * RETURNS
+ * RETURNS:
  * Zero for success or -errno.
  */
 int drm_atomic_helper_commit(struct drm_device *dev,
@@ -1152,8 +1215,11 @@ int drm_atomic_helper_commit(struct drm_device *dev,
 {
        int ret;
 
-       if (nonblock)
-               return -EBUSY;
+       ret = drm_atomic_helper_setup_commit(state, nonblock);
+       if (ret)
+               return ret;
+
+       INIT_WORK(&state->commit_work, commit_work);
 
        ret = drm_atomic_helper_prepare_planes(dev, state);
        if (ret)
@@ -1165,7 +1231,7 @@ int drm_atomic_helper_commit(struct drm_device *dev,
         * the software side now.
         */
 
-       drm_atomic_helper_swap_state(dev, state);
+       drm_atomic_helper_swap_state(state, true);
 
        /*
         * Everything below can be run asynchronously without the need to grab
@@ -1181,21 +1247,16 @@ int drm_atomic_helper_commit(struct drm_device *dev,
         * update. Which is important since compositors need to figure out the
         * composition of the next frame right after having submitted the
         * current layout.
+        *
+        * NOTE: Commit work has multiple phases, first hardware commit, then
+        * cleanup. We want them to overlap, hence need system_unbound_wq to
+        * make sure work items don't artifically stall on each another.
         */
 
-       drm_atomic_helper_wait_for_fences(dev, state);
-
-       drm_atomic_helper_commit_modeset_disables(dev, state);
-
-       drm_atomic_helper_commit_planes(dev, state, false);
-
-       drm_atomic_helper_commit_modeset_enables(dev, state);
-
-       drm_atomic_helper_wait_for_vblanks(dev, state);
-
-       drm_atomic_helper_cleanup_planes(dev, state);
-
-       drm_atomic_state_free(state);
+       if (nonblock)
+               queue_work(system_unbound_wq, &state->commit_work);
+       else
+               commit_tail(state);
 
        return 0;
 }
@@ -1204,12 +1265,7 @@ EXPORT_SYMBOL(drm_atomic_helper_commit);
 /**
  * DOC: implementing nonblocking commit
  *
- * For now the atomic helpers don't support nonblocking commit directly. If
- * there is real need it could be added though, using the dma-buf fence
- * infrastructure for generic synchronization with outstanding rendering.
- *
- * For now drivers have to implement nonblocking commit themselves, with the
- * following sequence being the recommended one:
+ * Nonblocking atomic commits have to be implemented in the following sequence:
  *
  * 1. Run drm_atomic_helper_prepare_planes() first. This is the only function
  * which commit needs to call which can fail, so we want to run it first and
@@ -1221,10 +1277,14 @@ EXPORT_SYMBOL(drm_atomic_helper_commit);
  * cancelled updates. Note that it is important to ensure that the framebuffer
  * cleanup is still done when cancelling.
  *
- * For sufficient parallelism it is recommended to have a work item per crtc
- * (for updates which don't touch global state) and a global one. Then we only
- * need to synchronize with the crtc work items for changed crtcs and the global
- * work item, which allows nice concurrent updates on disjoint sets of crtcs.
+ * Asynchronous workers need to have sufficient parallelism to be able to run
+ * different atomic commits on different CRTCs in parallel. The simplest way to
+ * achive this is by running them on the &system_unbound_wq work queue. Note
+ * that drivers are not required to split up atomic commits and run an
+ * individual commit in parallel - userspace is supposed to do that if it cares.
+ * But it might be beneficial to do that for modesets, since those necessarily
+ * must be done as one global operation, and enabling or disabling a CRTC can
+ * take a long time. But even that is not required.
  *
  * 3. The software state is updated synchronously with
  * drm_atomic_helper_swap_state(). Doing this under the protection of all modeset
@@ -1237,8 +1297,312 @@ EXPORT_SYMBOL(drm_atomic_helper_commit);
  * commit helpers: a) pre-plane commit b) plane commit c) post-plane commit and
  * then cleaning up the framebuffers after the old framebuffer is no longer
  * being displayed.
+ *
+ * The above scheme is implemented in the atomic helper libraries in
+ * drm_atomic_helper_commit() using a bunch of helper functions. See
+ * drm_atomic_helper_setup_commit() for a starting point.
  */
 
+static int stall_checks(struct drm_crtc *crtc, bool nonblock)
+{
+       struct drm_crtc_commit *commit, *stall_commit = NULL;
+       bool completed = true;
+       int i;
+       long ret = 0;
+
+       spin_lock(&crtc->commit_lock);
+       i = 0;
+       list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
+               if (i == 0) {
+                       completed = try_wait_for_completion(&commit->flip_done);
+                       /* Userspace is not allowed to get ahead of the previous
+                        * commit with nonblocking ones. */
+                       if (!completed && nonblock) {
+                               spin_unlock(&crtc->commit_lock);
+                               return -EBUSY;
+                       }
+               } else if (i == 1) {
+                       stall_commit = commit;
+                       drm_crtc_commit_get(stall_commit);
+                       break;
+               }
+
+               i++;
+       }
+       spin_unlock(&crtc->commit_lock);
+
+       if (!stall_commit)
+               return 0;
+
+       /* We don't want to let commits get ahead of cleanup work too much,
+        * stalling on 2nd previous commit means triple-buffer won't ever stall.
+        */
+       ret = wait_for_completion_interruptible_timeout(&stall_commit->cleanup_done,
+                                                       10*HZ);
+       if (ret == 0)
+               DRM_ERROR("[CRTC:%d:%s] cleanup_done timed out\n",
+                         crtc->base.id, crtc->name);
+
+       drm_crtc_commit_put(stall_commit);
+
+       return ret < 0 ? ret : 0;
+}
+
+/**
+ * drm_atomic_helper_setup_commit - setup possibly nonblocking commit
+ * @state: new modeset state to be committed
+ * @nonblock: whether nonblocking behavior is requested.
+ *
+ * This function prepares @state to be used by the atomic helper's support for
+ * nonblocking commits. Drivers using the nonblocking commit infrastructure
+ * should always call this function from their ->atomic_commit hook.
+ *
+ * To be able to use this support drivers need to use a few more helper
+ * functions. drm_atomic_helper_wait_for_dependencies() must be called before
+ * actually committing the hardware state, and for nonblocking commits this call
+ * must be placed in the async worker. See also drm_atomic_helper_swap_state()
+ * and it's stall parameter, for when a driver's commit hooks look at the
+ * ->state pointers of struct &drm_crtc, &drm_plane or &drm_connector directly.
+ *
+ * Completion of the hardware commit step must be signalled using
+ * drm_atomic_helper_commit_hw_done(). After this step the driver is not allowed
+ * to read or change any permanent software or hardware modeset state. The only
+ * exception is state protected by other means than &drm_modeset_lock locks.
+ * Only the free standing @state with pointers to the old state structures can
+ * be inspected, e.g. to clean up old buffers using
+ * drm_atomic_helper_cleanup_planes().
+ *
+ * At the very end, before cleaning up @state drivers must call
+ * drm_atomic_helper_commit_cleanup_done().
+ *
+ * This is all implemented by in drm_atomic_helper_commit(), giving drivers a
+ * complete and esay-to-use default implementation of the atomic_commit() hook.
+ *
+ * The tracking of asynchronously executed and still pending commits is done
+ * using the core structure &drm_crtc_commit.
+ *
+ * By default there's no need to clean up resources allocated by this function
+ * explicitly: drm_atomic_state_default_clear() will take care of that
+ * automatically.
+ *
+ * Returns:
+ *
+ * 0 on success. -EBUSY when userspace schedules nonblocking commits too fast,
+ * -ENOMEM on allocation failures and -EINTR when a signal is pending.
+ */
+int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
+                                  bool nonblock)
+{
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *crtc_state;
+       struct drm_crtc_commit *commit;
+       int i, ret;
+
+       for_each_crtc_in_state(state, crtc, crtc_state, i) {
+               commit = kzalloc(sizeof(*commit), GFP_KERNEL);
+               if (!commit)
+                       return -ENOMEM;
+
+               init_completion(&commit->flip_done);
+               init_completion(&commit->hw_done);
+               init_completion(&commit->cleanup_done);
+               INIT_LIST_HEAD(&commit->commit_entry);
+               kref_init(&commit->ref);
+               commit->crtc = crtc;
+
+               state->crtcs[i].commit = commit;
+
+               ret = stall_checks(crtc, nonblock);
+               if (ret)
+                       return ret;
+
+               /* Drivers only send out events when at least either current or
+                * new CRTC state is active. Complete right away if everything
+                * stays off. */
+               if (!crtc->state->active && !crtc_state->active) {
+                       complete_all(&commit->flip_done);
+                       continue;
+               }
+
+               /* Legacy cursor updates are fully unsynced. */
+               if (state->legacy_cursor_update) {
+                       complete_all(&commit->flip_done);
+                       continue;
+               }
+
+               if (!crtc_state->event) {
+                       commit->event = kzalloc(sizeof(*commit->event),
+                                               GFP_KERNEL);
+                       if (!commit->event)
+                               return -ENOMEM;
+
+                       crtc_state->event = commit->event;
+               }
+
+               crtc_state->event->base.completion = &commit->flip_done;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_atomic_helper_setup_commit);
+
+
+static struct drm_crtc_commit *preceeding_commit(struct drm_crtc *crtc)
+{
+       struct drm_crtc_commit *commit;
+       int i = 0;
+
+       list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
+               /* skip the first entry, that's the current commit */
+               if (i == 1)
+                       return commit;
+               i++;
+       }
+
+       return NULL;
+}
+
+/**
+ * drm_atomic_helper_wait_for_dependencies - wait for required preceeding commits
+ * @state: new modeset state to be committed
+ *
+ * This function waits for all preceeding commits that touch the same CRTC as
+ * @state to both be committed to the hardware (as signalled by
+ * drm_atomic_helper_commit_hw_done) and executed by the hardware (as signalled
+ * by calling drm_crtc_vblank_send_event on the event member of
+ * &drm_crtc_state).
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *state)
+{
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *crtc_state;
+       struct drm_crtc_commit *commit;
+       int i;
+       long ret;
+
+       for_each_crtc_in_state(state, crtc, crtc_state, i) {
+               spin_lock(&crtc->commit_lock);
+               commit = preceeding_commit(crtc);
+               if (commit)
+                       drm_crtc_commit_get(commit);
+               spin_unlock(&crtc->commit_lock);
+
+               if (!commit)
+                       continue;
+
+               ret = wait_for_completion_timeout(&commit->hw_done,
+                                                 10*HZ);
+               if (ret == 0)
+                       DRM_ERROR("[CRTC:%d:%s] hw_done timed out\n",
+                                 crtc->base.id, crtc->name);
+
+               /* Currently no support for overwriting flips, hence
+                * stall for previous one to execute completely. */
+               ret = wait_for_completion_timeout(&commit->flip_done,
+                                                 10*HZ);
+               if (ret == 0)
+                       DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n",
+                                 crtc->base.id, crtc->name);
+
+               drm_crtc_commit_put(commit);
+       }
+}
+EXPORT_SYMBOL(drm_atomic_helper_wait_for_dependencies);
+
+/**
+ * drm_atomic_helper_commit_hw_done - setup possible nonblocking commit
+ * @state: new modeset state to be committed
+ *
+ * This function is used to signal completion of the hardware commit step. After
+ * this step the driver is not allowed to read or change any permanent software
+ * or hardware modeset state. The only exception is state protected by other
+ * means than &drm_modeset_lock locks.
+ *
+ * Drivers should try to postpone any expensive or delayed cleanup work after
+ * this function is called.
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *state)
+{
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *crtc_state;
+       struct drm_crtc_commit *commit;
+       int i;
+
+       for_each_crtc_in_state(state, crtc, crtc_state, i) {
+               commit = state->crtcs[i].commit;
+               if (!commit)
+                       continue;
+
+               /* backend must have consumed any event by now */
+               WARN_ON(crtc->state->event);
+               spin_lock(&crtc->commit_lock);
+               complete_all(&commit->hw_done);
+               spin_unlock(&crtc->commit_lock);
+       }
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_hw_done);
+
+/**
+ * drm_atomic_helper_commit_cleanup_done - signal completion of commit
+ * @state: new modeset state to be committed
+ *
+ * This signals completion of the atomic update @state, including any cleanup
+ * work. If used, it must be called right before calling
+ * drm_atomic_state_free().
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *state)
+{
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *crtc_state;
+       struct drm_crtc_commit *commit;
+       int i;
+       long ret;
+
+       for_each_crtc_in_state(state, crtc, crtc_state, i) {
+               commit = state->crtcs[i].commit;
+               if (WARN_ON(!commit))
+                       continue;
+
+               spin_lock(&crtc->commit_lock);
+               complete_all(&commit->cleanup_done);
+               WARN_ON(!try_wait_for_completion(&commit->hw_done));
+
+               /* commit_list borrows our reference, need to remove before we
+                * clean up our drm_atomic_state. But only after it actually
+                * completed, otherwise subsequent commits won't stall properly. */
+               if (try_wait_for_completion(&commit->flip_done)) {
+                       list_del(&commit->commit_entry);
+                       spin_unlock(&crtc->commit_lock);
+                       continue;
+               }
+
+               spin_unlock(&crtc->commit_lock);
+
+               /* We must wait for the vblank event to signal our completion
+                * before releasing our reference, since the vblank work does
+                * not hold a reference of its own. */
+               ret = wait_for_completion_timeout(&commit->flip_done,
+                                                 10*HZ);
+               if (ret == 0)
+                       DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n",
+                                 crtc->base.id, crtc->name);
+
+               spin_lock(&crtc->commit_lock);
+               list_del(&commit->commit_entry);
+               spin_unlock(&crtc->commit_lock);
+       }
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_cleanup_done);
+
 /**
  * drm_atomic_helper_prepare_planes - prepare plane resources before commit
  * @dev: DRM device
@@ -1536,8 +1900,8 @@ EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes);
 
 /**
  * drm_atomic_helper_swap_state - store atomic state into current sw state
- * @dev: DRM device
  * @state: atomic state
+ * @stall: stall for proceeding commits
  *
  * This function stores the atomic state into the current state pointers in all
  * driver objects. It should be called after all failing steps have been done
@@ -1558,17 +1922,45 @@ EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes);
  *
  * 5. Call drm_atomic_helper_cleanup_planes() with @state, which since step 3
  * contains the old state. Also do any other cleanup required with that state.
+ *
+ * @stall must be set when nonblocking commits for this driver directly access
+ * the ->state pointer of &drm_plane, &drm_crtc or &drm_connector. With the
+ * current atomic helpers this is almost always the case, since the helpers
+ * don't pass the right state structures to the callbacks.
  */
-void drm_atomic_helper_swap_state(struct drm_device *dev,
-                                 struct drm_atomic_state *state)
+void drm_atomic_helper_swap_state(struct drm_atomic_state *state,
+                                 bool stall)
 {
        int i;
+       long ret;
        struct drm_connector *connector;
        struct drm_connector_state *conn_state;
        struct drm_crtc *crtc;
        struct drm_crtc_state *crtc_state;
        struct drm_plane *plane;
        struct drm_plane_state *plane_state;
+       struct drm_crtc_commit *commit;
+
+       if (stall) {
+               for_each_crtc_in_state(state, crtc, crtc_state, i) {
+                       spin_lock(&crtc->commit_lock);
+                       commit = list_first_entry_or_null(&crtc->commit_list,
+                                       struct drm_crtc_commit, commit_entry);
+                       if (commit)
+                               drm_crtc_commit_get(commit);
+                       spin_unlock(&crtc->commit_lock);
+
+                       if (!commit)
+                               continue;
+
+                       ret = wait_for_completion_timeout(&commit->hw_done,
+                                                         10*HZ);
+                       if (ret == 0)
+                               DRM_ERROR("[CRTC:%d:%s] hw_done timed out\n",
+                                         crtc->base.id, crtc->name);
+                       drm_crtc_commit_put(commit);
+               }
+       }
 
        for_each_connector_in_state(state, connector, conn_state, i) {
                connector->state->state = state;
@@ -1580,6 +1972,15 @@ void drm_atomic_helper_swap_state(struct drm_device *dev,
                crtc->state->state = state;
                swap(state->crtcs[i].state, crtc->state);
                crtc->state->state = NULL;
+
+               if (state->crtcs[i].commit) {
+                       spin_lock(&crtc->commit_lock);
+                       list_add(&state->crtcs[i].commit->commit_entry,
+                                &crtc->commit_list);
+                       spin_unlock(&crtc->commit_lock);
+
+                       state->crtcs[i].commit->event = NULL;
+               }
        }
 
        for_each_plane_in_state(state, plane, plane_state, i) {
@@ -2920,16 +3321,15 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
  * @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)
+int drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
+                                      u16 *red, u16 *green, u16 *blue,
+                                      uint32_t size)
 {
        struct drm_device *dev = crtc->dev;
        struct drm_mode_config *config = &dev->mode_config;
@@ -2941,7 +3341,7 @@ void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
 
        state = drm_atomic_state_alloc(crtc->dev);
        if (!state)
-               return;
+               return -ENOMEM;
 
        blob = drm_property_create_blob(dev,
                                        sizeof(struct drm_color_lut) * size,
@@ -2992,7 +3392,7 @@ retry:
 
        drm_property_unreference_blob(blob);
 
-       return;
+       return 0;
 fail:
        if (ret == -EDEADLK)
                goto backoff;
@@ -3000,7 +3400,7 @@ fail:
        drm_atomic_state_free(state);
        drm_property_unreference_blob(blob);
 
-       return;
+       return ret;
 backoff:
        drm_atomic_state_clear(state);
        drm_atomic_legacy_backoff(state);