[RFCv3 11/14] drm: convert crtc to properties/state

Rob Clark robdclark at gmail.com
Wed Nov 20 12:48:10 PST 2013


Break the mutable state of a crtc out into a separate structure
and use atomic properties mechanism to set crtc attributes.  This
makes it easier to have some helpers for crtc->set_property()
and for checking for invalid params.  The idea is that individual
drivers can wrap the state struct in their own struct which adds
driver specific parameters, for easy build-up of state across
multiple set_property() calls and for easy atomic commit or roll-
back.

This also re-works the locking a bit.. maybe some of these changes
should be rejuggled into different patch.  But now atomic plane updates
grab current (and if necessary, incoming) crtc locks for their
synchronization.
---
 drivers/gpu/drm/ast/ast_mode.c             |   1 +
 drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
 drivers/gpu/drm/drm_atomic_helper.c        | 347 ++++++++++++++++-
 drivers/gpu/drm/drm_crtc.c                 | 580 +++++++++++++++++------------
 drivers/gpu/drm/drm_fb_cma_helper.c        |   9 +-
 drivers/gpu/drm/drm_fb_helper.c            |  12 +-
 drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   5 +-
 drivers/gpu/drm/exynos/exynos_drm_fbdev.c  |   4 +-
 drivers/gpu/drm/gma500/cdv_intel_display.c |   1 +
 drivers/gpu/drm/gma500/psb_drv.c           |   4 +-
 drivers/gpu/drm/gma500/psb_intel_display.c |   1 +
 drivers/gpu/drm/i915/intel_display.c       |  17 +-
 drivers/gpu/drm/i915/intel_fbdev.c         |   6 +-
 drivers/gpu/drm/mgag200/mgag200_mode.c     |   1 +
 drivers/gpu/drm/msm/mdp4/mdp4_crtc.c       |   5 +-
 drivers/gpu/drm/msm/msm_drv.c              |   7 +-
 drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
 drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
 drivers/gpu/drm/omapdrm/omap_crtc.c        |  17 +-
 drivers/gpu/drm/omapdrm/omap_drv.c         |   6 +-
 drivers/gpu/drm/qxl/qxl_display.c          |   2 +
 drivers/gpu/drm/radeon/radeon_display.c    |   2 +
 drivers/gpu/drm/rcar-du/rcar_du_crtc.c     |   2 +
 drivers/gpu/drm/shmobile/shmob_drm_crtc.c  |   2 +
 drivers/gpu/drm/tegra/fb.c                 |   7 +-
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c       |   1 +
 drivers/gpu/drm/udl/udl_modeset.c          |   2 +
 drivers/gpu/drm/vmwgfx/vmwgfx_kms.c        |  12 +-
 drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c        |   1 +
 drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c       |   1 +
 include/drm/drm_atomic_helper.h            |  41 ++
 include/drm/drm_crtc.h                     |  87 ++++-
 include/drm/drm_fb_helper.h                |   3 +-
 include/uapi/drm/drm_mode.h                |   2 +
 34 files changed, 864 insertions(+), 327 deletions(-)

diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
index 7fc9f72..13f6943 100644
--- a/drivers/gpu/drm/ast/ast_mode.c
+++ b/drivers/gpu/drm/ast/ast_mode.c
@@ -619,6 +619,7 @@ static const struct drm_crtc_funcs ast_crtc_funcs = {
 	.cursor_move = ast_cursor_move,
 	.reset = ast_crtc_reset,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.gamma_set = ast_crtc_gamma_set,
 	.destroy = ast_crtc_destroy,
 };
diff --git a/drivers/gpu/drm/cirrus/cirrus_mode.c b/drivers/gpu/drm/cirrus/cirrus_mode.c
index adabc3d..9e0b713 100644
--- a/drivers/gpu/drm/cirrus/cirrus_mode.c
+++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
@@ -363,6 +363,7 @@ static void cirrus_crtc_destroy(struct drm_crtc *crtc)
 static const struct drm_crtc_funcs cirrus_crtc_funcs = {
 	.gamma_set = cirrus_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = cirrus_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 0618113..aaab456 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -40,17 +40,22 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
 {
 	struct drm_atomic_helper_state *state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs  = dev->mode_config.num_crtc;
 	int sz;
 	void *ptr;
 
 	sz = sizeof(*state);
 	sz += (sizeof(state->planes) + sizeof(state->pstates)) * nplanes;
+	sz += (sizeof(state->crtcs) + sizeof(state->cstates)) * ncrtcs;
 
 	ptr = kzalloc(sz, GFP_KERNEL);
 
 	state = ptr;
 	ptr = &state[1];
 
+	ww_acquire_init(&state->ww_ctx, &crtc_ww_class);
+	INIT_LIST_HEAD(&state->locked_crtcs);
+
 	kref_init(&state->refcount);
 	state->dev = dev;
 	state->flags = flags;
@@ -61,6 +66,12 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
 	state->pstates = ptr;
 	ptr = &state->pstates[nplanes];
 
+	state->crtcs = ptr;
+	ptr = &state->crtcs[ncrtcs];
+
+	state->cstates = ptr;
+	ptr = &state->cstates[ncrtcs];
+
 	return state;
 }
 EXPORT_SYMBOL(drm_atomic_helper_begin);
@@ -79,7 +90,16 @@ int drm_atomic_helper_set_event(struct drm_device *dev,
 		void *state, struct drm_mode_object *obj,
 		struct drm_pending_vblank_event *event)
 {
-	return -EINVAL;  /* for now */
+	switch (obj->type) {
+	case DRM_MODE_OBJECT_CRTC: {
+		struct drm_crtc_state *cstate =
+			drm_atomic_get_crtc_state(obj_to_crtc(obj), state);
+		cstate->event = event;
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
 }
 EXPORT_SYMBOL(drm_atomic_helper_set_event);
 
@@ -98,6 +118,7 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
 {
 	struct drm_atomic_helper_state *a = state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i, ret = 0;
 
 	for (i = 0; i < nplanes; i++) {
@@ -108,10 +129,55 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		if (a->crtcs[i]) {
+			ret = drm_atomic_check_crtc_state(a->crtcs[i], a->cstates[i]);
+			if (ret)
+				break;
+		}
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_helper_check);
 
+static void drop_locks(struct drm_atomic_helper_state *a)
+{
+	ww_acquire_done(&a->ww_ctx);
+	while (!list_empty(&a->locked_crtcs)) {
+		struct drm_crtc *crtc;
+
+		crtc = list_first_entry(&a->locked_crtcs,
+				struct drm_crtc, lock_head);
+
+		drm_modeset_unlock_crtc(crtc);
+	}
+	ww_acquire_fini(&a->ww_ctx);
+}
+
+static int grab_locks(struct drm_atomic_helper_state *a)
+{
+	int nplanes = a->dev->mode_config.num_plane;
+	int ncrtcs = a->dev->mode_config.num_crtc;
+	int i;
+
+	for (i = 0; i < nplanes; i++) {
+		if (a->planes[i]) {
+			/* both incoming and outgoing crtc: */
+			if (a->planes[i]->state->crtc)
+				drm_modeset_lock_crtc(a->planes[i]->state->crtc, a);
+			if (a->pstates[i]->crtc)
+				drm_modeset_lock_crtc(a->pstates[i]->crtc, a);
+		}
+	}
+
+	for (i = 0; i < ncrtcs; i++)
+		if (a->crtcs[i])
+			drm_modeset_lock_crtc(a->crtcs[i], a);
+
+	return 0;
+}
+
 /**
  * drm_atomic_helper_commit - commit state
  * @dev: DRM device
@@ -127,8 +193,18 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
 {
 	struct drm_atomic_helper_state *a = state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i, ret = 0;
 
+	/* re-acquire dropped locks, in case of NONBLOCK */
+	mutex_lock(&a->dev->struct_mutex);
+	if (a->locks_dropped) {
+		ww_acquire_init(&a->ww_ctx, &crtc_ww_class);
+		grab_locks(a);
+		a->locks_dropped = false;
+	}
+	mutex_unlock(&a->dev->struct_mutex);
+
 	for (i = 0; i < nplanes; i++) {
 		if (a->planes[i]) {
 			ret = drm_atomic_commit_plane_state(a->planes[i], a->pstates[i]);
@@ -137,6 +213,14 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		if (a->crtcs[i]) {
+			ret = drm_atomic_commit_crtc_state(a->crtcs[i], a->cstates[i]);
+			if (ret)
+				break;
+		}
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit);
@@ -150,26 +234,51 @@ EXPORT_SYMBOL(drm_atomic_helper_commit);
  */
 void drm_atomic_helper_end(struct drm_device *dev, void *state)
 {
+	struct drm_atomic_helper_state *a = state;
+
+	/* yes, we need to synchronize our locks!  So we don't race with
+	 * driver calling back drm_atomic_helper_commit() asynchronously
+	 * in NONBLOCK case
+	 */
+	mutex_lock(&a->dev->struct_mutex);
+	drop_locks(a);
+	a->locks_dropped = true;
+	mutex_unlock(&a->dev->struct_mutex);
+
 	drm_atomic_helper_state_unreference(state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_end);
 
 void _drm_atomic_helper_state_free(struct kref *kref)
 {
-	struct drm_atomic_helper_state *state =
+	struct drm_atomic_helper_state *a =
 		container_of(kref, struct drm_atomic_helper_state, refcount);
-	struct drm_device *dev = state->dev;
+	struct drm_device *dev = a->dev;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i;
 
 	for (i = 0; i < nplanes; i++) {
-		if (state->pstates[i]) {
-			state->planes[i]->state->state = NULL;
-			kfree(state->pstates[i]);
+		if (a->pstates[i]) {
+			a->planes[i]->state->state = NULL;
+			kfree(a->pstates[i]);
 		}
 	}
 
-	kfree(state);
+	for (i = 0; i < ncrtcs; i++) {
+		if (a->cstates[i]) {
+			a->crtcs[i]->state->state = NULL;
+			kfree(a->cstates[i]);
+		}
+	}
+
+	/* drop any locks we may have re-acquired (ie. NONBLOCK case,
+	 * if the driver defers commit to a worker)
+	 */
+	if (!a->locks_dropped)
+		drop_locks(a);
+
+	kfree(a);
 }
 EXPORT_SYMBOL(_drm_atomic_helper_state_free);
 
@@ -195,13 +304,21 @@ static struct drm_plane_state *
 drm_atomic_helper_get_plane_state(struct drm_plane *plane, void *state)
 {
 	struct drm_atomic_helper_state *a = state;
-	struct drm_plane_state *pstate = a->pstates[plane->id];
+	struct drm_plane_state *pstate;
+
+	/* grab lock of current crtc: */
+	if (plane->state->crtc)
+		drm_modeset_lock_crtc(plane->state->crtc, state);
+
+	pstate = a->pstates[plane->id];
+
 	if (!pstate) {
 		pstate = kzalloc(sizeof(*pstate), GFP_KERNEL);
 		drm_atomic_helper_init_plane_state(plane, pstate, state);
 		a->planes[plane->id] = plane;
 		a->pstates[plane->id] = pstate;
 	}
+
 	return pstate;
 }
 
@@ -216,15 +333,9 @@ static int
 drm_atomic_helper_commit_plane_state(struct drm_plane *plane,
 		struct drm_plane_state *pstate)
 {
-	struct drm_device *dev = plane->dev;
 	struct drm_framebuffer *old_fb = NULL, *fb = NULL;
 	int ret = 0;
 
-	/* probably more fine grain locking would be ok of old crtc
-	 * and new crtc were same..
-	 */
-	drm_modeset_lock_all(dev);
-
 	fb = pstate->fb;
 
 	if (pstate->crtc && fb) {
@@ -250,8 +361,210 @@ drm_atomic_helper_commit_plane_state(struct drm_plane *plane,
 		swap_plane_state(plane, pstate->state);
 	}
 
-	drm_modeset_unlock_all(dev);
+	if (fb)
+		drm_framebuffer_unreference(fb);
+	if (old_fb)
+		drm_framebuffer_unreference(old_fb);
+
+	return ret;
+}
+
+int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
+		struct drm_property *property, uint64_t val, void *blob_data)
+{
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
+}
+EXPORT_SYMBOL(drm_atomic_helper_crtc_set_property);
+
+void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate, void *state)
+{
+	/* snapshot current state: */
+	*cstate = *crtc->state;
+	cstate->state = state;
+
+	/* this should never happen.. but make sure! */
+	WARN_ON(cstate->event);
+	cstate->event = NULL;
+}
+EXPORT_SYMBOL(drm_atomic_helper_init_crtc_state);
+
+static struct drm_crtc_state *
+drm_atomic_helper_get_crtc_state(struct drm_crtc *crtc, void *state)
+{
+	struct drm_atomic_helper_state *a = state;
+	struct drm_crtc_state *cstate;
+
+	drm_modeset_lock_crtc(crtc, state);
+
+	cstate = a->cstates[crtc->id];
+
+	if (!cstate) {
+		cstate = kmalloc(sizeof(*cstate), GFP_KERNEL);
+		if (!cstate)
+			return NULL;
+		drm_atomic_helper_init_crtc_state(crtc, cstate, state);
+		a->crtcs[crtc->id] = crtc;
+		a->cstates[crtc->id] = cstate;
+	}
+	return cstate;
+}
+
+static void
+swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_helper_state *a)
+{
+	struct drm_crtc_state *cstate = a->cstates[crtc->id];
+	struct drm_device *dev = crtc->dev;
+	struct drm_pending_vblank_event *event = cstate->event;
+	if (event) {
+		/* hrm, need to sort out a better way to send events for
+		 * other-than-pageflip.. but modeset is not async, so:
+		 */
+		unsigned long flags;
+		spin_lock_irqsave(&dev->event_lock, flags);
+		drm_send_vblank_event(dev, crtc->id, event);
+		cstate->event = NULL;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+	swap(crtc->state, a->cstates[crtc->id]);
+	crtc->base.propvals = &crtc->state->propvals;
+}
+
+static struct drm_connector **get_connector_set(struct drm_device *dev,
+		uint32_t *connector_ids, uint32_t num_connector_ids)
+{
+	struct drm_connector **connector_set = NULL;
+	int i;
+
+	connector_set = kmalloc(num_connector_ids *
+			sizeof(struct drm_connector *),
+			GFP_KERNEL);
+	if (!connector_set)
+		return NULL;
+
+	for (i = 0; i < num_connector_ids; i++)
+		connector_set[i] = drm_connector_find(dev, connector_ids[i]);
+
+	return connector_set;
+}
+
+static struct drm_display_mode *get_mode(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_display_mode *mode = NULL;
+	if (cstate->mode_valid) {
+		struct drm_device *dev = crtc->dev;
+		int ret;
+
+		mode = drm_mode_create(dev);
+		if (!mode)
+			return ERR_PTR(-ENOMEM);
+
+		ret = drm_crtc_convert_umode(mode, &cstate->mode);
+		if (ret) {
+			DRM_DEBUG_KMS("Invalid mode\n");
+			drm_mode_destroy(dev, mode);
+			return ERR_PTR(ret);
+		}
+
+		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+	}
+	return mode;
+}
 
+static int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_framebuffer *fb = cstate->fb;
+	struct drm_connector **connector_set = get_connector_set(crtc->dev,
+			cstate->connector_ids, cstate->num_connector_ids);
+	struct drm_display_mode *mode = get_mode(crtc, cstate);
+	struct drm_mode_set set = {
+			.crtc = crtc,
+			.x = cstate->x,
+			.y = cstate->y,
+			.mode = mode,
+			.num_connectors = cstate->num_connector_ids,
+			.connectors = connector_set,
+			.fb = fb,
+	};
+	int ret;
+
+	if (IS_ERR(mode)) {
+		ret = PTR_ERR(mode);
+		return ret;
+	}
+
+	ret = drm_mode_set_config_internal(&set);
+	if (!ret)
+		swap_crtc_state(crtc, cstate->state);
+
+	if (fb)
+		drm_framebuffer_unreference(fb);
+
+	kfree(connector_set);
+	if (mode)
+		drm_mode_destroy(dev, mode);
+	return ret;
+}
+
+static int
+drm_atomic_helper_commit_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	struct drm_framebuffer *old_fb = NULL, *fb = NULL;
+	struct drm_atomic_helper_state *a = cstate->state;
+	int ret = -EINVAL;
+
+	if (cstate->set_config) {
+		cstate->set_config = false;
+		return set_config(crtc, cstate);
+	}
+
+	if (cstate->fb) {
+		/* pageflip */
+
+		if (crtc->fb == NULL) {
+			/* The framebuffer is currently unbound, presumably
+			 * due to a hotplug event, that userspace has not
+			 * yet discovered.
+			 */
+			ret = -EBUSY;
+			goto out;
+		}
+
+		if (crtc->funcs->page_flip == NULL)
+			goto out;
+
+		old_fb = crtc->fb;
+		fb = cstate->fb;
+
+		ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
+		if (ret) {
+			/* Keep the old fb, don't unref it. */
+			old_fb = NULL;
+		} else {
+			cstate->event = NULL;
+			swap_crtc_state(crtc, cstate->state);
+			/* Unref only the old framebuffer. */
+			fb = NULL;
+		}
+	} else {
+		/* disable */
+		struct drm_mode_set set = {
+				.crtc = crtc,
+				.fb = NULL,
+		};
+
+		old_fb = crtc->state->fb;
+		ret = drm_mode_set_config_internal(&set);
+		if (!ret) {
+			swap_crtc_state(crtc, cstate->state);
+		}
+	}
+
+out:
 	if (fb)
 		drm_framebuffer_unreference(fb);
 	if (old_fb)
@@ -264,5 +577,9 @@ const struct drm_atomic_helper_funcs drm_atomic_helper_funcs = {
 		.get_plane_state    = drm_atomic_helper_get_plane_state,
 		.check_plane_state  = drm_plane_check_state,
 		.commit_plane_state = drm_atomic_helper_commit_plane_state,
+
+		.get_crtc_state     = drm_atomic_helper_get_crtc_state,
+		.check_crtc_state   = drm_crtc_check_state,
+		.commit_crtc_state  = drm_atomic_helper_commit_crtc_state,
 };
 EXPORT_SYMBOL(drm_atomic_helper_funcs);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index b68984b..4b40a39 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -37,6 +37,53 @@
 #include <drm/drm_crtc.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_fourcc.h>
+#include <drm/drm_atomic_helper.h>
+
+/**
+ * drm_modeset_lock_crtc - take crtc modeset lock
+ * @crtc: crtc to lock
+ * @state: atomic state
+ *
+ * If state is not NULL, then then it's aquire context is used
+ * and the crtc does not need to be explicitly unlocked, it
+ * will be automatically unlocked when the atomic update is
+ * complete (ioctl returns)
+ */
+int drm_modeset_lock_crtc(struct drm_crtc *crtc, void *state)
+{
+	// ugg, this makes atomic_helper mandatory..  not really
+	// sure yet whether I should care, or just simplify things
+	// and require that drivers use or extend atomic_helper:
+	struct drm_atomic_helper_state *a = state;
+	struct ww_acquire_ctx *ww_ctx = NULL;
+	int ret;
+
+	if (a) {
+		if (a->flags & DRM_MODE_ATOMIC_NOLOCK)
+			return 0;
+		ww_ctx = &a->ww_ctx;
+	}
+
+	ret = ww_mutex_lock(&crtc->mutex, ww_ctx);
+	if (a && !ret) {
+		WARN_ON(!list_empty(&crtc->lock_head));
+		list_add(&crtc->lock_head, &a->locked_crtcs);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_modeset_lock_crtc);
+
+/**
+ * drm_modeset_unlock_crtc - drop crtc modeset lock
+ * @crtc: crtc to unlock
+ */
+void drm_modeset_unlock_crtc(struct drm_crtc *crtc)
+{
+	list_del_init(&crtc->lock_head);
+	ww_mutex_unlock(&crtc->mutex);
+}
+EXPORT_SYMBOL(drm_modeset_unlock_crtc);
 
 /**
  * drm_modeset_lock_all - take all modeset locks
@@ -576,8 +623,6 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
 	struct drm_device *dev = fb->dev;
 	struct drm_crtc *crtc;
 	struct drm_plane *plane;
-	struct drm_mode_set set;
-	int ret;
 
 	WARN_ON(!list_empty(&fb->filp_head));
 
@@ -597,6 +642,7 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
 	 * in this manner.
 	 */
 	if (atomic_read(&fb->refcount.refcount) > 1) {
+		struct drm_mode_config *config = &fb->dev->mode_config;
 		void *state;
 
 		state = dev->driver->atomic_begin(dev, 0);
@@ -605,22 +651,12 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
 			return;
 		}
 
-		/* TODO once CRTC is converted to state/properties, we can push the
-		 * locking down into drm_atomic_helper_commit(), since that is where
-		 * the actual changes take place..
-		 */
-		drm_modeset_lock_all(dev);
-
 		/* remove from any CRTC */
 		list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 			if (crtc->fb == fb) {
 				/* should turn off the crtc */
-				memset(&set, 0, sizeof(struct drm_mode_set));
-				set.crtc = crtc;
-				set.fb = NULL;
-				ret = drm_mode_set_config_internal(&set);
-				if (ret)
-					DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
+				drm_mode_crtc_set_obj_prop(crtc, state,
+					config->prop_fb_id, 0, NULL);
 			}
 		}
 
@@ -636,15 +672,13 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
 			dev->driver->atomic_commit(dev, state);
 
 		dev->driver->atomic_end(dev, state);
-
-		drm_modeset_unlock_all(dev);
 	}
 
 	drm_framebuffer_unreference(fb);
 }
 EXPORT_SYMBOL(drm_framebuffer_remove);
 
-static DEFINE_WW_CLASS(crtc_ww_class);
+DEFINE_WW_CLASS(crtc_ww_class);
 
 /**
  * drm_crtc_init - Initialise a new CRTC object
@@ -660,26 +694,38 @@ static DEFINE_WW_CLASS(crtc_ww_class);
 int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
 		   const struct drm_crtc_funcs *funcs)
 {
+	struct drm_mode_config *config = &dev->mode_config;
 	int ret;
 
+	if (!crtc->state)
+		crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
+
 	crtc->dev = dev;
 	crtc->funcs = funcs;
-	crtc->invert_dimensions = false;
+	crtc->state->invert_dimensions = false;
 
 	drm_modeset_lock_all(dev);
 	ww_mutex_init(&crtc->mutex, &crtc_ww_class);
 	mutex_lock_nest_lock(&crtc->mutex.base, &dev->mode_config.mutex);
 
+	INIT_LIST_HEAD(&crtc->lock_head);
+
 	ret = drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);
 	if (ret)
 		goto out;
 
 	crtc->base.properties = &crtc->properties;
-	crtc->base.propvals = &crtc->propvals;
+	crtc->base.propvals = &crtc->state->propvals;
 
 	list_add_tail(&crtc->head, &dev->mode_config.crtc_list);
 	dev->mode_config.num_crtc++;
 
+	drm_object_attach_property(&crtc->base, config->prop_mode, 0);
+	drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
+	drm_object_attach_property(&crtc->base, config->prop_fb_id, 0);
+	drm_object_attach_property(&crtc->base, config->prop_src_x, 0);
+	drm_object_attach_property(&crtc->base, config->prop_src_y, 0);
+
  out:
 	drm_modeset_unlock_all(dev);
 
@@ -702,12 +748,162 @@ void drm_crtc_cleanup(struct drm_crtc *crtc)
 	kfree(crtc->gamma_store);
 	crtc->gamma_store = NULL;
 
+	WARN_ON(!list_empty(&crtc->lock_head));
+
 	drm_mode_object_put(dev, &crtc->base);
 	list_del(&crtc->head);
 	dev->mode_config.num_crtc--;
 }
 EXPORT_SYMBOL(drm_crtc_cleanup);
 
+// XXX de-duplicate from drm_atomic_helper.c:
+// probably we want to stash the drm_display_mode as a ptr (transient data) on the state
+// so we only convert it once.. maybe in set_property()?
+static struct drm_display_mode *get_mode(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_display_mode *mode = NULL;
+	if (cstate->mode_valid) {
+		struct drm_device *dev = crtc->dev;
+		int ret;
+
+		mode = drm_mode_create(dev);
+		if (!mode)
+			return ERR_PTR(-ENOMEM);
+
+		ret = drm_crtc_convert_umode(mode, &cstate->mode);
+		if (ret) {
+			DRM_DEBUG_KMS("Invalid mode\n");
+			drm_mode_destroy(dev, mode);
+			return ERR_PTR(ret);
+		}
+
+		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+	}
+	return mode;
+}
+
+
+int drm_crtc_check_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	int hdisplay, vdisplay;
+	struct drm_display_mode *mode = get_mode(crtc, state);
+
+	if (IS_ERR(mode))
+		return PTR_ERR(mode);
+
+	/* disabling the crtc is allowed: */
+	if (!(fb && state->mode_valid))
+		return 0;
+
+	hdisplay = state->mode.hdisplay;
+	vdisplay = state->mode.vdisplay;
+
+	if (mode && drm_mode_is_stereo(mode)) {
+		struct drm_display_mode adjusted = *mode;
+
+		drm_mode_set_crtcinfo(&adjusted, CRTC_STEREO_DOUBLE);
+		hdisplay = adjusted.crtc_hdisplay;
+		vdisplay = adjusted.crtc_vdisplay;
+	}
+
+	if (state->invert_dimensions)
+		swap(hdisplay, vdisplay);
+
+	/* For some reason crtc x/y offsets are signed internally. */
+	if (state->x > INT_MAX || state->y > INT_MAX)
+		return -ERANGE;
+
+	if (hdisplay > fb->width ||
+	    vdisplay > fb->height ||
+	    state->x > fb->width - hdisplay ||
+	    state->y > fb->height - vdisplay) {
+		DRM_DEBUG_KMS("Invalid fb size %ux%u for CRTC viewport %ux%u+%d+%d%s.\n",
+			      fb->width, fb->height, hdisplay, vdisplay,
+			      state->x, state->y,
+			      state->invert_dimensions ? " (inverted)" : "");
+		return -ENOSPC;
+	}
+
+	if (crtc->enabled && !state->set_config) {
+		if (crtc->state->fb->pixel_format != fb->pixel_format) {
+			DRM_DEBUG_KMS("Page flip is not allowed to "
+					"change frame buffer format.\n");
+			return -EINVAL;
+		}
+	}
+
+	if (state->num_connector_ids == 0) {
+		DRM_DEBUG_KMS("Count connectors is 0 but mode set\n");
+		return -EINVAL;
+	}
+
+	if (mode)
+		drm_mode_destroy(crtc->dev, mode);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_check_state);
+
+void drm_crtc_commit_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	crtc->state = state;
+	crtc->base.propvals = &state->propvals;
+}
+EXPORT_SYMBOL(drm_crtc_commit_state);
+
+int drm_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_crtc_state *state,
+		struct drm_property *property,
+		uint64_t value, void *blob_data)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_mode_config *config = &dev->mode_config;
+
+	drm_object_property_set_value(&crtc->base,
+			&state->propvals, property, value, blob_data);
+
+	if (property == config->prop_mode) {
+		if (!blob_data) {
+			memset(&state->mode, 0, sizeof(state->mode));
+			state->mode_valid = false;
+		} else {
+			/* check size: */
+			if (value < sizeof(struct drm_mode_modeinfo))
+				return -EINVAL;
+			state->mode = *(struct drm_mode_modeinfo *)blob_data;
+			state->mode_valid = true;
+		}
+		state->set_config = true;
+	} else if (property == config->prop_connector_ids) {
+		state->num_connector_ids = value / sizeof(state->connector_ids[0]);
+		state->connector_ids = blob_data;
+		state->set_config = true;
+	} else if (property == config->prop_fb_id) {
+		state->new_fb = true;
+		state->fb = drm_framebuffer_lookup(dev, value);
+	} else if (property == config->prop_src_x) {
+		int x = *(int *)&value;
+		if (state->x != x) {
+			state->x = x;
+			state->set_config = true;
+		}
+	} else if (property == config->prop_src_y) {
+		int y = *(int *)&value;
+		if (state->y != y) {
+			state->y = y;
+			state->set_config = true;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_set_property);
+
 /**
  * drm_mode_probed_add - add a mode to a connector's probed mode list
  * @connector: connector the new mode
@@ -1093,7 +1289,14 @@ int drm_plane_set_property(struct drm_plane *plane,
 		state->fb = drm_framebuffer_lookup(dev, value);
 	} else if (property == config->prop_crtc_id) {
 		struct drm_mode_object *obj = drm_property_get_obj(property, value);
-		state->crtc = obj ? obj_to_crtc(obj) : NULL;
+		struct drm_crtc *crtc = obj ? obj_to_crtc(obj) : NULL;
+		/* take the lock of the incoming crtc as well, moving
+		 * plane between crtcs is synchronized on both incoming
+		 * and outgoing crtc.
+		 */
+		if (crtc)
+			drm_modeset_lock_crtc(crtc, state->state);
+		state->crtc = crtc;
 	} else if (property == config->prop_crtc_x) {
 		state->crtc_x = U642I64(value);
 	} else if (property == config->prop_crtc_y) {
@@ -1269,6 +1472,16 @@ static int drm_mode_create_standard_connector_properties(struct drm_device *dev)
 		return -ENOMEM;
 	dev->mode_config.prop_crtc_id = prop;
 
+	prop = drm_property_create(dev, DRM_MODE_PROP_BLOB, "CONNECTOR_IDS", 0);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_connector_ids = prop;
+
+	prop = drm_property_create(dev, DRM_MODE_PROP_BLOB, "MODE", 0);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.prop_mode = prop;
+
 	return 0;
 }
 
@@ -1533,7 +1746,7 @@ static void drm_crtc_convert_to_umode(struct drm_mode_modeinfo *out,
  * RETURNS:
  * Zero on success, errno on failure.
  */
-static int drm_crtc_convert_umode(struct drm_display_mode *out,
+int drm_crtc_convert_umode(struct drm_display_mode *out,
 				  const struct drm_mode_modeinfo *in)
 {
 	if (in->clock > INT_MAX || in->vrefresh > INT_MAX)
@@ -1782,11 +1995,11 @@ int drm_mode_getcrtc(struct drm_device *dev,
 	}
 	crtc = obj_to_crtc(obj);
 
-	crtc_resp->x = crtc->x;
-	crtc_resp->y = crtc->y;
+	crtc_resp->x = crtc->state->x;
+	crtc_resp->y = crtc->state->y;
 	crtc_resp->gamma_size = crtc->gamma_size;
-	if (crtc->fb)
-		crtc_resp->fb_id = crtc->fb->base.id;
+	if (crtc->state->fb)
+		crtc_resp->fb_id = crtc->state->fb->base.id;
 	else
 		crtc_resp->fb_id = 0;
 
@@ -2213,45 +2426,6 @@ int drm_mode_set_config_internal(struct drm_mode_set *set)
 }
 EXPORT_SYMBOL(drm_mode_set_config_internal);
 
-/*
- * Checks that the framebuffer is big enough for the CRTC viewport
- * (x, y, hdisplay, vdisplay)
- */
-static int drm_crtc_check_viewport(const struct drm_crtc *crtc,
-				   int x, int y,
-				   const struct drm_display_mode *mode,
-				   const struct drm_framebuffer *fb)
-
-{
-	int hdisplay, vdisplay;
-
-	hdisplay = mode->hdisplay;
-	vdisplay = mode->vdisplay;
-
-	if (drm_mode_is_stereo(mode)) {
-		struct drm_display_mode adjusted = *mode;
-
-		drm_mode_set_crtcinfo(&adjusted, CRTC_STEREO_DOUBLE);
-		hdisplay = adjusted.crtc_hdisplay;
-		vdisplay = adjusted.crtc_vdisplay;
-	}
-
-	if (crtc->invert_dimensions)
-		swap(hdisplay, vdisplay);
-
-	if (hdisplay > fb->width ||
-	    vdisplay > fb->height ||
-	    x > fb->width - hdisplay ||
-	    y > fb->height - vdisplay) {
-		DRM_DEBUG_KMS("Invalid fb size %ux%u for CRTC viewport %ux%u+%d+%d%s.\n",
-			      fb->width, fb->height, hdisplay, vdisplay, x, y,
-			      crtc->invert_dimensions ? " (inverted)" : "");
-		return -ENOSPC;
-	}
-
-	return 0;
-}
-
 /**
  * drm_mode_setcrtc - set CRTC configuration
  * @dev: drm device for the ioctl
@@ -2272,22 +2446,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 	struct drm_mode_crtc *crtc_req = data;
 	struct drm_mode_object *obj;
 	struct drm_crtc *crtc;
-	struct drm_connector **connector_set = NULL, *connector;
-	struct drm_framebuffer *fb = NULL;
-	struct drm_display_mode *mode = NULL;
-	struct drm_mode_set set;
-	uint32_t __user *set_connectors_ptr;
+	uint32_t fb_id = -1;
+	uint32_t *connector_ids = NULL;
+	void *state = NULL;
 	int ret;
 	int i;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return -EINVAL;
 
-	/* For some reason crtc x/y offsets are signed internally. */
-	if (crtc_req->x > INT_MAX || crtc_req->y > INT_MAX)
-		return -ERANGE;
-
-	drm_modeset_lock_all(dev);
 	obj = drm_mode_object_find(dev, crtc_req->crtc_id,
 				   DRM_MODE_OBJECT_CRTC);
 	if (!obj) {
@@ -2307,55 +2474,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 				ret = -EINVAL;
 				goto out;
 			}
-			fb = crtc->fb;
-			/* Make refcounting symmetric with the lookup path. */
-			drm_framebuffer_reference(fb);
+			fb_id = crtc->base.id;
 		} else {
-			fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
-			if (!fb) {
-				DRM_DEBUG_KMS("Unknown FB ID%d\n",
-						crtc_req->fb_id);
-				ret = -ENOENT;
-				goto out;
-			}
-		}
-
-		mode = drm_mode_create(dev);
-		if (!mode) {
-			ret = -ENOMEM;
-			goto out;
+			fb_id = crtc_req->fb_id;
 		}
-
-		ret = drm_crtc_convert_umode(mode, &crtc_req->mode);
-		if (ret) {
-			DRM_DEBUG_KMS("Invalid mode\n");
-			goto out;
-		}
-
-		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
-
-		ret = drm_crtc_check_viewport(crtc, crtc_req->x, crtc_req->y,
-					      mode, fb);
-		if (ret)
-			goto out;
-
-	}
-
-	if (crtc_req->count_connectors == 0 && mode) {
-		DRM_DEBUG_KMS("Count connectors is 0 but mode set\n");
-		ret = -EINVAL;
-		goto out;
-	}
-
-	if (crtc_req->count_connectors > 0 && (!mode || !fb)) {
-		DRM_DEBUG_KMS("Count connectors is %d but no mode or fb set\n",
-			  crtc_req->count_connectors);
-		ret = -EINVAL;
-		goto out;
 	}
 
 	if (crtc_req->count_connectors > 0) {
-		u32 out_id;
+		uint32_t __user *set_connectors_ptr =
+				(uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
 
 		/* Avoid unbounded kernel memory allocation */
 		if (crtc_req->count_connectors > config->num_connector) {
@@ -2363,54 +2490,52 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
 			goto out;
 		}
 
-		connector_set = kmalloc(crtc_req->count_connectors *
-					sizeof(struct drm_connector *),
+		connector_ids = kmalloc(crtc_req->count_connectors *
+					sizeof(connector_ids[0]),
 					GFP_KERNEL);
-		if (!connector_set) {
+		if (!connector_ids) {
 			ret = -ENOMEM;
 			goto out;
 		}
 
 		for (i = 0; i < crtc_req->count_connectors; i++) {
-			set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
+			u32 out_id;
+
 			if (get_user(out_id, &set_connectors_ptr[i])) {
 				ret = -EFAULT;
 				goto out;
 			}
-
-			obj = drm_mode_object_find(dev, out_id,
-						   DRM_MODE_OBJECT_CONNECTOR);
-			if (!obj) {
-				DRM_DEBUG_KMS("Connector id %d unknown\n",
-						out_id);
-				ret = -ENOENT;
-				goto out;
-			}
-			connector = obj_to_connector(obj);
-			DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
-					connector->base.id,
-					drm_get_connector_name(connector));
-
-			connector_set[i] = connector;
+			connector_ids[i] = out_id;
 		}
 	}
 
-	set.crtc = crtc;
-	set.x = crtc_req->x;
-	set.y = crtc_req->y;
-	set.mode = mode;
-	set.connectors = connector_set;
-	set.num_connectors = crtc_req->count_connectors;
-	set.fb = fb;
-	ret = drm_mode_set_config_internal(&set);
+	state = dev->driver->atomic_begin(dev, 0);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
 
-out:
-	if (fb)
-		drm_framebuffer_unreference(fb);
+	ret =
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_connector_ids,
+			crtc_req->count_connectors * sizeof(connector_ids[0]),
+			connector_ids) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_fb_id, fb_id, NULL) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_src_x, crtc_req->x, NULL) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_src_y, crtc_req->y, NULL) ||
+		dev->driver->atomic_check(dev, state);
+	if (ret)
+		goto out;
 
-	kfree(connector_set);
-	drm_mode_destroy(dev, mode);
-	drm_modeset_unlock_all(dev);
+	ret = dev->driver->atomic_commit(dev, state);
+
+out:
+	if (state)
+		dev->driver->atomic_end(dev, state);
+	kfree(connector_ids);
 	return ret;
 }
 
@@ -2435,7 +2560,7 @@ static int drm_mode_cursor_common(struct drm_device *dev,
 	}
 	crtc = obj_to_crtc(obj);
 
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 	if (req->flags & DRM_MODE_CURSOR_BO) {
 		if (!crtc->funcs->cursor_set && !crtc->funcs->cursor_set2) {
 			ret = -ENXIO;
@@ -2459,7 +2584,7 @@ static int drm_mode_cursor_common(struct drm_device *dev,
 		}
 	}
 out:
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 
 	return ret;
 
@@ -3519,9 +3644,6 @@ int drm_mode_crtc_set_obj_prop(struct drm_crtc *crtc,
 	if (crtc->funcs->set_property)
 		ret = crtc->funcs->set_property(crtc, state, property,
 				value, blob_data);
-	if (!ret)
-		drm_object_property_set_value(&crtc->base, &crtc->propvals,
-				property, value, NULL);
 
 	return ret;
 }
@@ -3847,15 +3969,60 @@ out:
 	return ret;
 }
 
+static struct drm_pending_vblank_event *create_vblank_event(
+		struct drm_device *dev, struct drm_file *file_priv, uint64_t user_data)
+{
+	struct drm_pending_vblank_event *e = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (file_priv->event_space < sizeof e->event) {
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+		goto out;
+	}
+	file_priv->event_space -= sizeof e->event;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	e = kzalloc(sizeof *e, GFP_KERNEL);
+	if (e == NULL) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		file_priv->event_space += sizeof e->event;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+		goto out;
+	}
+
+	e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
+	e->event.base.length = sizeof e->event;
+	e->event.user_data = user_data;
+	e->base.event = &e->event.base;
+	e->base.file_priv = file_priv;
+	e->base.destroy =
+		(void (*) (struct drm_pending_event *)) kfree;
+
+out:
+	return e;
+}
+
+static void destroy_vblank_event(struct drm_device *dev,
+		struct drm_file *file_priv, struct drm_pending_vblank_event *e)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	file_priv->event_space += sizeof e->event;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+	kfree(e);
+}
+
 int drm_mode_page_flip_ioctl(struct drm_device *dev,
 			     void *data, struct drm_file *file_priv)
 {
 	struct drm_mode_crtc_page_flip *page_flip = data;
+	struct drm_mode_config *config = &dev->mode_config;
 	struct drm_mode_object *obj;
 	struct drm_crtc *crtc;
-	struct drm_framebuffer *fb = NULL, *old_fb = NULL;
 	struct drm_pending_vblank_event *e = NULL;
-	unsigned long flags;
+	void *state;
 	int ret = -EINVAL;
 
 	if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
@@ -3870,92 +4037,37 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
 		return -ENOENT;
 	crtc = obj_to_crtc(obj);
 
-	ww_mutex_lock(&crtc->mutex, NULL);
-	if (crtc->fb == NULL) {
-		/* The framebuffer is currently unbound, presumably
-		 * due to a hotplug event, that userspace has not
-		 * yet discovered.
-		 */
-		ret = -EBUSY;
-		goto out;
-	}
-
-	if (crtc->funcs->page_flip == NULL)
-		goto out;
-
-	fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
-	if (!fb) {
-		ret = -ENOENT;
-		goto out;
-	}
-
-	ret = drm_crtc_check_viewport(crtc, crtc->x, crtc->y, &crtc->mode, fb);
-	if (ret)
-		goto out;
-
-	if (crtc->fb->pixel_format != fb->pixel_format) {
-		DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
-		ret = -EINVAL;
-		goto out;
-	}
+	state = dev->driver->atomic_begin(dev, page_flip->flags);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
 
 	if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
-		ret = -ENOMEM;
-		spin_lock_irqsave(&dev->event_lock, flags);
-		if (file_priv->event_space < sizeof e->event) {
-			spin_unlock_irqrestore(&dev->event_lock, flags);
+		e = create_vblank_event(dev, file_priv, page_flip->user_data);
+		if (!e) {
+			ret = -ENOMEM;
 			goto out;
 		}
-		file_priv->event_space -= sizeof e->event;
-		spin_unlock_irqrestore(&dev->event_lock, flags);
-
-		e = kzalloc(sizeof *e, GFP_KERNEL);
-		if (e == NULL) {
-			spin_lock_irqsave(&dev->event_lock, flags);
-			file_priv->event_space += sizeof e->event;
-			spin_unlock_irqrestore(&dev->event_lock, flags);
+		ret = dev->driver->atomic_set_event(dev, state, obj, e);
+		if (ret) {
 			goto out;
 		}
-
-		e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
-		e->event.base.length = sizeof e->event;
-		e->event.user_data = page_flip->user_data;
-		e->base.event = &e->event.base;
-		e->base.file_priv = file_priv;
-		e->base.destroy =
-			(void (*) (struct drm_pending_event *)) kfree;
 	}
 
-	old_fb = crtc->fb;
-	ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
-	if (ret) {
-		if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
-			spin_lock_irqsave(&dev->event_lock, flags);
-			file_priv->event_space += sizeof e->event;
-			spin_unlock_irqrestore(&dev->event_lock, flags);
-			kfree(e);
-		}
-		/* Keep the old fb, don't unref it. */
-		old_fb = NULL;
-	} else {
-		/*
-		 * Warn if the driver hasn't properly updated the crtc->fb
-		 * field to reflect that the new framebuffer is now used.
-		 * Failing to do so will screw with the reference counting
-		 * on framebuffers.
-		 */
-		WARN_ON(crtc->fb != fb);
-		/* Unref only the old framebuffer. */
-		fb = NULL;
-	}
+	ret = drm_mode_set_obj_prop(obj, state,
+			config->prop_fb_id, page_flip->fb_id, NULL);
+	if (ret)
+		goto out;
 
-out:
-	if (fb)
-		drm_framebuffer_unreference(fb);
-	if (old_fb)
-		drm_framebuffer_unreference(old_fb);
-	ww_mutex_unlock(&crtc->mutex);
+	ret = dev->driver->atomic_check(dev, state);
+	if (ret)
+		goto out;
 
+	ret = dev->driver->atomic_commit(dev, state);
+
+out:
+	if (ret && e)
+		destroy_vblank_event(dev, file_priv, e);
+	dev->driver->atomic_end(dev, state);
 	return ret;
 }
 
diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c
index 61b5a47..df3ad41 100644
--- a/drivers/gpu/drm/drm_fb_cma_helper.c
+++ b/drivers/gpu/drm/drm_fb_cma_helper.c
@@ -429,13 +429,8 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_fini);
  */
 void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma)
 {
-	if (fbdev_cma) {
-		struct drm_device *dev = fbdev_cma->fb_helper.dev;
-
-		drm_modeset_lock_all(dev);
-		drm_fb_helper_restore_fbdev_mode(&fbdev_cma->fb_helper);
-		drm_modeset_unlock_all(dev);
-	}
+	if (fbdev_cma)
+		drm_fb_helper_restore_fbdev_mode(&fbdev_cma->fb_helper, false);
 }
 EXPORT_SYMBOL_GPL(drm_fbdev_cma_restore_mode);
 
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 5773468..294fe5e 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -276,12 +276,15 @@ EXPORT_SYMBOL(drm_fb_helper_debug_leave);
 /**
  * drm_fb_helper_restore_fbdev_mode - restore fbdev configuration
  * @fb_helper: fbcon to restore
+ * @lockless: true in drm_fb_helper_force_kernel_mode() path, to avoid
+ *   blocking in panic case, everywhere else should use false
  *
  * This should be called from driver's drm ->lastclose callback
  * when implementing an fbcon on top of kms using this helper. This ensures that
  * the user isn't greeted with a black screen when e.g. X dies.
  */
-bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
+bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper,
+		bool lockless)
 {
 	struct drm_device *dev = fb_helper->dev;
 	struct drm_plane *plane;
@@ -289,9 +292,8 @@ bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 	void *state;
 	int i;
 
-	drm_warn_on_modeset_not_all_locked(dev);
-
-	state = dev->driver->atomic_begin(dev, 0);
+	state = dev->driver->atomic_begin(dev, lockless ?
+			DRM_MODE_ATOMIC_NOLOCK : 0);
 	if (IS_ERR(state)) {
 		DRM_ERROR("failed to restore fbdev mode\n");
 		return true;
@@ -343,7 +345,7 @@ static bool drm_fb_helper_force_kernel_mode(void)
 		if (helper->dev->switch_power_state == DRM_SWITCH_POWER_OFF)
 			continue;
 
-		ret = drm_fb_helper_restore_fbdev_mode(helper);
+		ret = drm_fb_helper_restore_fbdev_mode(helper, true);
 		if (ret)
 			error = true;
 	}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
index 4ae55b8..c1ef671 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
@@ -14,6 +14,7 @@
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 
 #include "exynos_drm_crtc.h"
 #include "exynos_drm_drv.h"
@@ -286,7 +287,9 @@ static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
 		return 0;
 	}
 
-	return -EINVAL;
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
 }
 
 static struct drm_crtc_funcs exynos_crtc_funcs = {
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
index e7c2f2d..8032021 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
@@ -354,7 +354,5 @@ void exynos_drm_fbdev_restore_mode(struct drm_device *dev)
 	if (!private || !private->fb_helper)
 		return;
 
-	drm_modeset_lock_all(dev);
-	drm_fb_helper_restore_fbdev_mode(private->fb_helper);
-	drm_modeset_unlock_all(dev);
+	drm_fb_helper_restore_fbdev_mode(private->fb_helper, false);
 }
diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
index 8fbfa06..2b4bbf5 100644
--- a/drivers/gpu/drm/gma500/cdv_intel_display.c
+++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
@@ -1022,6 +1022,7 @@ const struct drm_crtc_funcs cdv_intel_crtc_funcs = {
 	.cursor_move = gma_crtc_cursor_move,
 	.gamma_set = gma_crtc_gamma_set,
 	.set_config = gma_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = gma_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/gma500/psb_drv.c b/drivers/gpu/drm/gma500/psb_drv.c
index 34c3116..a6545fe 100644
--- a/drivers/gpu/drm/gma500/psb_drv.c
+++ b/drivers/gpu/drm/gma500/psb_drv.c
@@ -153,11 +153,9 @@ static void psb_lastclose(struct drm_device *dev)
 	struct drm_psb_private *dev_priv = dev->dev_private;
 	struct psb_fbdev *fbdev = dev_priv->fbdev;
 
-	drm_modeset_lock_all(dev);
-	ret = drm_fb_helper_restore_fbdev_mode(&fbdev->psb_fb_helper);
+	ret = drm_fb_helper_restore_fbdev_mode(&fbdev->psb_fb_helper, false);
 	if (ret)
 		DRM_DEBUG("failed to restore crtc mode\n");
-	drm_modeset_unlock_all(dev);
 
 	return;
 }
diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
index c8841ac..35e13ef 100644
--- a/drivers/gpu/drm/gma500/psb_intel_display.c
+++ b/drivers/gpu/drm/gma500/psb_intel_display.c
@@ -444,6 +444,7 @@ const struct drm_crtc_funcs psb_intel_crtc_funcs = {
 	.cursor_move = gma_crtc_cursor_move,
 	.gamma_set = gma_crtc_gamma_set,
 	.set_config = gma_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = gma_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 3b4e60e..02d705b 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -2232,11 +2232,11 @@ void intel_display_handle_reset(struct drm_device *dev)
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
 
-		ww_mutex_lock(&crtc->mutex, NULL);
+		drm_modeset_lock_crtc(crtc, NULL);
 		if (intel_crtc->active)
 			dev_priv->display.update_plane(crtc, crtc->fb,
 						       crtc->x, crtc->y);
-		ww_mutex_unlock(&crtc->mutex);
+		drm_modeset_unlock_crtc(crtc);
 	}
 }
 
@@ -7550,7 +7550,7 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
 	if (encoder->crtc) {
 		crtc = encoder->crtc;
 
-		ww_mutex_lock(&crtc->mutex, NULL);
+		drm_modeset_lock_crtc(crtc, NULL);
 
 		old->dpms_mode = connector->dpms;
 		old->load_detect_temp = false;
@@ -7581,7 +7581,7 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
 		return false;
 	}
 
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 	intel_encoder->new_crtc = to_intel_crtc(crtc);
 	to_intel_connector(connector)->new_encoder = intel_encoder;
 
@@ -7609,7 +7609,7 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
 		DRM_DEBUG_KMS("reusing fbdev for load-detection framebuffer\n");
 	if (IS_ERR(fb)) {
 		DRM_DEBUG_KMS("failed to allocate framebuffer for load-detection\n");
-		ww_mutex_unlock(&crtc->mutex);
+		drm_modeset_unlock_crtc(crtc);
 		return false;
 	}
 
@@ -7617,7 +7617,7 @@ bool intel_get_load_detect_pipe(struct drm_connector *connector,
 		DRM_DEBUG_KMS("failed to set mode on load-detect pipe\n");
 		if (old->release_fb)
 			old->release_fb->funcs->destroy(old->release_fb);
-		ww_mutex_unlock(&crtc->mutex);
+		drm_modeset_unlock_crtc(crtc);
 		return false;
 	}
 
@@ -7648,7 +7648,7 @@ void intel_release_load_detect_pipe(struct drm_connector *connector,
 			drm_framebuffer_unreference(old->release_fb);
 		}
 
-		ww_mutex_unlock(&crtc->mutex);
+		drm_modeset_unlock_crtc(crtc);
 		return;
 	}
 
@@ -7656,7 +7656,7 @@ void intel_release_load_detect_pipe(struct drm_connector *connector,
 	if (old->dpms_mode != DRM_MODE_DPMS_ON)
 		connector->funcs->dpms(connector, old->dpms_mode);
 
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 }
 
 static int i9xx_pll_refclk(struct drm_device *dev,
@@ -9778,6 +9778,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = {
 	.cursor_move = intel_crtc_cursor_move,
 	.gamma_set = intel_crtc_gamma_set,
 	.set_config = intel_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = intel_crtc_destroy,
 	.page_flip = intel_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/i915/intel_fbdev.c b/drivers/gpu/drm/i915/intel_fbdev.c
index 895fcb4..4f7046d 100644
--- a/drivers/gpu/drm/i915/intel_fbdev.c
+++ b/drivers/gpu/drm/i915/intel_fbdev.c
@@ -313,11 +313,7 @@ void intel_fbdev_restore_mode(struct drm_device *dev)
 	if (INTEL_INFO(dev)->num_pipes == 0)
 		return;
 
-	drm_modeset_lock_all(dev);
-
-	ret = drm_fb_helper_restore_fbdev_mode(&dev_priv->fbdev->helper);
+	ret = drm_fb_helper_restore_fbdev_mode(&dev_priv->fbdev->helper, false);
 	if (ret)
 		DRM_DEBUG("failed to restore crtc mode\n");
-
-	drm_modeset_unlock_all(dev);
 }
diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
index ee6ed63..8b796e1 100644
--- a/drivers/gpu/drm/mgag200/mgag200_mode.c
+++ b/drivers/gpu/drm/mgag200/mgag200_mode.c
@@ -1296,6 +1296,7 @@ static const struct drm_crtc_funcs mga_crtc_funcs = {
 	.cursor_move = mga_crtc_cursor_move,
 	.gamma_set = mga_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = mga_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
index 37a3fd2..ba6ed7d 100644
--- a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
+++ b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
@@ -427,8 +427,9 @@ static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
 static int mdp4_crtc_set_property(struct drm_crtc *crtc, void *state,
 		struct drm_property *property, uint64_t val, void *blob_data)
 {
-	// XXX
-	return -EINVAL;
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
 }
 
 #define CURSOR_WIDTH 64
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index dadd55a..cd7cd43 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -319,11 +319,8 @@ static void msm_preclose(struct drm_device *dev, struct drm_file *file)
 static void msm_lastclose(struct drm_device *dev)
 {
 	struct msm_drm_private *priv = dev->dev_private;
-	if (priv->fbdev) {
-		drm_modeset_lock_all(dev);
-		drm_fb_helper_restore_fbdev_mode(priv->fbdev);
-		drm_modeset_unlock_all(dev);
-	}
+	if (priv->fbdev)
+		drm_fb_helper_restore_fbdev_mode(priv->fbdev, false);
 }
 
 static irqreturn_t msm_irq(DRM_IRQ_ARGS)
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
index 0e3270c..605fdb6 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
@@ -1086,6 +1086,7 @@ static const struct drm_crtc_funcs nv04_crtc_funcs = {
 	.cursor_move = nv04_crtc_cursor_move,
 	.gamma_set = nv_crtc_gamma_set,
 	.set_config = nouveau_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = nouveau_crtc_page_flip,
 	.destroy = nv_crtc_destroy,
 };
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
index f8e66c0..bfcdf8e 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -1327,6 +1327,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = {
 	.cursor_move = nv50_crtc_cursor_move,
 	.gamma_set = nv50_crtc_gamma_set,
 	.set_config = nouveau_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = nv50_crtc_destroy,
 	.page_flip = nouveau_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c
index c040fc6..865e863 100644
--- a/drivers/gpu/drm/omapdrm/omap_crtc.c
+++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
@@ -307,13 +307,13 @@ static void page_flip_worker(struct work_struct *work)
 	struct drm_display_mode *mode = &crtc->mode;
 	struct drm_gem_object *bo;
 
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 	omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb,
 			0, 0, mode->hdisplay, mode->vdisplay,
 			crtc->x << 16, crtc->y << 16,
 			mode->hdisplay << 16, mode->vdisplay << 16,
 			vblank_cb, crtc);
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 
 	bo = omap_framebuffer_bo(crtc->fb, 0);
 	drm_gem_object_unreference_unlocked(bo);
@@ -367,14 +367,19 @@ static int omap_crtc_set_property(struct drm_crtc *crtc, void *state,
 {
 	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
 	struct omap_drm_private *priv = crtc->dev->dev_private;
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+	int ret;
 
 	if (property == priv->rotation_prop) {
-		crtc->invert_dimensions =
+		cstate->invert_dimensions =
 				!!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270)));
 	}
 
-	return omap_plane_set_property(omap_crtc->plane, state,
+	ret = omap_plane_set_property(omap_crtc->plane, state,
 			property, val, blob_data);
+	if (ret)
+		ret = drm_crtc_set_property(crtc, cstate, property, val, blob_data);
+	return ret;
 }
 
 static const struct drm_crtc_funcs omap_crtc_funcs = {
@@ -447,7 +452,7 @@ static void apply_worker(struct work_struct *work)
 	 * the callbacks and list modification all serialized
 	 * with respect to modesetting ioctls from userspace.
 	 */
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 	dispc_runtime_get();
 
 	/*
@@ -492,7 +497,7 @@ static void apply_worker(struct work_struct *work)
 
 out:
 	dispc_runtime_put();
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 }
 
 int omap_crtc_apply(struct drm_crtc *crtc,
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index e1e794a..cfd40f9 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -553,7 +553,7 @@ static void dev_lastclose(struct drm_device *dev)
 		 */
 		for (i = 0; i < priv->num_crtcs; i++) {
 			drm_object_property_set_value(&priv->crtcs[i]->base,
-					&priv->crtcs[i]->propvals,
+					&priv->crtcs[i]->state->propvals,
 					priv->rotation_prop, 0, NULL);
 		}
 
@@ -564,9 +564,7 @@ static void dev_lastclose(struct drm_device *dev)
 		}
 	}
 
-	drm_modeset_lock_all(dev);
-	ret = drm_fb_helper_restore_fbdev_mode(priv->fbdev);
-	drm_modeset_unlock_all(dev);
+	ret = drm_fb_helper_restore_fbdev_mode(priv->fbdev, false);
 	if (ret)
 		DBG("failed to restore crtc mode");
 }
diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
index d36abbc..29b9572 100644
--- a/drivers/gpu/drm/qxl/qxl_display.c
+++ b/drivers/gpu/drm/qxl/qxl_display.c
@@ -29,6 +29,7 @@
 #include "qxl_drv.h"
 #include "qxl_object.h"
 #include "drm_crtc_helper.h"
+#include "drm_atomic_helper.h"
 
 static bool qxl_head_enabled(struct qxl_head *head)
 {
@@ -373,6 +374,7 @@ static const struct drm_crtc_funcs qxl_crtc_funcs = {
 	.cursor_set2 = qxl_crtc_cursor_set2,
 	.cursor_move = qxl_crtc_cursor_move,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = qxl_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c
index 7b25381..84e0c5e 100644
--- a/drivers/gpu/drm/radeon/radeon_display.c
+++ b/drivers/gpu/drm/radeon/radeon_display.c
@@ -32,6 +32,7 @@
 
 #include <linux/pm_runtime.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
 
 static void avivo_crtc_load_lut(struct drm_crtc *crtc)
@@ -544,6 +545,7 @@ static const struct drm_crtc_funcs radeon_crtc_funcs = {
 	.cursor_move = radeon_crtc_cursor_move,
 	.gamma_set = radeon_crtc_gamma_set,
 	.set_config = radeon_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = radeon_crtc_destroy,
 	.page_flip = radeon_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index a9d24e4..c840ba8 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -17,6 +17,7 @@
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -528,6 +529,7 @@ static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
 static const struct drm_crtc_funcs crtc_funcs = {
 	.destroy = drm_crtc_cleanup,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = rcar_du_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
index 9e86b99..9209526 100644
--- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
+++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
@@ -17,6 +17,7 @@
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -496,6 +497,7 @@ static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
 static const struct drm_crtc_funcs crtc_funcs = {
 	.destroy = drm_crtc_cleanup,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = shmob_drm_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c
index 490f771..59f7fd1 100644
--- a/drivers/gpu/drm/tegra/fb.c
+++ b/drivers/gpu/drm/tegra/fb.c
@@ -384,9 +384,6 @@ void tegra_drm_fb_exit(struct drm_device *drm)
 
 void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev)
 {
-	if (fbdev) {
-		drm_modeset_lock_all(fbdev->base.dev);
-		drm_fb_helper_restore_fbdev_mode(&fbdev->base);
-		drm_modeset_unlock_all(fbdev->base.dev);
-	}
+	if (fbdev)
+		drm_fb_helper_restore_fbdev_mode(&fbdev->base, false);
 }
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
index d36efc1..34d9804 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -411,6 +411,7 @@ static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
 static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
 		.destroy        = tilcdc_crtc_destroy,
 		.set_config     = drm_crtc_helper_set_config,
+		.set_property   = drm_atomic_helper_crtc_set_property,
 		.page_flip      = tilcdc_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c
index 2ae1eb7..e05b2ea 100644
--- a/drivers/gpu/drm/udl/udl_modeset.c
+++ b/drivers/gpu/drm/udl/udl_modeset.c
@@ -14,6 +14,7 @@
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include "udl_drv.h"
 
 /*
@@ -383,6 +384,7 @@ static struct drm_crtc_helper_funcs udl_helper_funcs = {
 
 static const struct drm_crtc_funcs udl_crtc_funcs = {
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = udl_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
index 7b3bf18..36c9301 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
@@ -186,7 +186,7 @@ int vmw_du_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
 	 * can do this since the caller in the drm core doesn't check anything
 	 * which is protected by any looks.
 	 */
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 	drm_modeset_lock_all(dev_priv->dev);
 
 	/* A lot of the code assumes this */
@@ -251,9 +251,7 @@ int vmw_du_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
 	ret = 0;
 out:
 	drm_modeset_unlock_all(dev_priv->dev);
-// XXX umm, we probably need the state object here to properly
-// re-aquire the lock..
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 
 	return ret;
 }
@@ -274,7 +272,7 @@ int vmw_du_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
 	 * can do this since the caller in the drm core doesn't check anything
 	 * which is protected by any looks.
 	 */
-	ww_mutex_unlock(&crtc->mutex);
+	drm_modeset_unlock_crtc(crtc);
 	drm_modeset_lock_all(dev_priv->dev);
 
 	vmw_cursor_update_position(dev_priv, shown,
@@ -282,9 +280,7 @@ int vmw_du_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
 				   du->cursor_y + du->hotspot_y);
 
 	drm_modeset_unlock_all(dev_priv->dev);
-// XXX umm, we probably need the state object here to properly
-// re-aquire the lock..
-	ww_mutex_lock(&crtc->mutex, NULL);
+	drm_modeset_lock_crtc(crtc, NULL);
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
index 79f7e8e..5d25f21 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
@@ -298,6 +298,7 @@ static struct drm_crtc_funcs vmw_legacy_crtc_funcs = {
 	.cursor_move = vmw_du_crtc_cursor_move,
 	.gamma_set = vmw_du_crtc_gamma_set,
 	.destroy = vmw_ldu_crtc_destroy,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.set_config = vmw_ldu_crtc_set_config,
 };
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
index 26387c3..f65fec7 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
@@ -394,6 +394,7 @@ static struct drm_crtc_funcs vmw_screen_object_crtc_funcs = {
 	.gamma_set = vmw_du_crtc_gamma_set,
 	.destroy = vmw_sou_crtc_destroy,
 	.set_config = vmw_sou_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = vmw_du_page_flip,
 };
 
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index d0d29e1..44b51dd 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -65,6 +65,10 @@ struct drm_atomic_helper_funcs {
 	struct drm_plane_state *(*get_plane_state)(struct drm_plane *plane, void *state);
 	int (*check_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
 	int (*commit_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
+
+	struct drm_crtc_state *(*get_crtc_state)(struct drm_crtc *crtc, void *state);
+	int (*check_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
+	int (*commit_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
 };
 
 const extern struct drm_atomic_helper_funcs drm_atomic_helper_funcs;
@@ -108,6 +112,37 @@ drm_atomic_commit_plane_state(struct drm_plane *plane,
 	return funcs->commit_plane_state(plane, pstate);
 }
 
+int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
+		struct drm_property *property, uint64_t val, void *blob_data);
+void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate, void *state);
+
+static inline struct drm_crtc_state *
+drm_atomic_get_crtc_state(struct drm_crtc *crtc, void *state)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->get_crtc_state(crtc, state);
+}
+
+static inline int
+drm_atomic_check_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->check_crtc_state(crtc, cstate);
+}
+
+static inline int
+drm_atomic_commit_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->commit_crtc_state(crtc, cstate);
+}
+
 /**
  * struct drm_atomic_helper_state - the state object used by atomic helpers
  */
@@ -117,6 +152,12 @@ struct drm_atomic_helper_state {
 	uint32_t flags;
 	struct drm_plane **planes;
 	struct drm_plane_state **pstates;
+	struct drm_crtc **crtcs;
+	struct drm_crtc_state **cstates;
+
+	struct ww_acquire_ctx ww_ctx;
+	struct list_head locked_crtcs;
+	bool locks_dropped;
 };
 
 static inline void
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index dfe8d20..2531658 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -333,6 +333,8 @@ struct drm_pending_vblank_event;
 struct drm_plane;
 struct drm_bridge;
 
+extern struct ww_class crtc_ww_class;
+
 /**
  * drm_crtc_funcs - control CRTCs for a given device
  * @save: save CRTC state
@@ -399,18 +401,51 @@ struct drm_crtc_funcs {
 };
 
 /**
+ * drm_crtc_state - mutable crtc state
+ * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
+ *    invert the width/height of the crtc.  This is used if the driver
+ *    is performing 90 or 270 degree rotated scanout
+ * @set_config: needs modeset (crtc->set_config())
+ * @new_fb: has the fb been changed
+ * @mode_valid: a valid mode has been set
+ * @num_connector_ids: the number of connector-ids
+ * @connector_ids: array of connector ids
+ * @mode: current mode timings
+ * @fb: the framebuffer that the CRTC is currently bound to
+ * @x: x position on screen
+ * @y: y position on screen
+ * @event: pending pageflip event
+ * @propvals: property values
+ * @state: current global/toplevel state object (for atomic) while an
+ *    update is in progress, NULL otherwise.
+ */
+struct drm_crtc_state {
+	bool invert_dimensions : 1;
+	bool set_config        : 1;
+	bool new_fb            : 1;
+	bool mode_valid        : 1;  /* drop this if when mode a refcnt'd ptr */
+	uint8_t num_connector_ids;
+	uint32_t *connector_ids;
+	struct drm_mode_modeinfo mode;
+	struct drm_framebuffer *fb;
+	int x, y;
+
+	struct drm_pending_vblank_event *event;
+
+	struct drm_object_property_values propvals;
+
+	void *state;
+};
+
+/**
  * drm_crtc - central CRTC control structure
  * @dev: parent DRM device
  * @head: list management
+ * @id: CRTC number, 0..n
  * @base: base KMS object for ID tracking etc.
+ * @state: the mutable state
  * @enabled: is this CRTC enabled?
- * @mode: current mode timings
  * @hwmode: mode timings as programmed to hw regs
- * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
- *    invert the width/height of the crtc.  This is used if the driver
- *    is performing 90 or 270 degree rotated scanout
- * @x: x position on screen
- * @y: y position on screen
  * @funcs: CRTC control functions
  * @gamma_size: size of gamma ramp
  * @gamma_store: gamma ramp values
@@ -427,6 +462,8 @@ struct drm_crtc {
 	struct drm_device *dev;
 	struct list_head head;
 
+	int id;
+
 	/**
 	 * crtc mutex
 	 *
@@ -436,10 +473,15 @@ struct drm_crtc {
 	 */
 	struct ww_mutex mutex;
 
+	/**
+	 * CRTC's that are locked as part of an atomic update are added to
+	 * a list (so we know what to unlock at the end).
+	 */
+	struct list_head lock_head;
+
 	struct drm_mode_object base;
 
-	/* framebuffer the connector is currently bound to */
-	struct drm_framebuffer *fb;
+	struct drm_crtc_state *state;
 
 	/* Temporary tracking of the old fb while a modeset is ongoing. Used
 	 * by drm_mode_set_config_internal to implement correct refcounting. */
@@ -447,17 +489,11 @@ struct drm_crtc {
 
 	bool enabled;
 
-	/* Requested mode from modesetting. */
-	struct drm_display_mode mode;
-
 	/* Programmed mode in hw, after adjustments for encoders,
 	 * crtc, panel scaling etc. Needed for timestamping etc.
 	 */
 	struct drm_display_mode hwmode;
 
-	bool invert_dimensions;
-
-	int x, y;
 	const struct drm_crtc_funcs *funcs;
 
 	/* CRTC gamma size for reporting to userspace */
@@ -471,7 +507,15 @@ struct drm_crtc {
 	void *helper_private;
 
 	struct drm_object_properties properties;
-	struct drm_object_property_values propvals;
+
+	/* These are (temporary) duplicate information from what is in the
+	 * drm_crtc_state struct..  keeping duplicate copy here makes the
+	 * switch to atomic far less intrusive.  Once all the drivers and
+	 * the crtc/fb helpers are updated, then we can remove these:
+	 */
+	struct drm_framebuffer *fb;
+	int x, y;
+	struct drm_display_mode mode;
 };
 
 
@@ -951,6 +995,8 @@ struct drm_mode_config {
 	struct drm_property *prop_crtc_h;
 	struct drm_property *prop_fb_id;
 	struct drm_property *prop_crtc_id;
+	struct drm_property *prop_connector_ids;
+	struct drm_property *prop_mode;
 	struct drm_property *edid_property;
 	struct drm_property *dpms_property;
 
@@ -998,6 +1044,8 @@ struct drm_prop_enum_list {
 	char *name;
 };
 
+int drm_modeset_lock_crtc(struct drm_crtc *crtc, void *state);
+void drm_modeset_unlock_crtc(struct drm_crtc *crtc);
 extern void drm_modeset_lock_all(struct drm_device *dev);
 extern void drm_modeset_unlock_all(struct drm_device *dev);
 extern void drm_warn_on_modeset_not_all_locked(struct drm_device *dev);
@@ -1006,6 +1054,14 @@ extern int drm_crtc_init(struct drm_device *dev,
 			 struct drm_crtc *crtc,
 			 const struct drm_crtc_funcs *funcs);
 extern void drm_crtc_cleanup(struct drm_crtc *crtc);
+extern int drm_crtc_check_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state);
+extern void drm_crtc_commit_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state);
+extern int drm_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_crtc_state *state,
+		struct drm_property *property,
+		uint64_t value, void *blob_data);
 
 extern void drm_connector_ida_init(void);
 extern void drm_connector_ida_destroy(void);
@@ -1055,6 +1111,7 @@ extern const char *drm_get_tv_subconnector_name(int val);
 extern const char *drm_get_tv_select_name(int val);
 extern void drm_fb_release(struct drm_file *file_priv);
 extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group);
+extern int drm_crtc_convert_umode(struct drm_display_mode *out, const struct drm_mode_modeinfo *in);
 extern bool drm_probe_ddc(struct i2c_adapter *adapter);
 extern struct edid *drm_get_edid(struct drm_connector *connector,
 				 struct i2c_adapter *adapter);
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index 471f276..b130a4d 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -108,7 +108,8 @@ int drm_fb_helper_set_par(struct fb_info *info);
 int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
 			    struct fb_info *info);
 
-bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper);
+bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper,
+		bool lockless);
 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper,
 			    uint32_t fb_width, uint32_t fb_height);
 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 9fed70e..a913953 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -508,4 +508,6 @@ struct drm_mode_destroy_dumb {
 	uint32_t handle;
 };
 
+#define DRM_MODE_ATOMIC_NOLOCK    0x8000  /* only used internally */
+
 #endif
-- 
1.8.4.2



More information about the dri-devel mailing list