diff mbox

[12/17] drm: convert crtc to properties/state

Message ID 1400956226-28053-13-git-send-email-robdclark@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Rob Clark May 24, 2014, 6:30 p.m. UTC
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.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/armada/armada_crtc.c       |  11 +-
 drivers/gpu/drm/ast/ast_mode.c             |   1 +
 drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
 drivers/gpu/drm/drm_atomic.c               | 231 ++++++++++-
 drivers/gpu/drm/drm_crtc.c                 | 598 ++++++++++++++++++-----------
 drivers/gpu/drm/drm_fb_helper.c            |   2 +-
 drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   7 +-
 drivers/gpu/drm/gma500/cdv_intel_display.c |   1 +
 drivers/gpu/drm/gma500/psb_intel_display.c |   1 +
 drivers/gpu/drm/i915/intel_display.c       |   1 +
 drivers/gpu/drm/mgag200/mgag200_mode.c     |   1 +
 drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c   |   6 +-
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c   |   6 +-
 drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
 drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
 drivers/gpu/drm/omapdrm/omap_crtc.c        |  12 +-
 drivers/gpu/drm/omapdrm/omap_drv.c         |   2 +-
 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/tilcdc/tilcdc_crtc.c       |   1 +
 drivers/gpu/drm/udl/udl_modeset.c          |   2 +
 drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c        |   1 +
 drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c       |   1 +
 include/drm/drm_atomic.h                   |  29 ++
 include/drm/drm_crtc.h                     | 103 ++++-
 27 files changed, 785 insertions(+), 243 deletions(-)

Comments

Daniel Vetter May 26, 2014, 9:31 a.m. UTC | #1
On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
> 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.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Same comments about interface design as for the plane patch apply here.
One additional comment below.

> ---
>  drivers/gpu/drm/armada/armada_crtc.c       |  11 +-
>  drivers/gpu/drm/ast/ast_mode.c             |   1 +
>  drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
>  drivers/gpu/drm/drm_atomic.c               | 231 ++++++++++-
>  drivers/gpu/drm/drm_crtc.c                 | 598 ++++++++++++++++++-----------
>  drivers/gpu/drm/drm_fb_helper.c            |   2 +-
>  drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   7 +-
>  drivers/gpu/drm/gma500/cdv_intel_display.c |   1 +
>  drivers/gpu/drm/gma500/psb_intel_display.c |   1 +
>  drivers/gpu/drm/i915/intel_display.c       |   1 +
>  drivers/gpu/drm/mgag200/mgag200_mode.c     |   1 +
>  drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c   |   6 +-
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c   |   6 +-
>  drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
>  drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
>  drivers/gpu/drm/omapdrm/omap_crtc.c        |  12 +-
>  drivers/gpu/drm/omapdrm/omap_drv.c         |   2 +-
>  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/tilcdc/tilcdc_crtc.c       |   1 +
>  drivers/gpu/drm/udl/udl_modeset.c          |   2 +
>  drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c        |   1 +
>  drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c       |   1 +
>  include/drm/drm_atomic.h                   |  29 ++
>  include/drm/drm_crtc.h                     | 103 ++++-
>  27 files changed, 785 insertions(+), 243 deletions(-)
> 
> diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> index 7d3c649..6237af4 100644
> --- a/drivers/gpu/drm/armada/armada_crtc.c
> +++ b/drivers/gpu/drm/armada/armada_crtc.c
> @@ -9,6 +9,7 @@
>  #include <linux/clk.h>
>  #include <drm/drmP.h>
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic.h>
>  #include "armada_crtc.h"
>  #include "armada_drm.h"
>  #include "armada_fb.h"
> @@ -966,7 +967,12 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
>  {
>  	struct armada_private *priv = crtc->dev->dev_private;
>  	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
>  	bool update_csc = false;
> +	int ret = 0;
> +
> +	if (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
>  
>  	if (property == priv->csc_yuv_prop) {
>  		dcrtc->csc_yuv_mode = val;
> @@ -974,6 +980,9 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
>  	} else if (property == priv->csc_rgb_prop) {
>  		dcrtc->csc_rgb_mode = val;
>  		update_csc = true;
> +	} else {
> +		ret = drm_crtc_set_property(crtc, cstate, property,
> +				val, blob_data);
>  	}
>  
>  	if (update_csc) {
> @@ -984,7 +993,7 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
>  		writel_relaxed(val, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
>  	}
>  
> -	return 0;
> +	return ret;
>  }
>  
>  static struct drm_crtc_funcs armada_crtc_funcs = {
> diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
> index 114aee9..c08e0e1 100644
> --- a/drivers/gpu/drm/ast/ast_mode.c
> +++ b/drivers/gpu/drm/ast/ast_mode.c
> @@ -632,6 +632,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_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 49332c5..3c4428c 100644
> --- a/drivers/gpu/drm/cirrus/cirrus_mode.c
> +++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
> @@ -366,6 +366,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_crtc_set_property,
>  	.destroy = cirrus_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 403ffc5..863a0fe 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -48,11 +48,13 @@ struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
>  {
>  	struct drm_atomic_state *state;
>  	int nplanes = dev->mode_config.num_total_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);
>  
> @@ -74,6 +76,12 @@ struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
>  	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_begin);
> @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
>  		struct drm_atomic_state *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);
> +		if (IS_ERR(cstate))
> +			return PTR_ERR(cstate);
> +		cstate->event = event;
> +		return 0;
> +	}
> +	default:
> +		return -EINVAL;
> +	}

Hm, I think if we only want completion events on crtcs (which I agree on)
then we should make the set_event interface more specific by passing
struct drm_crtc * and only call it for crtcs.

>  }
>  EXPORT_SYMBOL(drm_atomic_set_event);
>  
> @@ -111,6 +130,7 @@ int drm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
>  {
>  	struct drm_atomic_state *a = state;
>  	int nplanes = dev->mode_config.num_total_plane;
> +	int ncrtcs = dev->mode_config.num_crtc;
>  	int i, ret = 0;
>  
>  	for (i = 0; i < nplanes; i++) {
> @@ -120,6 +140,13 @@ int drm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
>  				break;
>  		}
>  	}
> +	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;
> +		}
> +	}
>  
>  	a->acquire_ctx.frozen = true;
>  
> @@ -203,6 +230,7 @@ static void commit_locks(struct drm_atomic_state *a,
>  {
>  	struct drm_device *dev = a->dev;
>  	int nplanes = dev->mode_config.num_total_plane;
> +	int ncrtcs = dev->mode_config.num_crtc;
>  	int i;
>  
>  	for (i = 0; i < nplanes; i++) {
> @@ -213,6 +241,14 @@ static void commit_locks(struct drm_atomic_state *a,
>  		}
>  	}
>  
> +	for (i = 0; i < ncrtcs; i++) {
> +		struct drm_crtc *crtc = a->crtcs[i];
> +		if (crtc) {
> +			crtc->state->state = NULL;
> +			drm_crtc_destroy_state(crtc, a->cstates[i]);
> +		}
> +	}
> +
>  	/* and properly release them (clear in_atomic, remove from list): */
>  	drm_modeset_drop_locks(&a->acquire_ctx);
>  	ww_acquire_fini(ww_ctx);
> @@ -223,8 +259,18 @@ static int atomic_commit(struct drm_atomic_state *a,
>  		struct ww_acquire_ctx *ww_ctx)
>  {
>  	int nplanes = a->dev->mode_config.num_total_plane;
> +	int ncrtcs = a->dev->mode_config.num_crtc;
>  	int i, ret = 0;
>  
> +	for (i = 0; i < ncrtcs; i++) {
> +		struct drm_crtc *crtc = a->crtcs[i];
> +		if (crtc) {
> +			ret = drm_atomic_commit_crtc_state(crtc, a->cstates[i]);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
>  	for (i = 0; i < nplanes; i++) {
>  		struct drm_plane *plane = a->planes[i];
>  		if (plane) {
> @@ -403,6 +449,7 @@ static int
>  commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
>  {
>  	struct drm_atomic_state *a = pstate->state;
> +	struct drm_crtc_state *cstate = NULL;
>  	struct drm_framebuffer *old_fb = plane->fb;
>  	struct drm_framebuffer *fb = pstate->fb;
>  	bool enabled = pstate->crtc && fb;
> @@ -425,8 +472,11 @@ commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
>  		}
>  	} else {
>  		struct drm_crtc *crtc = pstate->crtc;
> +		cstate = drm_atomic_get_crtc_state(crtc, pstate->state);
>  		if (pstate->update_plane ||
>  				(pstate->new_fb && !can_flip(plane, pstate))) {
> +/* TODO pass event to update_plane().. */
> +WARN_ON(cstate->event);
>  			ret = plane->funcs->update_plane(plane, crtc, pstate->fb,
>  					pstate->crtc_x, pstate->crtc_y,
>  					pstate->crtc_w, pstate->crtc_h,
> @@ -443,7 +493,7 @@ commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
>  			}
>  
>  		} else if (pstate->new_fb) {
> -			ret = crtc->funcs->page_flip(crtc, fb, NULL, a->flags);
> +			ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
>  			if (ret == 0) {
>  				/*
>  				 * Warn if the driver hasn't properly updated the plane->fb
> @@ -473,9 +523,10 @@ commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
>  		 * original code.
>  		 */
>  		swap_plane_state(plane, pstate->state);
> +		if (cstate)
> +			cstate->event = NULL;
>  	}
>  
> -
>  	if (fb)
>  		drm_framebuffer_unreference(fb);
>  	if (old_fb)
> @@ -484,8 +535,182 @@ commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
>  	return ret;
>  }
>  
> +int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
> +		struct drm_atomic_state *state, struct drm_property *property,
> +		uint64_t val, void *blob_data)
> +{
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> +	if (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
> +	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
> +}
> +EXPORT_SYMBOL(drm_atomic_crtc_set_property);
> +
> +static void init_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate, struct drm_atomic_state *state)
> +{
> +	/* snapshot current state: */
> +	*cstate = *crtc->state;
> +	cstate->state = state;
> +
> +	if (cstate->connector_ids) {
> +		int sz = cstate->num_connector_ids * sizeof(cstate->connector_ids[0]);
> +		cstate->connector_ids = kmemdup(cstate->connector_ids, sz, GFP_KERNEL);
> +	}
> +
> +	/* this should never happen.. but make sure! */
> +	WARN_ON(cstate->event);
> +	cstate->event = NULL;
> +}
> +
> +struct drm_crtc_state *
> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, struct drm_atomic_state *a)
> +{
> +	struct drm_crtc_state *cstate;
> +	int ret;
> +
> +	cstate = a->cstates[crtc->id];
> +
> +	if (!cstate) {
> +		ret = drm_modeset_lock(&crtc->mutex, &a->acquire_ctx);
> +		if (ret)
> +			return ERR_PTR(ret);
> +
> +		cstate = drm_crtc_create_state(crtc);
> +		if (!cstate)
> +			return ERR_PTR(-ENOMEM);
> +		init_crtc_state(crtc, cstate, a);
> +		a->crtcs[crtc->id] = crtc;
> +		a->cstates[crtc->id] = cstate;
> +
> +		/* we'll need it later, so make sure we have state
> +		 * for primary plane too:
> +		 */
> +		drm_atomic_get_plane_state(crtc->primary, a);

I haven't figured out why. With primary planes I don't really see a need
for this. If we need it to implement the legacy setcrtc interface, then
that should be done there, not here.

> +	}
> +	return cstate;
> +}
> +EXPORT_SYMBOL(drm_atomic_get_crtc_state);
> +
> +static void
> +swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_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);
> +	}
> +
> +	/* clear transient state (only valid during atomic update): */
> +	cstate->set_config = false;
> +	cstate->connectors_change = false;
> +
> +	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 int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct drm_plane_state *pstate =
> +			drm_atomic_get_plane_state(crtc->primary, cstate->state);
> +	struct drm_framebuffer *fb = pstate->fb;
> +	struct drm_connector **connector_set = get_connector_set(crtc->dev,
> +			cstate->connector_ids, cstate->num_connector_ids);
> +	struct drm_display_mode *mode = drm_crtc_get_mode(crtc, cstate);
> +	struct drm_mode_set set = {
> +			.crtc = crtc,
> +			.x = pstate->src_x >> 16,
> +			.y = pstate->src_y >> 16,
> +			.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;
> +	}
> +
> +	if (fb)
> +		drm_framebuffer_reference(fb);
> +
> +	ret = drm_mode_set_config_internal(&set);
> +	if (!ret) {
> +		swap_crtc_state(crtc, cstate->state);
> +		pstate->new_fb = pstate->update_plane = false;
> +	}
> +
> +	if (fb)
> +		drm_framebuffer_unreference(fb);
> +
> +	kfree(connector_set);
> +	if (mode)
> +		drm_mode_destroy(dev, mode);
> +	return ret;
> +}
> +
> +static int
> +commit_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	struct drm_plane_state *pstate =
> +			drm_atomic_get_plane_state(crtc->primary, cstate->state);
> +	int ret = -EINVAL;
> +
> +	if (cstate->set_config)
> +		return set_config(crtc, cstate);
> +
> +	if (!pstate->fb) {
> +		/* disable */
> +		struct drm_mode_set set = {
> +				.crtc = crtc,
> +				.fb = NULL,
> +		};
> +
> +		ret = drm_mode_set_config_internal(&set);
> +		if (!ret) {
> +			swap_crtc_state(crtc, cstate->state);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
>  const struct drm_atomic_funcs drm_atomic_funcs = {
>  		.check_plane_state  = drm_plane_check_state,
>  		.commit_plane_state = commit_plane_state,
> +
> +		.check_crtc_state   = drm_crtc_check_state,
> +		.commit_crtc_state  = commit_crtc_state,
>  };
>  EXPORT_SYMBOL(drm_atomic_funcs);
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index b556a31..e14d517 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -689,10 +689,7 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup);
>  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));
>  
> @@ -712,7 +709,7 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>  	 * in this manner.
>  	 */
>  	if (atomic_read(&fb->refcount.refcount) > 1) {
> -		void *state;
> +		struct drm_atomic_state *state;
>  
>  		state = dev->driver->atomic_begin(dev, 0);
>  		if (IS_ERR(state)) {
> @@ -720,24 +717,7 @@ 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_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->primary->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);
> -			}
> -		}
> -
> +		/* remove from any plane */
>  		list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
>  			if (plane->fb == fb)
>  				drm_plane_force_disable(plane, state);
> @@ -750,8 +730,6 @@ 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);
> @@ -782,9 +760,13 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
>  	struct drm_mode_config *config = &dev->mode_config;
>  	int ret;
>  
> +	/* this is now required: */
> +	WARN_ON(!funcs->set_property);
> +
>  	crtc->dev = dev;
>  	crtc->funcs = funcs;
> -	crtc->invert_dimensions = false;
> +	crtc->state = drm_crtc_create_state(crtc);
> +	crtc->state->invert_dimensions = false;
>  
>  	drm_modeset_lock_all(dev);
>  	drm_modeset_lock_init(&crtc->mutex);
> @@ -796,14 +778,17 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
>  		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++;
>  
>  	crtc->primary = primary;
>  	if (primary)
> -		primary->possible_crtcs = 1 << drm_crtc_index(crtc);
> +		primary->possible_crtcs = 1 << crtc->id;
> +
> +	drm_object_attach_property(&crtc->base, config->prop_mode, 0);
> +	drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
>  
>   out:
>  	drm_modeset_unlock_all(dev);
> @@ -832,31 +817,245 @@ void drm_crtc_cleanup(struct drm_crtc *crtc)
>  	drm_mode_object_put(dev, &crtc->base);
>  	list_del(&crtc->head);
>  	dev->mode_config.num_crtc--;
> +
> +	drm_crtc_destroy_state(crtc, crtc->state);
>  }
>  EXPORT_SYMBOL(drm_crtc_cleanup);
>  
> -/**
> - * drm_crtc_index - find the index of a registered CRTC
> - * @crtc: CRTC to find index for
> - *
> - * Given a registered CRTC, return the index of that CRTC within a DRM
> - * device's list of CRTCs.
> - */
> -unsigned int drm_crtc_index(struct drm_crtc *crtc)
> +/* get display-mode from user-mode */
> +struct drm_display_mode *drm_crtc_get_mode(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
>  {
> -	unsigned int index = 0;
> -	struct drm_crtc *tmp;
> +	struct drm_display_mode *mode = NULL;
> +	if (cstate->mode_valid) {
> +		struct drm_device *dev = crtc->dev;
> +		int ret;
>  
> -	list_for_each_entry(tmp, &crtc->dev->mode_config.crtc_list, head) {
> -		if (tmp == crtc)
> -			return index;
> +		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);
> +		}
>  
> -		index++;
> +		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
>  	}
> +	return mode;
> +}
>  
> -	BUG();
> +static int connector_idx(struct drm_crtc_state *state,
> +		uint32_t connector_id)
> +{
> +	int i;
> +	for (i = 0; i < state->num_connector_ids; i++)
> +		if (state->connector_ids[i] == connector_id)
> +			return i;
> +	return -1;
> +}
> +
> +static int remove_connector(struct drm_crtc *ocrtc,
> +		struct drm_crtc_state *ostate, struct drm_atomic_state *state,
> +		int idx)
> +{
> +	struct drm_mode_config *config = &ocrtc->dev->mode_config;
> +	uint32_t *new_connector_ids;
> +	int a, b;
> +
> +	/* before deletion point: */
> +	a = idx * sizeof(ostate->connector_ids[0]);
> +
> +	/* after deletion point: */
> +	b = (ostate->num_connector_ids - 1 - idx) *
> +			sizeof(ostate->connector_ids[0]);
> +
> +	new_connector_ids = kmalloc(a+b, GFP_KERNEL);
> +	if (!new_connector_ids)
> +		return -ENOMEM;
> +
> +	memcpy(new_connector_ids, ostate->connector_ids, a);
> +	memcpy(&new_connector_ids[idx],
> +			&ostate->connector_ids[idx + 1], b);
> +
> +	return drm_mode_crtc_set_obj_prop(ocrtc, state,
> +		config->prop_connector_ids, a + b,
> +		new_connector_ids);
>  }
> -EXPORT_SYMBOL(drm_crtc_index);
> +
> +static int check_connectors(struct drm_crtc *crtc,
> +		struct drm_atomic_state *state, bool fix,
> +		uint32_t *connector_ids, uint32_t num_connector_ids)
> +{
> +	struct drm_mode_config *config = &crtc->dev->mode_config;
> +	struct drm_crtc *ocrtc; /* other connector */
> +
> +	list_for_each_entry(ocrtc, &config->crtc_list, head) {
> +		struct drm_crtc_state *ostate; /* other state */
> +		unsigned i;
> +
> +		if (ocrtc == crtc)
> +			continue;
> +
> +		ostate = drm_atomic_get_crtc_state(crtc, state);
> +		if (IS_ERR(ostate))
> +			return PTR_ERR(ostate);
> +
> +		for (i = 0; i < num_connector_ids; i++) {
> +			struct drm_connector *connector;
> +			uint32_t cid = connector_ids[i];
> +			int idx;
> +
> +retry:
> +			idx = connector_idx(ostate, cid);
> +			if (idx < 0)
> +				continue;
> +
> +			if (fix) {
> +				int ret = remove_connector(ocrtc,
> +						ostate, state, idx);
> +				if (ret)
> +					return ret;
> +				goto retry;
> +			}
> +
> +			connector = drm_connector_find(crtc->dev, cid);
> +			DRM_DEBUG_KMS("[CONNECTOR:%d:%s] already in use\n",
> +					connector->base.id,
> +					drm_get_connector_name(connector));
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +int drm_crtc_check_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state)
> +{
> +	struct drm_plane *primary = crtc->primary;
> +	struct drm_plane_state *pstate =
> +			drm_atomic_get_plane_state(primary, state->state);
> +	struct drm_framebuffer *fb = pstate->fb;
> +	int hdisplay, vdisplay;
> +	struct drm_display_mode *mode = drm_crtc_get_mode(crtc, state);
> +	unsigned x, y;
> +
> +	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);
> +
> +	x = pstate->src_x >> 16;
> +	y = pstate->src_y >> 16;
> +
> +	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, state->invert_dimensions ? " (inverted)" : "");
> +		return -ENOSPC;
> +	}
> +
> +	if (crtc->enabled && !state->set_config) {
> +		if (primary->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 (state->connectors_change) {
> +		int ret = check_connectors(crtc, state->state, false,
> +				state->connector_ids, state->num_connector_ids);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	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;
> +
> +	/* grab primary plane state now, to ensure locks are held, etc. */
> +	drm_atomic_get_plane_state(crtc->primary, state->state);
> +
> +	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) {
> +		/* if connector-id's changing, we need to have all the locks: */
> +		struct drm_atomic_state *a = state->state;
> +		int ret = drm_modeset_lock_all_crtcs(crtc->dev, &a->acquire_ctx);
> +		if (ret)
> +			return ret;
> +		state->connectors_change = true;
> +		state->num_connector_ids = value / sizeof(state->connector_ids[0]);
> +		kfree(state->connector_ids);
> +		state->connector_ids = blob_data;
> +		state->set_config = true;
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_crtc_set_property);
>  
>  /*
>   * drm_mode_remove - remove and free a mode
> @@ -1239,6 +1438,10 @@ int drm_plane_check_state(struct drm_plane *plane,
>  	if (!fb)
>  		return 0;
>  
> +	/* we'll need this later during commit: */
> +	if (state->crtc)
> +		drm_atomic_get_crtc_state(state->crtc, state->state);
> +
>  	fb_width = fb->width << 16;
>  	fb_height = fb->height << 16;
>  
> @@ -1465,6 +1668,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;
>  }
>  
> @@ -1754,7 +1967,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)
> @@ -2001,8 +2214,8 @@ int drm_mode_getcrtc(struct drm_device *dev,
>  		goto out;
>  	}
>  
> -	crtc_resp->x = crtc->x;
> -	crtc_resp->y = crtc->y;
> +	crtc_resp->x = crtc->primary->state->src_x >> 16;
> +	crtc_resp->y = crtc->primary->state->src_y >> 16;
>  	crtc_resp->gamma_size = crtc->gamma_size;
>  	if (crtc->primary->fb)
>  		crtc_resp->fb_id = crtc->primary->fb->base.id;
> @@ -2495,7 +2708,7 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc,
>  		vdisplay = adjusted.crtc_vdisplay;
>  	}
>  
> -	if (crtc->invert_dimensions)
> +	if (crtc->state->invert_dimensions)
>  		swap(hdisplay, vdisplay);
>  
>  	if (hdisplay > fb->width ||
> @@ -2504,7 +2717,7 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc,
>  	    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)" : "");
> +			      crtc->state->invert_dimensions ? " (inverted)" : "");
>  		return -ENOSPC;
>  	}
>  
> @@ -2531,22 +2744,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  	struct drm_mode_config *config = &dev->mode_config;
>  	struct drm_mode_crtc *crtc_req = data;
>  	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;
> +	struct drm_atomic_state *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);
>  	crtc = drm_crtc_find(dev, crtc_req->crtc_id);
>  	if (!crtc) {
>  		DRM_DEBUG_KMS("Unknown CRTC ID %d\n", crtc_req->crtc_id);
> @@ -2564,55 +2770,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  				ret = -EINVAL;
>  				goto out;
>  			}
> -			fb = crtc->primary->fb;
> -			/* Make refcounting symmetric with the lookup path. */
> -			drm_framebuffer_reference(fb);
> +			fb_id = crtc->primary->fb->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) {
> @@ -2620,52 +2786,65 @@ 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;
>  			}
> -
> -			connector = drm_connector_find(dev, out_id);
> -			if (!connector) {
> -				DRM_DEBUG_KMS("Connector id %d unknown\n",
> -						out_id);
> -				ret = -ENOENT;
> -				goto out;
> -			}
> -			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);
> +retry:
> +	state = dev->driver->atomic_begin(dev, 0);
> +	if (IS_ERR(state))
> +		return PTR_ERR(state);
>  
> -out:
> -	if (fb)
> -		drm_framebuffer_unreference(fb);
> +	/* If connectors change, we need to check if we need to steal one
> +	 * from another CRTC..  setcrtc makes this implicit, but atomic
> +	 * treats it as an error so we need to handle here:
> +	 */
> +	ret = check_connectors(crtc, state, true,
> +		connector_ids, crtc_req->count_connectors);
> +	if (ret)
> +		goto out;
>  
> -	kfree(connector_set);
> -	drm_mode_destroy(dev, mode);
> -	drm_modeset_unlock_all(dev);
> +	ret =
> +		drm_mode_crtc_set_obj_prop(crtc, state,
> +			config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
> +		drm_mode_crtc_set_obj_prop(crtc, state,
> +			config->prop_connector_ids,
> +			crtc_req->count_connectors * sizeof(connector_ids[0]),
> +			connector_ids) ||
> +		drm_mode_plane_set_obj_prop(crtc->primary, state,
> +			config->prop_crtc_id, crtc->base.id, NULL) ||
> +		drm_mode_plane_set_obj_prop(crtc->primary, state,
> +			config->prop_fb_id, fb_id, NULL) ||
> +		drm_mode_plane_set_obj_prop(crtc->primary, state,
> +			config->prop_src_x, crtc_req->x << 16, NULL) ||
> +		drm_mode_plane_set_obj_prop(crtc->primary, state,
> +			config->prop_src_y, crtc_req->y << 16, NULL) ||
> +		dev->driver->atomic_check(dev, state);
> +	if (ret)
> +		goto out;
> +
> +	ret = dev->driver->atomic_commit(dev, state);
> +
> +out:
> +	if (state)
> +		dev->driver->atomic_end(dev, state);
> +	if (ret == -EDEADLK)
> +		goto retry;
>  	return ret;
>  }
>  
> @@ -4028,9 +4207,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;
>  }
> @@ -4424,6 +4600,51 @@ 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);
> +}
> +
>  /**
>   * drm_mode_page_flip_ioctl - schedule an asynchronous fb update
>   * @dev: DRM device
> @@ -4446,10 +4667,10 @@ 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_crtc *crtc;
> -	struct drm_framebuffer *fb = NULL, *old_fb = NULL;
>  	struct drm_pending_vblank_event *e = NULL;
> -	unsigned long flags;
> +	struct drm_atomic_state *state;
>  	int ret = -EINVAL;
>  
>  	if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
> @@ -4463,92 +4684,41 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
>  	if (!crtc)
>  		return -ENOENT;
>  
> -	drm_modeset_lock(&crtc->mutex, NULL);
> -	if (crtc->primary->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->primary->fb->pixel_format != fb->pixel_format) {
> -		DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
> -		ret = -EINVAL;
> -		goto out;
> -	}
> +retry:
> +	state = dev->driver->atomic_begin(dev,
> +			page_flip->flags | DRM_MODE_ATOMIC_NONBLOCK);
> +	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, &crtc->base, 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->primary->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->primary->fb != fb);
> -		/* Unref only the old framebuffer. */
> -		fb = NULL;
> -	}
> +	ret = drm_mode_plane_set_obj_prop(crtc->primary, 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);
> -	drm_modeset_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);
> +	if (ret == -EDEADLK)
> +		goto retry;
>  	return ret;
>  }
>  
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index b73d3b0..4669e69 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -286,7 +286,7 @@ bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  	struct drm_device *dev = fb_helper->dev;
>  	struct drm_plane *plane;
>  	bool error = false;
> -	void *state;
> +	struct drm_atomic_state *state;
>  	int i;
>  
>  	drm_warn_on_modeset_not_all_locked(dev);
> diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
> index 2a56973..f3c7e77 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.h>
>  
>  #include "exynos_drm_crtc.h"
>  #include "exynos_drm_drv.h"
> @@ -289,6 +290,10 @@ static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
>  	struct drm_device *dev = crtc->dev;
>  	struct exynos_drm_private *dev_priv = dev->dev_private;
>  	struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> +
> +	if (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
>  
>  	if (property == dev_priv->crtc_mode_property) {
>  		enum exynos_crtc_mode mode = val;
> @@ -313,7 +318,7 @@ static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
>  		return 0;
>  	}
>  
> -	return -EINVAL;
> +	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
>  }
>  
>  static struct drm_crtc_funcs exynos_crtc_funcs = {
> diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
> index 6672732..5b6eee9 100644
> --- a/drivers/gpu/drm/gma500/cdv_intel_display.c
> +++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
> @@ -989,6 +989,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_crtc_set_property,
>  	.destroy = gma_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
> index 87b50ba..79b5692 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_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 e9f6eb7..53b996f 100644
> --- a/drivers/gpu/drm/i915/intel_display.c
> +++ b/drivers/gpu/drm/i915/intel_display.c
> @@ -10430,6 +10430,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_crtc_set_property,
>  	.destroy = intel_crtc_destroy,
>  	.page_flip = intel_crtc_page_flip,
>  };
> diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
> index a034ed4..ba9bd91 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_crtc_set_property,
>  	.destroy = mga_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
> index 7cf0f78..d0d8befd 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
> @@ -471,8 +471,10 @@ static int mdp4_crtc_set_property(struct drm_crtc *crtc,
>  		struct drm_atomic_state *state, struct drm_property *property,
>  		uint64_t val, void *blob_data)
>  {
> -	// XXX
> -	return -EINVAL;
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> +	if (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
> +	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
>  }
>  
>  #define CURSOR_WIDTH 64
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
> index 771390b..7f4ee99 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
> @@ -389,8 +389,10 @@ static int mdp5_crtc_set_property(struct drm_crtc *crtc,
>  		struct drm_atomic_state *state, struct drm_property *property,
>  		uint64_t val, void *blob_data)
>  {
> -	// XXX
> -	return -EINVAL;
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> +	if (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
> +	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
>  }
>  
>  static const struct drm_crtc_funcs mdp5_crtc_funcs = {
> diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
> index 41be342..9e24632 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_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 58af547..ecbffeb 100644
> --- a/drivers/gpu/drm/nouveau/nv50_display.c
> +++ b/drivers/gpu/drm/nouveau/nv50_display.c
> @@ -1329,6 +1329,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_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 a75934d..772687b 100644
> --- a/drivers/gpu/drm/omapdrm/omap_crtc.c
> +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
> @@ -387,14 +387,22 @@ static int omap_crtc_set_property(struct drm_crtc *crtc,
>  {
>  	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 (IS_ERR(cstate))
> +		return PTR_ERR(cstate);
>  
>  	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 = {
> diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
> index da80bdc..3f64c47 100644
> --- a/drivers/gpu/drm/omapdrm/omap_drv.c
> +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
> @@ -579,7 +579,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);
>  		}
>  
> diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
> index b54c970..25896a9 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.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_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 8d99d5e..cc86aac 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.h>
>  #include <drm/drm_edid.h>
>  
>  #include <linux/gcd.h>
> @@ -546,6 +547,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_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 299267d..f5a3d55 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.h>
>  #include <drm/drm_fb_cma_helper.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> @@ -527,6 +528,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_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 90e023a..0a5280c 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.h>
>  #include <drm/drm_fb_cma_helper.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> @@ -506,6 +507,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_crtc_set_property,
>  	.page_flip = shmob_drm_crtc_page_flip,
>  };
>  
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
> index 92839ba..b07f116 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_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 cddc4fc..36d0116 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.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_crtc_set_property,
>  	.destroy = udl_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> index b2b9bd2..0313b00 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> @@ -300,6 +300,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_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 a95d3a0..b723e09 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
> @@ -397,6 +397,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_crtc_set_property,
>  	.page_flip = vmw_du_page_flip,
>  };
>  
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 78e93ec..7946b7f 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -70,6 +70,9 @@
>  struct drm_atomic_funcs {
>  	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);
> +
> +	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_funcs drm_atomic_funcs;
> @@ -109,6 +112,30 @@ drm_atomic_commit_plane_state(struct drm_plane *plane,
>  	return funcs->commit_plane_state(plane, pstate);
>  }
>  
> +int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
> +		struct drm_atomic_state *state, struct drm_property *property,
> +		uint64_t val, void *blob_data);
> +struct drm_crtc_state *drm_atomic_get_crtc_state(struct drm_crtc *crtc,
> +		struct drm_atomic_state *state);
> +
> +static inline int
> +drm_atomic_check_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	const struct drm_atomic_funcs *funcs =
> +			crtc->dev->driver->atomic_funcs;
> +	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_funcs *funcs =
> +			crtc->dev->driver->atomic_funcs;
> +	return funcs->commit_crtc_state(crtc, cstate);
> +}
> +
>  /**
>   * struct drm_atomic_state - the state object used by atomic helpers
>   */
> @@ -118,6 +145,8 @@ struct drm_atomic_state {
>  	uint32_t flags;
>  	struct drm_plane **planes;
>  	struct drm_plane_state **pstates;
> +	struct drm_crtc **crtcs;
> +	struct drm_crtc_state **cstates;
>  
>  	bool committed;
>  	bool checked;       /* just for debugging */
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index 58309cc..2fbf13a 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -284,6 +284,10 @@ struct drm_crtc_funcs {
>  			 struct drm_pending_vblank_event *event,
>  			 uint32_t flags);
>  
> +	struct drm_crtc_state *(*create_state)(struct drm_crtc *crtc);
> +	void (*destroy_state)(struct drm_crtc *crtc,
> +			    struct drm_crtc_state *cstate);
> +
>  	int (*set_property)(struct drm_crtc *crtc,
>  			    struct drm_atomic_state *state,
>  			    struct drm_property *property, uint64_t val,
> @@ -291,21 +295,52 @@ 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
> + * @mode_valid: a valid mode has been set
> + * @set_config: needs modeset (crtc->set_config())
> + * @connectors_change: the connector-ids array has changed
> + * @num_connector_ids: the number of connector-ids
> + * @connector_ids: array of connector ids
> + * @mode: current mode timings
> + * @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 mode_valid        : 1;
> +
> +	/* transient state, only valid during atomic operation: */
> +	bool set_config        : 1;
> +	bool connectors_change : 1;
> +
> +	uint8_t num_connector_ids;
> +	uint32_t *connector_ids;
> +	struct drm_mode_modeinfo mode;
> +
> +	struct drm_pending_vblank_event *event;
> +
> +	struct drm_object_property_values propvals;
> +
> +	struct drm_atomic_state *state;
> +};
> +
> +/**
>   * drm_crtc - central CRTC control structure
>   * @dev: parent DRM device
>   * @head: list management
> + * @id: CRTC number, 0..n
>   * @mutex: per-CRTC locking
>   * @base: base KMS object for ID tracking etc.
>   * @primary: primary plane for this CRTC
>   * @cursor: cursor plane for this CRTC
> + * @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
> @@ -322,6 +357,8 @@ struct drm_crtc {
>  	struct drm_device *dev;
>  	struct list_head head;
>  
> +	int id;
> +
>  	/**
>  	 * crtc mutex
>  	 *
> @@ -337,23 +374,19 @@ struct drm_crtc {
>  	struct drm_plane *primary;
>  	struct drm_plane *cursor;
>  
> +	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. */
>  	struct drm_framebuffer *old_fb;
>  
>  	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 */
> @@ -367,9 +400,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:
> +	 */
> +	int x, y;
> +	struct drm_display_mode mode;
> +};
>  
>  /**
>   * drm_connector_funcs - control connectors on a given device
> @@ -875,6 +914,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;
>  	struct drm_property *plane_type_property;
> @@ -935,7 +976,8 @@ 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 unsigned int drm_crtc_index(struct drm_crtc *crtc);
> +struct drm_display_mode *drm_crtc_get_mode(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate);
>  
>  /**
>   * drm_crtc_mask - find the mask of a registered CRTC
> @@ -946,9 +988,18 @@ extern unsigned int drm_crtc_index(struct drm_crtc *crtc);
>   */
>  static inline uint32_t drm_crtc_mask(struct drm_crtc *crtc)
>  {
> -	return 1 << drm_crtc_index(crtc);
> +	return 1 << crtc->id;
>  }
>  
> +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);
>  extern int drm_connector_init(struct drm_device *dev,
> @@ -1024,6 +1075,7 @@ 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 void drm_mode_group_destroy(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);
> @@ -1251,6 +1303,25 @@ drm_property_blob_find(struct drm_device *dev, uint32_t id)
>  	return mo ? obj_to_blob(mo) : NULL;
>  }
>  
> +static inline struct drm_crtc_state *
> +drm_crtc_create_state(struct drm_crtc *crtc)
> +{
> +	if (crtc->funcs->create_state)
> +		return crtc->funcs->create_state(crtc);
> +	return kzalloc(sizeof(struct drm_crtc_state), GFP_KERNEL);
> +}
> +
> +static inline void
> +drm_crtc_destroy_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	kfree(cstate->connector_ids);
> +	if (crtc->funcs->destroy_state)
> +		crtc->funcs->destroy_state(crtc, cstate);
> +	else
> +		kfree(cstate);
> +}
> +
>  static inline struct drm_plane_state *
>  drm_plane_create_state(struct drm_plane *plane)
>  {
> -- 
> 1.9.0
>
Rob Clark May 26, 2014, 11:35 a.m. UTC | #2
On Mon, May 26, 2014 at 5:31 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
>> +struct drm_crtc_state *
>> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, struct drm_atomic_state *a)
>> +{
>> +     struct drm_crtc_state *cstate;
>> +     int ret;
>> +
>> +     cstate = a->cstates[crtc->id];
>> +
>> +     if (!cstate) {
>> +             ret = drm_modeset_lock(&crtc->mutex, &a->acquire_ctx);
>> +             if (ret)
>> +                     return ERR_PTR(ret);
>> +
>> +             cstate = drm_crtc_create_state(crtc);
>> +             if (!cstate)
>> +                     return ERR_PTR(-ENOMEM);
>> +             init_crtc_state(crtc, cstate, a);
>> +             a->crtcs[crtc->id] = crtc;
>> +             a->cstates[crtc->id] = cstate;
>> +
>> +             /* we'll need it later, so make sure we have state
>> +              * for primary plane too:
>> +              */
>> +             drm_atomic_get_plane_state(crtc->primary, a);
>
> I haven't figured out why. With primary planes I don't really see a need
> for this. If we need it to implement the legacy setcrtc interface, then
> that should be done there, not here.


well, if you sort out how to disable primary helper plane, then yes,
you are right :-)

see commit_crtc_state()

BR,
-R
Daniel Vetter May 26, 2014, 2:56 p.m. UTC | #3
On Mon, May 26, 2014 at 07:35:45AM -0400, Rob Clark wrote:
> On Mon, May 26, 2014 at 5:31 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> >> +struct drm_crtc_state *
> >> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, struct drm_atomic_state *a)
> >> +{
> >> +     struct drm_crtc_state *cstate;
> >> +     int ret;
> >> +
> >> +     cstate = a->cstates[crtc->id];
> >> +
> >> +     if (!cstate) {
> >> +             ret = drm_modeset_lock(&crtc->mutex, &a->acquire_ctx);
> >> +             if (ret)
> >> +                     return ERR_PTR(ret);
> >> +
> >> +             cstate = drm_crtc_create_state(crtc);
> >> +             if (!cstate)
> >> +                     return ERR_PTR(-ENOMEM);
> >> +             init_crtc_state(crtc, cstate, a);
> >> +             a->crtcs[crtc->id] = crtc;
> >> +             a->cstates[crtc->id] = cstate;
> >> +
> >> +             /* we'll need it later, so make sure we have state
> >> +              * for primary plane too:
> >> +              */
> >> +             drm_atomic_get_plane_state(crtc->primary, a);
> >
> > I haven't figured out why. With primary planes I don't really see a need
> > for this. If we need it to implement the legacy setcrtc interface, then
> > that should be done there, not here.
> 
> 
> well, if you sort out how to disable primary helper plane, then yes,
> you are right :-)
> 
> see commit_crtc_state()

Imo bail when we have a crtc with NULL primary plane in crtc_commit (and
wont disable the entire crtc ofc).

Also I think your current commit_crtc should be pushed down into the crtc
helpers - it's not going to do any good for i915. But I guess until we
have a real user of the atomic interface (i.e. updates more than 1 crtc)
like the fb helper code we don't need this yet.

But as soon as we update more than one crtc we _must_ push it down into
the crtc helpers or the i915 machinery - if you try to enable crtcs which
need resources from crtcs which aren't yet off this is simply going to
fail.
-Daniel
Rob Clark May 26, 2014, 3:15 p.m. UTC | #4
On Mon, May 26, 2014 at 10:56 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Mon, May 26, 2014 at 07:35:45AM -0400, Rob Clark wrote:
>> On Mon, May 26, 2014 at 5:31 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
>> >> +struct drm_crtc_state *
>> >> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, struct drm_atomic_state *a)
>> >> +{
>> >> +     struct drm_crtc_state *cstate;
>> >> +     int ret;
>> >> +
>> >> +     cstate = a->cstates[crtc->id];
>> >> +
>> >> +     if (!cstate) {
>> >> +             ret = drm_modeset_lock(&crtc->mutex, &a->acquire_ctx);
>> >> +             if (ret)
>> >> +                     return ERR_PTR(ret);
>> >> +
>> >> +             cstate = drm_crtc_create_state(crtc);
>> >> +             if (!cstate)
>> >> +                     return ERR_PTR(-ENOMEM);
>> >> +             init_crtc_state(crtc, cstate, a);
>> >> +             a->crtcs[crtc->id] = crtc;
>> >> +             a->cstates[crtc->id] = cstate;
>> >> +
>> >> +             /* we'll need it later, so make sure we have state
>> >> +              * for primary plane too:
>> >> +              */
>> >> +             drm_atomic_get_plane_state(crtc->primary, a);
>> >
>> > I haven't figured out why. With primary planes I don't really see a need
>> > for this. If we need it to implement the legacy setcrtc interface, then
>> > that should be done there, not here.
>>
>>
>> well, if you sort out how to disable primary helper plane, then yes,
>> you are right :-)
>>
>> see commit_crtc_state()
>
> Imo bail when we have a crtc with NULL primary plane in crtc_commit (and
> wont disable the entire crtc ofc).
>
> Also I think your current commit_crtc should be pushed down into the crtc
> helpers - it's not going to do any good for i915. But I guess until we
> have a real user of the atomic interface (i.e. updates more than 1 crtc)
> like the fb helper code we don't need this yet.

it is in helpers (if we rename drm_atomic_funcs ->
drm_atomic_helper_funcs, which is basically what it is).

I suspect what you actually want is to split up atomic_commit() so you
can do the loop over all the planes and crtcs internally.

BR,
-R

> But as soon as we update more than one crtc we _must_ push it down into
> the crtc helpers or the i915 machinery - if you try to enable crtcs which
> need resources from crtcs which aren't yet off this is simply going to
> fail.
> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Ville Syrjälä May 26, 2014, 3:23 p.m. UTC | #5
On Mon, May 26, 2014 at 11:31:10AM +0200, Daniel Vetter wrote:
> On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
> > 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.
> > 
> > Signed-off-by: Rob Clark <robdclark@gmail.com>
> 
> Same comments about interface design as for the plane patch apply here.
> One additional comment below.
> 
> > ---
> >  drivers/gpu/drm/armada/armada_crtc.c       |  11 +-
> >  drivers/gpu/drm/ast/ast_mode.c             |   1 +
> >  drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
> >  drivers/gpu/drm/drm_atomic.c               | 231 ++++++++++-
> >  drivers/gpu/drm/drm_crtc.c                 | 598 ++++++++++++++++++-----------
> >  drivers/gpu/drm/drm_fb_helper.c            |   2 +-
> >  drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   7 +-
> >  drivers/gpu/drm/gma500/cdv_intel_display.c |   1 +
> >  drivers/gpu/drm/gma500/psb_intel_display.c |   1 +
> >  drivers/gpu/drm/i915/intel_display.c       |   1 +
> >  drivers/gpu/drm/mgag200/mgag200_mode.c     |   1 +
> >  drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c   |   6 +-
> >  drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c   |   6 +-
> >  drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
> >  drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
> >  drivers/gpu/drm/omapdrm/omap_crtc.c        |  12 +-
> >  drivers/gpu/drm/omapdrm/omap_drv.c         |   2 +-
> >  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/tilcdc/tilcdc_crtc.c       |   1 +
> >  drivers/gpu/drm/udl/udl_modeset.c          |   2 +
> >  drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c        |   1 +
> >  drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c       |   1 +
> >  include/drm/drm_atomic.h                   |  29 ++
> >  include/drm/drm_crtc.h                     | 103 ++++-
> >  27 files changed, 785 insertions(+), 243 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
> > index 7d3c649..6237af4 100644
> > --- a/drivers/gpu/drm/armada/armada_crtc.c
> > +++ b/drivers/gpu/drm/armada/armada_crtc.c
> > @@ -9,6 +9,7 @@
> >  #include <linux/clk.h>
> >  #include <drm/drmP.h>
> >  #include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_atomic.h>
> >  #include "armada_crtc.h"
> >  #include "armada_drm.h"
> >  #include "armada_fb.h"
> > @@ -966,7 +967,12 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
> >  {
> >  	struct armada_private *priv = crtc->dev->dev_private;
> >  	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
> > +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> >  	bool update_csc = false;
> > +	int ret = 0;
> > +
> > +	if (IS_ERR(cstate))
> > +		return PTR_ERR(cstate);
> >  
> >  	if (property == priv->csc_yuv_prop) {
> >  		dcrtc->csc_yuv_mode = val;
> > @@ -974,6 +980,9 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
> >  	} else if (property == priv->csc_rgb_prop) {
> >  		dcrtc->csc_rgb_mode = val;
> >  		update_csc = true;
> > +	} else {
> > +		ret = drm_crtc_set_property(crtc, cstate, property,
> > +				val, blob_data);
> >  	}
> >  
> >  	if (update_csc) {
> > @@ -984,7 +993,7 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
> >  		writel_relaxed(val, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
> >  	}
> >  
> > -	return 0;
> > +	return ret;
> >  }
> >  
> >  static struct drm_crtc_funcs armada_crtc_funcs = {
> > diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
> > index 114aee9..c08e0e1 100644
> > --- a/drivers/gpu/drm/ast/ast_mode.c
> > +++ b/drivers/gpu/drm/ast/ast_mode.c
> > @@ -632,6 +632,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_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 49332c5..3c4428c 100644
> > --- a/drivers/gpu/drm/cirrus/cirrus_mode.c
> > +++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
> > @@ -366,6 +366,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_crtc_set_property,
> >  	.destroy = cirrus_crtc_destroy,
> >  };
> >  
> > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > index 403ffc5..863a0fe 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -48,11 +48,13 @@ struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
> >  {
> >  	struct drm_atomic_state *state;
> >  	int nplanes = dev->mode_config.num_total_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);
> >  
> > @@ -74,6 +76,12 @@ struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
> >  	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_begin);
> > @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
> >  		struct drm_atomic_state *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);
> > +		if (IS_ERR(cstate))
> > +			return PTR_ERR(cstate);
> > +		cstate->event = event;
> > +		return 0;
> > +	}
> > +	default:
> > +		return -EINVAL;
> > +	}
> 
> Hm, I think if we only want completion events on crtcs (which I agree on)

I don't. Unless you have a nice way of passing some kind of "fbs now
available for rendering" list back to userland in the single crtc
event. Last time I looked making the drm event stuff deal with
variable length events looked more painful than just adding per
plane events. But I must admit that I didn't really try to do it.
Daniel Vetter May 26, 2014, 3:37 p.m. UTC | #6
On Mon, May 26, 2014 at 06:23:05PM +0300, Ville Syrjälä wrote:
> On Mon, May 26, 2014 at 11:31:10AM +0200, Daniel Vetter wrote:
> > On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
> > > @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
> > >  		struct drm_atomic_state *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);
> > > +		if (IS_ERR(cstate))
> > > +			return PTR_ERR(cstate);
> > > +		cstate->event = event;
> > > +		return 0;
> > > +	}
> > > +	default:
> > > +		return -EINVAL;
> > > +	}
> > 
> > Hm, I think if we only want completion events on crtcs (which I agree on)
> 
> I don't. Unless you have a nice way of passing some kind of "fbs now
> available for rendering" list back to userland in the single crtc
> event. Last time I looked making the drm event stuff deal with
> variable length events looked more painful than just adding per
> plane events. But I must admit that I didn't really try to do it.

Hm, why can't userspace keep a list of fb ids involved in a given crtc
pageflip around and index those with the cookie we pass around?

I really don't see why the kernel has to implement a half-baked event
multiplexer (which just copies the same thing n times), while userspace is
perfectly capable of doing that itself?

Now if the timestamps would be genuinely different then I'd agree, but
then that's not an atomic update.
-Daniel
Rob Clark May 26, 2014, 3:42 p.m. UTC | #7
On Mon, May 26, 2014 at 11:37 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Mon, May 26, 2014 at 06:23:05PM +0300, Ville Syrjälä wrote:
>> On Mon, May 26, 2014 at 11:31:10AM +0200, Daniel Vetter wrote:
>> > On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
>> > > @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
>> > >           struct drm_atomic_state *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);
>> > > +         if (IS_ERR(cstate))
>> > > +                 return PTR_ERR(cstate);
>> > > +         cstate->event = event;
>> > > +         return 0;
>> > > + }
>> > > + default:
>> > > +         return -EINVAL;
>> > > + }
>> >
>> > Hm, I think if we only want completion events on crtcs (which I agree on)
>>
>> I don't. Unless you have a nice way of passing some kind of "fbs now
>> available for rendering" list back to userland in the single crtc
>> event. Last time I looked making the drm event stuff deal with
>> variable length events looked more painful than just adding per
>> plane events. But I must admit that I didn't really try to do it.
>
> Hm, why can't userspace keep a list of fb ids involved in a given crtc
> pageflip around and index those with the cookie we pass around?
>
> I really don't see why the kernel has to implement a half-baked event
> multiplexer (which just copies the same thing n times), while userspace is
> perfectly capable of doing that itself?
>
> Now if the timestamps would be genuinely different then I'd agree, but
> then that's not an atomic update.

I do kinda think userspace would want to know somehow if an fb is
going to be really late and explicitly submit an older fb.  If you
were resizing, for example, I don't think the driver could make the
call to leave one plane displaying an older fb.

But I do think we can bikeshed about that in parallel with getting the
inital atomic infrastructure merged.

BR,
-R

> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Ville Syrjälä May 26, 2014, 3:46 p.m. UTC | #8
On Mon, May 26, 2014 at 05:37:57PM +0200, Daniel Vetter wrote:
> On Mon, May 26, 2014 at 06:23:05PM +0300, Ville Syrjälä wrote:
> > On Mon, May 26, 2014 at 11:31:10AM +0200, Daniel Vetter wrote:
> > > On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
> > > > @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
> > > >  		struct drm_atomic_state *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);
> > > > +		if (IS_ERR(cstate))
> > > > +			return PTR_ERR(cstate);
> > > > +		cstate->event = event;
> > > > +		return 0;
> > > > +	}
> > > > +	default:
> > > > +		return -EINVAL;
> > > > +	}
> > > 
> > > Hm, I think if we only want completion events on crtcs (which I agree on)
> > 
> > I don't. Unless you have a nice way of passing some kind of "fbs now
> > available for rendering" list back to userland in the single crtc
> > event. Last time I looked making the drm event stuff deal with
> > variable length events looked more painful than just adding per
> > plane events. But I must admit that I didn't really try to do it.
> 
> Hm, why can't userspace keep a list of fb ids involved in a given crtc
> pageflip around and index those with the cookie we pass around?
> 
> I really don't see why the kernel has to implement a half-baked event
> multiplexer (which just copies the same thing n times), while userspace is
> perfectly capable of doing that itself?
> 
> Now if the timestamps would be genuinely different then I'd agree, but
> then that's not an atomic update.

User space can't do it if flips get issued faster than the vrefresh
rate. I still want my "moar fps" triple buffering mode.
Daniel Vetter May 26, 2014, 4:12 p.m. UTC | #9
On Mon, May 26, 2014 at 06:46:30PM +0300, Ville Syrjälä wrote:
> On Mon, May 26, 2014 at 05:37:57PM +0200, Daniel Vetter wrote:
> > On Mon, May 26, 2014 at 06:23:05PM +0300, Ville Syrjälä wrote:
> > > On Mon, May 26, 2014 at 11:31:10AM +0200, Daniel Vetter wrote:
> > > > On Sat, May 24, 2014 at 02:30:21PM -0400, Rob Clark wrote:
> > > > > @@ -92,7 +100,18 @@ int drm_atomic_set_event(struct drm_device *dev,
> > > > >  		struct drm_atomic_state *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);
> > > > > +		if (IS_ERR(cstate))
> > > > > +			return PTR_ERR(cstate);
> > > > > +		cstate->event = event;
> > > > > +		return 0;
> > > > > +	}
> > > > > +	default:
> > > > > +		return -EINVAL;
> > > > > +	}
> > > > 
> > > > Hm, I think if we only want completion events on crtcs (which I agree on)
> > > 
> > > I don't. Unless you have a nice way of passing some kind of "fbs now
> > > available for rendering" list back to userland in the single crtc
> > > event. Last time I looked making the drm event stuff deal with
> > > variable length events looked more painful than just adding per
> > > plane events. But I must admit that I didn't really try to do it.
> > 
> > Hm, why can't userspace keep a list of fb ids involved in a given crtc
> > pageflip around and index those with the cookie we pass around?
> > 
> > I really don't see why the kernel has to implement a half-baked event
> > multiplexer (which just copies the same thing n times), while userspace is
> > perfectly capable of doing that itself?
> > 
> > Now if the timestamps would be genuinely different then I'd agree, but
> > then that's not an atomic update.
> 
> User space can't do it if flips get issued faster than the vrefresh
> rate. I still want my "moar fps" triple buffering mode.

Hm right, I forget about this every time it comes up. Imo we should
postpone this to when it actually gets implemented - usualy preemptive
generalism just ends up hurting everyone for now real good. For the
current logic we could ditch all the indirection and callbacks really and
simply require that the ->commit hook sends out the crtc even if it's
there.

And even if we add per-plane events we don't need new hooks imo. Just
adding an event pointer to the plane state structure and firing off those
events together with the crtc one in drivers should be good enough.

The proposed set_event callback plus implementation otoh are serious
overkill.
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c
index 7d3c649..6237af4 100644
--- a/drivers/gpu/drm/armada/armada_crtc.c
+++ b/drivers/gpu/drm/armada/armada_crtc.c
@@ -9,6 +9,7 @@ 
 #include <linux/clk.h>
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic.h>
 #include "armada_crtc.h"
 #include "armada_drm.h"
 #include "armada_fb.h"
@@ -966,7 +967,12 @@  armada_drm_crtc_set_property(struct drm_crtc *crtc,
 {
 	struct armada_private *priv = crtc->dev->dev_private;
 	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
 	bool update_csc = false;
+	int ret = 0;
+
+	if (IS_ERR(cstate))
+		return PTR_ERR(cstate);
 
 	if (property == priv->csc_yuv_prop) {
 		dcrtc->csc_yuv_mode = val;
@@ -974,6 +980,9 @@  armada_drm_crtc_set_property(struct drm_crtc *crtc,
 	} else if (property == priv->csc_rgb_prop) {
 		dcrtc->csc_rgb_mode = val;
 		update_csc = true;
+	} else {
+		ret = drm_crtc_set_property(crtc, cstate, property,
+				val, blob_data);
 	}
 
 	if (update_csc) {
@@ -984,7 +993,7 @@  armada_drm_crtc_set_property(struct drm_crtc *crtc,
 		writel_relaxed(val, dcrtc->base + LCD_SPU_IOPAD_CONTROL);
 	}
 
-	return 0;
+	return ret;
 }
 
 static struct drm_crtc_funcs armada_crtc_funcs = {
diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
index 114aee9..c08e0e1 100644
--- a/drivers/gpu/drm/ast/ast_mode.c
+++ b/drivers/gpu/drm/ast/ast_mode.c
@@ -632,6 +632,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_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 49332c5..3c4428c 100644
--- a/drivers/gpu/drm/cirrus/cirrus_mode.c
+++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
@@ -366,6 +366,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_crtc_set_property,
 	.destroy = cirrus_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 403ffc5..863a0fe 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -48,11 +48,13 @@  struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
 {
 	struct drm_atomic_state *state;
 	int nplanes = dev->mode_config.num_total_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);
 
@@ -74,6 +76,12 @@  struct drm_atomic_state *drm_atomic_begin(struct drm_device *dev,
 	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_begin);
@@ -92,7 +100,18 @@  int drm_atomic_set_event(struct drm_device *dev,
 		struct drm_atomic_state *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);
+		if (IS_ERR(cstate))
+			return PTR_ERR(cstate);
+		cstate->event = event;
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
 }
 EXPORT_SYMBOL(drm_atomic_set_event);
 
@@ -111,6 +130,7 @@  int drm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
 {
 	struct drm_atomic_state *a = state;
 	int nplanes = dev->mode_config.num_total_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i, ret = 0;
 
 	for (i = 0; i < nplanes; i++) {
@@ -120,6 +140,13 @@  int drm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
 				break;
 		}
 	}
+	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;
+		}
+	}
 
 	a->acquire_ctx.frozen = true;
 
@@ -203,6 +230,7 @@  static void commit_locks(struct drm_atomic_state *a,
 {
 	struct drm_device *dev = a->dev;
 	int nplanes = dev->mode_config.num_total_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i;
 
 	for (i = 0; i < nplanes; i++) {
@@ -213,6 +241,14 @@  static void commit_locks(struct drm_atomic_state *a,
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		struct drm_crtc *crtc = a->crtcs[i];
+		if (crtc) {
+			crtc->state->state = NULL;
+			drm_crtc_destroy_state(crtc, a->cstates[i]);
+		}
+	}
+
 	/* and properly release them (clear in_atomic, remove from list): */
 	drm_modeset_drop_locks(&a->acquire_ctx);
 	ww_acquire_fini(ww_ctx);
@@ -223,8 +259,18 @@  static int atomic_commit(struct drm_atomic_state *a,
 		struct ww_acquire_ctx *ww_ctx)
 {
 	int nplanes = a->dev->mode_config.num_total_plane;
+	int ncrtcs = a->dev->mode_config.num_crtc;
 	int i, ret = 0;
 
+	for (i = 0; i < ncrtcs; i++) {
+		struct drm_crtc *crtc = a->crtcs[i];
+		if (crtc) {
+			ret = drm_atomic_commit_crtc_state(crtc, a->cstates[i]);
+			if (ret)
+				break;
+		}
+	}
+
 	for (i = 0; i < nplanes; i++) {
 		struct drm_plane *plane = a->planes[i];
 		if (plane) {
@@ -403,6 +449,7 @@  static int
 commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
 {
 	struct drm_atomic_state *a = pstate->state;
+	struct drm_crtc_state *cstate = NULL;
 	struct drm_framebuffer *old_fb = plane->fb;
 	struct drm_framebuffer *fb = pstate->fb;
 	bool enabled = pstate->crtc && fb;
@@ -425,8 +472,11 @@  commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
 		}
 	} else {
 		struct drm_crtc *crtc = pstate->crtc;
+		cstate = drm_atomic_get_crtc_state(crtc, pstate->state);
 		if (pstate->update_plane ||
 				(pstate->new_fb && !can_flip(plane, pstate))) {
+/* TODO pass event to update_plane().. */
+WARN_ON(cstate->event);
 			ret = plane->funcs->update_plane(plane, crtc, pstate->fb,
 					pstate->crtc_x, pstate->crtc_y,
 					pstate->crtc_w, pstate->crtc_h,
@@ -443,7 +493,7 @@  commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
 			}
 
 		} else if (pstate->new_fb) {
-			ret = crtc->funcs->page_flip(crtc, fb, NULL, a->flags);
+			ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
 			if (ret == 0) {
 				/*
 				 * Warn if the driver hasn't properly updated the plane->fb
@@ -473,9 +523,10 @@  commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
 		 * original code.
 		 */
 		swap_plane_state(plane, pstate->state);
+		if (cstate)
+			cstate->event = NULL;
 	}
 
-
 	if (fb)
 		drm_framebuffer_unreference(fb);
 	if (old_fb)
@@ -484,8 +535,182 @@  commit_plane_state(struct drm_plane *plane, struct drm_plane_state *pstate)
 	return ret;
 }
 
+int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_atomic_state *state, struct drm_property *property,
+		uint64_t val, void *blob_data)
+{
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+	if (IS_ERR(cstate))
+		return PTR_ERR(cstate);
+	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
+}
+EXPORT_SYMBOL(drm_atomic_crtc_set_property);
+
+static void init_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate, struct drm_atomic_state *state)
+{
+	/* snapshot current state: */
+	*cstate = *crtc->state;
+	cstate->state = state;
+
+	if (cstate->connector_ids) {
+		int sz = cstate->num_connector_ids * sizeof(cstate->connector_ids[0]);
+		cstate->connector_ids = kmemdup(cstate->connector_ids, sz, GFP_KERNEL);
+	}
+
+	/* this should never happen.. but make sure! */
+	WARN_ON(cstate->event);
+	cstate->event = NULL;
+}
+
+struct drm_crtc_state *
+drm_atomic_get_crtc_state(struct drm_crtc *crtc, struct drm_atomic_state *a)
+{
+	struct drm_crtc_state *cstate;
+	int ret;
+
+	cstate = a->cstates[crtc->id];
+
+	if (!cstate) {
+		ret = drm_modeset_lock(&crtc->mutex, &a->acquire_ctx);
+		if (ret)
+			return ERR_PTR(ret);
+
+		cstate = drm_crtc_create_state(crtc);
+		if (!cstate)
+			return ERR_PTR(-ENOMEM);
+		init_crtc_state(crtc, cstate, a);
+		a->crtcs[crtc->id] = crtc;
+		a->cstates[crtc->id] = cstate;
+
+		/* we'll need it later, so make sure we have state
+		 * for primary plane too:
+		 */
+		drm_atomic_get_plane_state(crtc->primary, a);
+	}
+	return cstate;
+}
+EXPORT_SYMBOL(drm_atomic_get_crtc_state);
+
+static void
+swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_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);
+	}
+
+	/* clear transient state (only valid during atomic update): */
+	cstate->set_config = false;
+	cstate->connectors_change = false;
+
+	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 int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_plane_state *pstate =
+			drm_atomic_get_plane_state(crtc->primary, cstate->state);
+	struct drm_framebuffer *fb = pstate->fb;
+	struct drm_connector **connector_set = get_connector_set(crtc->dev,
+			cstate->connector_ids, cstate->num_connector_ids);
+	struct drm_display_mode *mode = drm_crtc_get_mode(crtc, cstate);
+	struct drm_mode_set set = {
+			.crtc = crtc,
+			.x = pstate->src_x >> 16,
+			.y = pstate->src_y >> 16,
+			.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;
+	}
+
+	if (fb)
+		drm_framebuffer_reference(fb);
+
+	ret = drm_mode_set_config_internal(&set);
+	if (!ret) {
+		swap_crtc_state(crtc, cstate->state);
+		pstate->new_fb = pstate->update_plane = false;
+	}
+
+	if (fb)
+		drm_framebuffer_unreference(fb);
+
+	kfree(connector_set);
+	if (mode)
+		drm_mode_destroy(dev, mode);
+	return ret;
+}
+
+static int
+commit_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	struct drm_plane_state *pstate =
+			drm_atomic_get_plane_state(crtc->primary, cstate->state);
+	int ret = -EINVAL;
+
+	if (cstate->set_config)
+		return set_config(crtc, cstate);
+
+	if (!pstate->fb) {
+		/* disable */
+		struct drm_mode_set set = {
+				.crtc = crtc,
+				.fb = NULL,
+		};
+
+		ret = drm_mode_set_config_internal(&set);
+		if (!ret) {
+			swap_crtc_state(crtc, cstate->state);
+		}
+	}
+
+	return ret;
+}
+
 const struct drm_atomic_funcs drm_atomic_funcs = {
 		.check_plane_state  = drm_plane_check_state,
 		.commit_plane_state = commit_plane_state,
+
+		.check_crtc_state   = drm_crtc_check_state,
+		.commit_crtc_state  = commit_crtc_state,
 };
 EXPORT_SYMBOL(drm_atomic_funcs);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index b556a31..e14d517 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -689,10 +689,7 @@  EXPORT_SYMBOL(drm_framebuffer_cleanup);
 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));
 
@@ -712,7 +709,7 @@  void drm_framebuffer_remove(struct drm_framebuffer *fb)
 	 * in this manner.
 	 */
 	if (atomic_read(&fb->refcount.refcount) > 1) {
-		void *state;
+		struct drm_atomic_state *state;
 
 		state = dev->driver->atomic_begin(dev, 0);
 		if (IS_ERR(state)) {
@@ -720,24 +717,7 @@  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_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->primary->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);
-			}
-		}
-
+		/* remove from any plane */
 		list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
 			if (plane->fb == fb)
 				drm_plane_force_disable(plane, state);
@@ -750,8 +730,6 @@  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);
@@ -782,9 +760,13 @@  int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
 	struct drm_mode_config *config = &dev->mode_config;
 	int ret;
 
+	/* this is now required: */
+	WARN_ON(!funcs->set_property);
+
 	crtc->dev = dev;
 	crtc->funcs = funcs;
-	crtc->invert_dimensions = false;
+	crtc->state = drm_crtc_create_state(crtc);
+	crtc->state->invert_dimensions = false;
 
 	drm_modeset_lock_all(dev);
 	drm_modeset_lock_init(&crtc->mutex);
@@ -796,14 +778,17 @@  int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
 		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++;
 
 	crtc->primary = primary;
 	if (primary)
-		primary->possible_crtcs = 1 << drm_crtc_index(crtc);
+		primary->possible_crtcs = 1 << crtc->id;
+
+	drm_object_attach_property(&crtc->base, config->prop_mode, 0);
+	drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
 
  out:
 	drm_modeset_unlock_all(dev);
@@ -832,31 +817,245 @@  void drm_crtc_cleanup(struct drm_crtc *crtc)
 	drm_mode_object_put(dev, &crtc->base);
 	list_del(&crtc->head);
 	dev->mode_config.num_crtc--;
+
+	drm_crtc_destroy_state(crtc, crtc->state);
 }
 EXPORT_SYMBOL(drm_crtc_cleanup);
 
-/**
- * drm_crtc_index - find the index of a registered CRTC
- * @crtc: CRTC to find index for
- *
- * Given a registered CRTC, return the index of that CRTC within a DRM
- * device's list of CRTCs.
- */
-unsigned int drm_crtc_index(struct drm_crtc *crtc)
+/* get display-mode from user-mode */
+struct drm_display_mode *drm_crtc_get_mode(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
 {
-	unsigned int index = 0;
-	struct drm_crtc *tmp;
+	struct drm_display_mode *mode = NULL;
+	if (cstate->mode_valid) {
+		struct drm_device *dev = crtc->dev;
+		int ret;
 
-	list_for_each_entry(tmp, &crtc->dev->mode_config.crtc_list, head) {
-		if (tmp == crtc)
-			return index;
+		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);
+		}
 
-		index++;
+		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
 	}
+	return mode;
+}
 
-	BUG();
+static int connector_idx(struct drm_crtc_state *state,
+		uint32_t connector_id)
+{
+	int i;
+	for (i = 0; i < state->num_connector_ids; i++)
+		if (state->connector_ids[i] == connector_id)
+			return i;
+	return -1;
+}
+
+static int remove_connector(struct drm_crtc *ocrtc,
+		struct drm_crtc_state *ostate, struct drm_atomic_state *state,
+		int idx)
+{
+	struct drm_mode_config *config = &ocrtc->dev->mode_config;
+	uint32_t *new_connector_ids;
+	int a, b;
+
+	/* before deletion point: */
+	a = idx * sizeof(ostate->connector_ids[0]);
+
+	/* after deletion point: */
+	b = (ostate->num_connector_ids - 1 - idx) *
+			sizeof(ostate->connector_ids[0]);
+
+	new_connector_ids = kmalloc(a+b, GFP_KERNEL);
+	if (!new_connector_ids)
+		return -ENOMEM;
+
+	memcpy(new_connector_ids, ostate->connector_ids, a);
+	memcpy(&new_connector_ids[idx],
+			&ostate->connector_ids[idx + 1], b);
+
+	return drm_mode_crtc_set_obj_prop(ocrtc, state,
+		config->prop_connector_ids, a + b,
+		new_connector_ids);
 }
-EXPORT_SYMBOL(drm_crtc_index);
+
+static int check_connectors(struct drm_crtc *crtc,
+		struct drm_atomic_state *state, bool fix,
+		uint32_t *connector_ids, uint32_t num_connector_ids)
+{
+	struct drm_mode_config *config = &crtc->dev->mode_config;
+	struct drm_crtc *ocrtc; /* other connector */
+
+	list_for_each_entry(ocrtc, &config->crtc_list, head) {
+		struct drm_crtc_state *ostate; /* other state */
+		unsigned i;
+
+		if (ocrtc == crtc)
+			continue;
+
+		ostate = drm_atomic_get_crtc_state(crtc, state);
+		if (IS_ERR(ostate))
+			return PTR_ERR(ostate);
+
+		for (i = 0; i < num_connector_ids; i++) {
+			struct drm_connector *connector;
+			uint32_t cid = connector_ids[i];
+			int idx;
+
+retry:
+			idx = connector_idx(ostate, cid);
+			if (idx < 0)
+				continue;
+
+			if (fix) {
+				int ret = remove_connector(ocrtc,
+						ostate, state, idx);
+				if (ret)
+					return ret;
+				goto retry;
+			}
+
+			connector = drm_connector_find(crtc->dev, cid);
+			DRM_DEBUG_KMS("[CONNECTOR:%d:%s] already in use\n",
+					connector->base.id,
+					drm_get_connector_name(connector));
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+int drm_crtc_check_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	struct drm_plane *primary = crtc->primary;
+	struct drm_plane_state *pstate =
+			drm_atomic_get_plane_state(primary, state->state);
+	struct drm_framebuffer *fb = pstate->fb;
+	int hdisplay, vdisplay;
+	struct drm_display_mode *mode = drm_crtc_get_mode(crtc, state);
+	unsigned x, y;
+
+	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);
+
+	x = pstate->src_x >> 16;
+	y = pstate->src_y >> 16;
+
+	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, state->invert_dimensions ? " (inverted)" : "");
+		return -ENOSPC;
+	}
+
+	if (crtc->enabled && !state->set_config) {
+		if (primary->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 (state->connectors_change) {
+		int ret = check_connectors(crtc, state->state, false,
+				state->connector_ids, state->num_connector_ids);
+		if (ret)
+			return ret;
+	}
+
+	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;
+
+	/* grab primary plane state now, to ensure locks are held, etc. */
+	drm_atomic_get_plane_state(crtc->primary, state->state);
+
+	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) {
+		/* if connector-id's changing, we need to have all the locks: */
+		struct drm_atomic_state *a = state->state;
+		int ret = drm_modeset_lock_all_crtcs(crtc->dev, &a->acquire_ctx);
+		if (ret)
+			return ret;
+		state->connectors_change = true;
+		state->num_connector_ids = value / sizeof(state->connector_ids[0]);
+		kfree(state->connector_ids);
+		state->connector_ids = blob_data;
+		state->set_config = true;
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_set_property);
 
 /*
  * drm_mode_remove - remove and free a mode
@@ -1239,6 +1438,10 @@  int drm_plane_check_state(struct drm_plane *plane,
 	if (!fb)
 		return 0;
 
+	/* we'll need this later during commit: */
+	if (state->crtc)
+		drm_atomic_get_crtc_state(state->crtc, state->state);
+
 	fb_width = fb->width << 16;
 	fb_height = fb->height << 16;
 
@@ -1465,6 +1668,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;
 }
 
@@ -1754,7 +1967,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)
@@ -2001,8 +2214,8 @@  int drm_mode_getcrtc(struct drm_device *dev,
 		goto out;
 	}
 
-	crtc_resp->x = crtc->x;
-	crtc_resp->y = crtc->y;
+	crtc_resp->x = crtc->primary->state->src_x >> 16;
+	crtc_resp->y = crtc->primary->state->src_y >> 16;
 	crtc_resp->gamma_size = crtc->gamma_size;
 	if (crtc->primary->fb)
 		crtc_resp->fb_id = crtc->primary->fb->base.id;
@@ -2495,7 +2708,7 @@  int drm_crtc_check_viewport(const struct drm_crtc *crtc,
 		vdisplay = adjusted.crtc_vdisplay;
 	}
 
-	if (crtc->invert_dimensions)
+	if (crtc->state->invert_dimensions)
 		swap(hdisplay, vdisplay);
 
 	if (hdisplay > fb->width ||
@@ -2504,7 +2717,7 @@  int drm_crtc_check_viewport(const struct drm_crtc *crtc,
 	    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)" : "");
+			      crtc->state->invert_dimensions ? " (inverted)" : "");
 		return -ENOSPC;
 	}
 
@@ -2531,22 +2744,15 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 	struct drm_mode_config *config = &dev->mode_config;
 	struct drm_mode_crtc *crtc_req = data;
 	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;
+	struct drm_atomic_state *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);
 	crtc = drm_crtc_find(dev, crtc_req->crtc_id);
 	if (!crtc) {
 		DRM_DEBUG_KMS("Unknown CRTC ID %d\n", crtc_req->crtc_id);
@@ -2564,55 +2770,15 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 				ret = -EINVAL;
 				goto out;
 			}
-			fb = crtc->primary->fb;
-			/* Make refcounting symmetric with the lookup path. */
-			drm_framebuffer_reference(fb);
+			fb_id = crtc->primary->fb->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) {
@@ -2620,52 +2786,65 @@  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;
 			}
-
-			connector = drm_connector_find(dev, out_id);
-			if (!connector) {
-				DRM_DEBUG_KMS("Connector id %d unknown\n",
-						out_id);
-				ret = -ENOENT;
-				goto out;
-			}
-			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);
+retry:
+	state = dev->driver->atomic_begin(dev, 0);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
 
-out:
-	if (fb)
-		drm_framebuffer_unreference(fb);
+	/* If connectors change, we need to check if we need to steal one
+	 * from another CRTC..  setcrtc makes this implicit, but atomic
+	 * treats it as an error so we need to handle here:
+	 */
+	ret = check_connectors(crtc, state, true,
+		connector_ids, crtc_req->count_connectors);
+	if (ret)
+		goto out;
 
-	kfree(connector_set);
-	drm_mode_destroy(dev, mode);
-	drm_modeset_unlock_all(dev);
+	ret =
+		drm_mode_crtc_set_obj_prop(crtc, state,
+			config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
+		drm_mode_crtc_set_obj_prop(crtc, state,
+			config->prop_connector_ids,
+			crtc_req->count_connectors * sizeof(connector_ids[0]),
+			connector_ids) ||
+		drm_mode_plane_set_obj_prop(crtc->primary, state,
+			config->prop_crtc_id, crtc->base.id, NULL) ||
+		drm_mode_plane_set_obj_prop(crtc->primary, state,
+			config->prop_fb_id, fb_id, NULL) ||
+		drm_mode_plane_set_obj_prop(crtc->primary, state,
+			config->prop_src_x, crtc_req->x << 16, NULL) ||
+		drm_mode_plane_set_obj_prop(crtc->primary, state,
+			config->prop_src_y, crtc_req->y << 16, NULL) ||
+		dev->driver->atomic_check(dev, state);
+	if (ret)
+		goto out;
+
+	ret = dev->driver->atomic_commit(dev, state);
+
+out:
+	if (state)
+		dev->driver->atomic_end(dev, state);
+	if (ret == -EDEADLK)
+		goto retry;
 	return ret;
 }
 
@@ -4028,9 +4207,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;
 }
@@ -4424,6 +4600,51 @@  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);
+}
+
 /**
  * drm_mode_page_flip_ioctl - schedule an asynchronous fb update
  * @dev: DRM device
@@ -4446,10 +4667,10 @@  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_crtc *crtc;
-	struct drm_framebuffer *fb = NULL, *old_fb = NULL;
 	struct drm_pending_vblank_event *e = NULL;
-	unsigned long flags;
+	struct drm_atomic_state *state;
 	int ret = -EINVAL;
 
 	if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
@@ -4463,92 +4684,41 @@  int drm_mode_page_flip_ioctl(struct drm_device *dev,
 	if (!crtc)
 		return -ENOENT;
 
-	drm_modeset_lock(&crtc->mutex, NULL);
-	if (crtc->primary->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->primary->fb->pixel_format != fb->pixel_format) {
-		DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
-		ret = -EINVAL;
-		goto out;
-	}
+retry:
+	state = dev->driver->atomic_begin(dev,
+			page_flip->flags | DRM_MODE_ATOMIC_NONBLOCK);
+	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, &crtc->base, 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->primary->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->primary->fb != fb);
-		/* Unref only the old framebuffer. */
-		fb = NULL;
-	}
+	ret = drm_mode_plane_set_obj_prop(crtc->primary, 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);
-	drm_modeset_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);
+	if (ret == -EDEADLK)
+		goto retry;
 	return ret;
 }
 
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index b73d3b0..4669e69 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -286,7 +286,7 @@  bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 	struct drm_device *dev = fb_helper->dev;
 	struct drm_plane *plane;
 	bool error = false;
-	void *state;
+	struct drm_atomic_state *state;
 	int i;
 
 	drm_warn_on_modeset_not_all_locked(dev);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
index 2a56973..f3c7e77 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.h>
 
 #include "exynos_drm_crtc.h"
 #include "exynos_drm_drv.h"
@@ -289,6 +290,10 @@  static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
 	struct drm_device *dev = crtc->dev;
 	struct exynos_drm_private *dev_priv = dev->dev_private;
 	struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+
+	if (IS_ERR(cstate))
+		return PTR_ERR(cstate);
 
 	if (property == dev_priv->crtc_mode_property) {
 		enum exynos_crtc_mode mode = val;
@@ -313,7 +318,7 @@  static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
 		return 0;
 	}
 
-	return -EINVAL;
+	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
 }
 
 static struct drm_crtc_funcs exynos_crtc_funcs = {
diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
index 6672732..5b6eee9 100644
--- a/drivers/gpu/drm/gma500/cdv_intel_display.c
+++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
@@ -989,6 +989,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_crtc_set_property,
 	.destroy = gma_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
index 87b50ba..79b5692 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_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 e9f6eb7..53b996f 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -10430,6 +10430,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_crtc_set_property,
 	.destroy = intel_crtc_destroy,
 	.page_flip = intel_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
index a034ed4..ba9bd91 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_crtc_set_property,
 	.destroy = mga_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
index 7cf0f78..d0d8befd 100644
--- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
+++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
@@ -471,8 +471,10 @@  static int mdp4_crtc_set_property(struct drm_crtc *crtc,
 		struct drm_atomic_state *state, struct drm_property *property,
 		uint64_t val, void *blob_data)
 {
-	// XXX
-	return -EINVAL;
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+	if (IS_ERR(cstate))
+		return PTR_ERR(cstate);
+	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
 }
 
 #define CURSOR_WIDTH 64
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
index 771390b..7f4ee99 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
@@ -389,8 +389,10 @@  static int mdp5_crtc_set_property(struct drm_crtc *crtc,
 		struct drm_atomic_state *state, struct drm_property *property,
 		uint64_t val, void *blob_data)
 {
-	// XXX
-	return -EINVAL;
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+	if (IS_ERR(cstate))
+		return PTR_ERR(cstate);
+	return drm_crtc_set_property(crtc, cstate, property, val, blob_data);
 }
 
 static const struct drm_crtc_funcs mdp5_crtc_funcs = {
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
index 41be342..9e24632 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_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 58af547..ecbffeb 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -1329,6 +1329,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_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 a75934d..772687b 100644
--- a/drivers/gpu/drm/omapdrm/omap_crtc.c
+++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
@@ -387,14 +387,22 @@  static int omap_crtc_set_property(struct drm_crtc *crtc,
 {
 	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 (IS_ERR(cstate))
+		return PTR_ERR(cstate);
 
 	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 = {
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index da80bdc..3f64c47 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -579,7 +579,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);
 		}
 
diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
index b54c970..25896a9 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.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_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 8d99d5e..cc86aac 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.h>
 #include <drm/drm_edid.h>
 
 #include <linux/gcd.h>
@@ -546,6 +547,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_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 299267d..f5a3d55 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.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -527,6 +528,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_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 90e023a..0a5280c 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.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -506,6 +507,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_crtc_set_property,
 	.page_flip = shmob_drm_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
index 92839ba..b07f116 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_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 cddc4fc..36d0116 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.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_crtc_set_property,
 	.destroy = udl_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
index b2b9bd2..0313b00 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
@@ -300,6 +300,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_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 a95d3a0..b723e09 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
@@ -397,6 +397,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_crtc_set_property,
 	.page_flip = vmw_du_page_flip,
 };
 
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 78e93ec..7946b7f 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -70,6 +70,9 @@ 
 struct drm_atomic_funcs {
 	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);
+
+	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_funcs drm_atomic_funcs;
@@ -109,6 +112,30 @@  drm_atomic_commit_plane_state(struct drm_plane *plane,
 	return funcs->commit_plane_state(plane, pstate);
 }
 
+int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_atomic_state *state, struct drm_property *property,
+		uint64_t val, void *blob_data);
+struct drm_crtc_state *drm_atomic_get_crtc_state(struct drm_crtc *crtc,
+		struct drm_atomic_state *state);
+
+static inline int
+drm_atomic_check_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	const struct drm_atomic_funcs *funcs =
+			crtc->dev->driver->atomic_funcs;
+	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_funcs *funcs =
+			crtc->dev->driver->atomic_funcs;
+	return funcs->commit_crtc_state(crtc, cstate);
+}
+
 /**
  * struct drm_atomic_state - the state object used by atomic helpers
  */
@@ -118,6 +145,8 @@  struct drm_atomic_state {
 	uint32_t flags;
 	struct drm_plane **planes;
 	struct drm_plane_state **pstates;
+	struct drm_crtc **crtcs;
+	struct drm_crtc_state **cstates;
 
 	bool committed;
 	bool checked;       /* just for debugging */
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 58309cc..2fbf13a 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -284,6 +284,10 @@  struct drm_crtc_funcs {
 			 struct drm_pending_vblank_event *event,
 			 uint32_t flags);
 
+	struct drm_crtc_state *(*create_state)(struct drm_crtc *crtc);
+	void (*destroy_state)(struct drm_crtc *crtc,
+			    struct drm_crtc_state *cstate);
+
 	int (*set_property)(struct drm_crtc *crtc,
 			    struct drm_atomic_state *state,
 			    struct drm_property *property, uint64_t val,
@@ -291,21 +295,52 @@  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
+ * @mode_valid: a valid mode has been set
+ * @set_config: needs modeset (crtc->set_config())
+ * @connectors_change: the connector-ids array has changed
+ * @num_connector_ids: the number of connector-ids
+ * @connector_ids: array of connector ids
+ * @mode: current mode timings
+ * @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 mode_valid        : 1;
+
+	/* transient state, only valid during atomic operation: */
+	bool set_config        : 1;
+	bool connectors_change : 1;
+
+	uint8_t num_connector_ids;
+	uint32_t *connector_ids;
+	struct drm_mode_modeinfo mode;
+
+	struct drm_pending_vblank_event *event;
+
+	struct drm_object_property_values propvals;
+
+	struct drm_atomic_state *state;
+};
+
+/**
  * drm_crtc - central CRTC control structure
  * @dev: parent DRM device
  * @head: list management
+ * @id: CRTC number, 0..n
  * @mutex: per-CRTC locking
  * @base: base KMS object for ID tracking etc.
  * @primary: primary plane for this CRTC
  * @cursor: cursor plane for this CRTC
+ * @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
@@ -322,6 +357,8 @@  struct drm_crtc {
 	struct drm_device *dev;
 	struct list_head head;
 
+	int id;
+
 	/**
 	 * crtc mutex
 	 *
@@ -337,23 +374,19 @@  struct drm_crtc {
 	struct drm_plane *primary;
 	struct drm_plane *cursor;
 
+	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. */
 	struct drm_framebuffer *old_fb;
 
 	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 */
@@ -367,9 +400,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:
+	 */
+	int x, y;
+	struct drm_display_mode mode;
+};
 
 /**
  * drm_connector_funcs - control connectors on a given device
@@ -875,6 +914,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;
 	struct drm_property *plane_type_property;
@@ -935,7 +976,8 @@  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 unsigned int drm_crtc_index(struct drm_crtc *crtc);
+struct drm_display_mode *drm_crtc_get_mode(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate);
 
 /**
  * drm_crtc_mask - find the mask of a registered CRTC
@@ -946,9 +988,18 @@  extern unsigned int drm_crtc_index(struct drm_crtc *crtc);
  */
 static inline uint32_t drm_crtc_mask(struct drm_crtc *crtc)
 {
-	return 1 << drm_crtc_index(crtc);
+	return 1 << crtc->id;
 }
 
+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);
 extern int drm_connector_init(struct drm_device *dev,
@@ -1024,6 +1075,7 @@  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 void drm_mode_group_destroy(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);
@@ -1251,6 +1303,25 @@  drm_property_blob_find(struct drm_device *dev, uint32_t id)
 	return mo ? obj_to_blob(mo) : NULL;
 }
 
+static inline struct drm_crtc_state *
+drm_crtc_create_state(struct drm_crtc *crtc)
+{
+	if (crtc->funcs->create_state)
+		return crtc->funcs->create_state(crtc);
+	return kzalloc(sizeof(struct drm_crtc_state), GFP_KERNEL);
+}
+
+static inline void
+drm_crtc_destroy_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	kfree(cstate->connector_ids);
+	if (crtc->funcs->destroy_state)
+		crtc->funcs->destroy_state(crtc, cstate);
+	else
+		kfree(cstate);
+}
+
 static inline struct drm_plane_state *
 drm_plane_create_state(struct drm_plane *plane)
 {