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 340ca3ee130442b0af1962cc01346dbdf6b7d529..79b5f9d66a82819220c8a005047d25dffdef6a9d 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 3760f0e758fdc854079c32d5d9f325f70bb89890..77d6fe18d91c009ff87a3048b99ffd431ad70f1c 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 *