drm/nouveau/kms: prepare to support suspend/resume of display state with atomic
authorBen Skeggs <bskeggs@redhat.com>
Fri, 4 Nov 2016 07:20:36 +0000 (17:20 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Mon, 7 Nov 2016 04:04:49 +0000 (14:04 +1000)
This is different from the equivilant functions in the atomic helpers in
that we fully disable the pipe instead of just setting it to inactive.

We do this (primarily) to ensure the framebuffer cleanup paths are hit,
allowing buffers to be un-pinned from memory so they can be evicted to
system memory and not lose their contents while suspended.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_display.c
drivers/gpu/drm/nouveau/nouveau_display.h

index 340ca3e..79b5f9d 100644 (file)
@@ -25,6 +25,8 @@
  */
 
 #include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 
 #include <nvif/class.h>
@@ -573,11 +575,137 @@ nouveau_display_destroy(struct drm_device *dev)
        kfree(disp);
 }
 
+static int
+nouveau_atomic_disable_connector(struct drm_atomic_state *state,
+                                struct drm_connector *connector)
+{
+       struct drm_connector_state *connector_state;
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *crtc_state;
+       struct drm_plane_state *plane_state;
+       struct drm_plane *plane;
+       int ret;
+
+       if (!(crtc = connector->state->crtc))
+               return 0;
+
+       connector_state = drm_atomic_get_connector_state(state, connector);
+       if (IS_ERR(connector_state))
+               return PTR_ERR(connector_state);
+
+       ret = drm_atomic_set_crtc_for_connector(connector_state, NULL);
+       if (ret)
+               return ret;
+
+       crtc_state = drm_atomic_get_crtc_state(state, crtc);
+       if (IS_ERR(crtc_state))
+               return PTR_ERR(crtc_state);
+
+       ret = drm_atomic_set_mode_for_crtc(crtc_state, NULL);
+       if (ret)
+               return ret;
+
+       crtc_state->active = false;
+
+       drm_for_each_plane_mask(plane, connector->dev, crtc_state->plane_mask) {
+               plane_state = drm_atomic_get_plane_state(state, plane);
+               if (IS_ERR(plane_state))
+                       return PTR_ERR(plane_state);
+
+               ret = drm_atomic_set_crtc_for_plane(plane_state, NULL);
+               if (ret)
+                       return ret;
+
+               drm_atomic_set_fb_for_plane(plane_state, NULL);
+       }
+
+       return 0;
+}
+
+static int
+nouveau_atomic_disable(struct drm_device *dev,
+                      struct drm_modeset_acquire_ctx *ctx)
+{
+       struct drm_atomic_state *state;
+       struct drm_connector *connector;
+       int ret;
+
+       state = drm_atomic_state_alloc(dev);
+       if (!state)
+               return -ENOMEM;
+
+       state->acquire_ctx = ctx;
+
+       drm_for_each_connector(connector, dev) {
+               ret = nouveau_atomic_disable_connector(state, connector);
+               if (ret)
+                       break;
+       }
+
+       if (ret == 0)
+               ret = drm_atomic_commit(state);
+       drm_atomic_state_put(state);
+       return ret;
+}
+
+static struct drm_atomic_state *
+nouveau_atomic_suspend(struct drm_device *dev)
+{
+       struct drm_modeset_acquire_ctx ctx;
+       struct drm_atomic_state *state;
+       int ret;
+
+       drm_modeset_acquire_init(&ctx, 0);
+
+retry:
+       ret = drm_modeset_lock_all_ctx(dev, &ctx);
+       if (ret < 0) {
+               state = ERR_PTR(ret);
+               goto unlock;
+       }
+
+       state = drm_atomic_helper_duplicate_state(dev, &ctx);
+       if (IS_ERR(state))
+               goto unlock;
+
+       ret = nouveau_atomic_disable(dev, &ctx);
+       if (ret < 0) {
+               drm_atomic_state_put(state);
+               state = ERR_PTR(ret);
+               goto unlock;
+       }
+
+unlock:
+       if (PTR_ERR(state) == -EDEADLK) {
+               drm_modeset_backoff(&ctx);
+               goto retry;
+       }
+
+       drm_modeset_drop_locks(&ctx);
+       drm_modeset_acquire_fini(&ctx);
+       return state;
+}
+
 int
 nouveau_display_suspend(struct drm_device *dev, bool runtime)
 {
+       struct nouveau_display *disp = nouveau_display(dev);
        struct drm_crtc *crtc;
 
+       if (dev->mode_config.funcs->atomic_commit) {
+               if (!runtime) {
+                       disp->suspend = nouveau_atomic_suspend(dev);
+                       if (IS_ERR(disp->suspend)) {
+                               int ret = PTR_ERR(disp->suspend);
+                               disp->suspend = NULL;
+                               return ret;
+                       }
+               }
+
+               nouveau_display_fini(dev, true);
+               return 0;
+       }
+
        nouveau_display_fini(dev, true);
 
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
@@ -605,10 +733,20 @@ nouveau_display_suspend(struct drm_device *dev, bool runtime)
 void
 nouveau_display_resume(struct drm_device *dev, bool runtime)
 {
+       struct nouveau_display *disp = nouveau_display(dev);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct drm_crtc *crtc;
        int ret, head;
 
+       if (dev->mode_config.funcs->atomic_commit) {
+               nouveau_display_init(dev);
+               if (disp->suspend) {
+                       drm_atomic_helper_resume(dev, disp->suspend);
+                       disp->suspend = NULL;
+               }
+               return;
+       }
+
        /* re-pin fb/cursors */
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
                struct nouveau_framebuffer *nouveau_fb;
index 3760f0e..77d6fe1 100644 (file)
@@ -53,6 +53,8 @@ struct nouveau_display {
        /* not really hue and saturation: */
        struct drm_property *vibrant_hue_property;
        struct drm_property *color_vibrance_property;
+
+       struct drm_atomic_state *suspend;
 };
 
 static inline struct nouveau_display *