diff mbox

[RFCv2,11/13] drm: convert crtc to properties/state

Message ID 1381771608-15237-12-git-send-email-robdclark@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Rob Clark Oct. 14, 2013, 5:26 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.
---
 drivers/gpu/drm/ast/ast_mode.c             |   1 +
 drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
 drivers/gpu/drm/drm_atomic_helper.c        | 249 +++++++++++++++-
 drivers/gpu/drm/drm_crtc.c                 | 458 +++++++++++++++--------------
 drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   5 +-
 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/mdp4/mdp4_crtc.c       |   5 +-
 drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
 drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
 drivers/gpu/drm/omapdrm/omap_crtc.c        |   9 +-
 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 +
 drivers/gpu/host1x/drm/dc.c                |   1 +
 drivers/staging/imx-drm/ipuv3-crtc.c       |   1 +
 include/drm/drm_atomic_helper.h            |  37 +++
 include/drm/drm_crtc.h                     |  77 ++++-
 26 files changed, 627 insertions(+), 238 deletions(-)

Comments

Matt Roper Oct. 22, 2013, 10:27 p.m. UTC | #1
On Mon, Oct 14, 2013 at 01:26:46PM -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.
> ---
>  drivers/gpu/drm/ast/ast_mode.c             |   1 +
>  drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
>  drivers/gpu/drm/drm_atomic_helper.c        | 249 +++++++++++++++-
>  drivers/gpu/drm/drm_crtc.c                 | 458 +++++++++++++++--------------
>  drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   5 +-
>  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/mdp4/mdp4_crtc.c       |   5 +-
>  drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
>  drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
>  drivers/gpu/drm/omapdrm/omap_crtc.c        |   9 +-
>  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 +
>  drivers/gpu/host1x/drm/dc.c                |   1 +
>  drivers/staging/imx-drm/ipuv3-crtc.c       |   1 +
>  include/drm/drm_atomic_helper.h            |  37 +++
>  include/drm/drm_crtc.h                     |  77 ++++-
>  26 files changed, 627 insertions(+), 238 deletions(-)
> 
> diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
> index 7fc9f72..13f6943 100644
> --- a/drivers/gpu/drm/ast/ast_mode.c
> +++ b/drivers/gpu/drm/ast/ast_mode.c
> @@ -619,6 +619,7 @@ static const struct drm_crtc_funcs ast_crtc_funcs = {
>  	.cursor_move = ast_cursor_move,
>  	.reset = ast_crtc_reset,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.gamma_set = ast_crtc_gamma_set,
>  	.destroy = ast_crtc_destroy,
>  };
> diff --git a/drivers/gpu/drm/cirrus/cirrus_mode.c b/drivers/gpu/drm/cirrus/cirrus_mode.c
> index 60685b2..2d8b4ce 100644
> --- a/drivers/gpu/drm/cirrus/cirrus_mode.c
> +++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
> @@ -363,6 +363,7 @@ static void cirrus_crtc_destroy(struct drm_crtc *crtc)
>  static const struct drm_crtc_funcs cirrus_crtc_funcs = {
>  	.gamma_set = cirrus_crtc_gamma_set,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = cirrus_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 0618113..91b7580 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -40,11 +40,13 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
>  {
>  	struct drm_atomic_helper_state *state;
>  	int nplanes = dev->mode_config.num_plane;
> +	int ncrtcs  = dev->mode_config.num_crtc;
>  	int sz;
>  	void *ptr;
>  
>  	sz = sizeof(*state);
>  	sz += (sizeof(state->planes) + sizeof(state->pstates)) * nplanes;
> +	sz += (sizeof(state->crtcs) + sizeof(state->cstates)) * ncrtcs;
>  
>  	ptr = kzalloc(sz, GFP_KERNEL);
>  
> @@ -61,6 +63,12 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
>  	state->pstates = ptr;
>  	ptr = &state->pstates[nplanes];
>  
> +	state->crtcs = ptr;
> +	ptr = &state->crtcs[ncrtcs];
> +
> +	state->cstates = ptr;
> +	ptr = &state->cstates[ncrtcs];
> +
>  	return state;
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_begin);
> @@ -79,7 +87,16 @@ int drm_atomic_helper_set_event(struct drm_device *dev,
>  		void *state, struct drm_mode_object *obj,
>  		struct drm_pending_vblank_event *event)
>  {
> -	return -EINVAL;  /* for now */
> +	switch (obj->type) {
> +	case DRM_MODE_OBJECT_CRTC: {
> +		struct drm_crtc_state *cstate =
> +			drm_atomic_get_crtc_state(obj_to_crtc(obj), state);
> +		cstate->event = event;
> +		return 0;
> +	}
> +	default:
> +		return -EINVAL;
> +	}
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_set_event);
>  
> @@ -98,6 +115,7 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
>  {
>  	struct drm_atomic_helper_state *a = state;
>  	int nplanes = dev->mode_config.num_plane;
> +	int ncrtcs = dev->mode_config.num_crtc;
>  	int i, ret = 0;
>  
>  	for (i = 0; i < nplanes; i++) {
> @@ -108,6 +126,14 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
>  		}
>  	}
>  
> +	for (i = 0; i < ncrtcs; i++) {
> +		if (a->crtcs[i]) {
> +			ret = drm_atomic_check_crtc_state(a->crtcs[i], a->cstates[i]);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
>  	return ret;
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_check);
> @@ -127,6 +153,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
>  {
>  	struct drm_atomic_helper_state *a = state;
>  	int nplanes = dev->mode_config.num_plane;
> +	int ncrtcs = dev->mode_config.num_crtc;
>  	int i, ret = 0;
>  
>  	for (i = 0; i < nplanes; i++) {
> @@ -137,6 +164,14 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
>  		}
>  	}
>  
> +	for (i = 0; i < ncrtcs; i++) {
> +		if (a->crtcs[i]) {
> +			ret = drm_atomic_commit_crtc_state(a->crtcs[i], a->cstates[i]);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
>  	return ret;
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit);
> @@ -160,6 +195,7 @@ void _drm_atomic_helper_state_free(struct kref *kref)
>  		container_of(kref, struct drm_atomic_helper_state, refcount);
>  	struct drm_device *dev = state->dev;
>  	int nplanes = dev->mode_config.num_plane;
> +	int ncrtcs = dev->mode_config.num_crtc;
>  	int i;
>  
>  	for (i = 0; i < nplanes; i++) {
> @@ -169,6 +205,13 @@ void _drm_atomic_helper_state_free(struct kref *kref)
>  		}
>  	}
>  
> +	for (i = 0; i < ncrtcs; i++) {
> +		if (state->cstates[i]) {
> +			state->crtcs[i]->state->state = NULL;
> +			kfree(state->cstates[i]);
> +		}
> +	}
> +
>  	kfree(state);
>  }
>  EXPORT_SYMBOL(_drm_atomic_helper_state_free);
> @@ -260,9 +303,213 @@ drm_atomic_helper_commit_plane_state(struct drm_plane *plane,
>  	return ret;
>  }
>  
> +int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
> +		struct drm_property *property, uint64_t val, void *blob_data)
> +{
> +	return drm_crtc_set_property(crtc,
> +			drm_atomic_get_crtc_state(crtc, state),
> +			property, val, blob_data);
> +}
> +EXPORT_SYMBOL(drm_atomic_helper_crtc_set_property);
> +
> +void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate, void *state)
> +{
> +	/* snapshot current state: */
> +	*cstate = *crtc->state;
> +	cstate->state = state;
> +}
> +EXPORT_SYMBOL(drm_atomic_helper_init_crtc_state);
> +
> +static struct drm_crtc_state *
> +drm_atomic_helper_get_crtc_state(struct drm_crtc *crtc, void *state)
> +{
> +	struct drm_atomic_helper_state *a = state;
> +	struct drm_crtc_state *cstate = a->cstates[crtc->id];
> +	if (!cstate) {
> +		cstate = kzalloc(sizeof(*cstate), GFP_KERNEL);
> +		drm_atomic_helper_init_crtc_state(crtc, cstate, state);
> +		a->crtcs[crtc->id] = crtc;
> +		a->cstates[crtc->id] = cstate;
> +	}
> +	return cstate;
> +}
> +
> +static void
> +swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_helper_state *a)
> +{
> +	struct drm_crtc_state *cstate = a->cstates[crtc->id];
> +	struct drm_device *dev = crtc->dev;
> +	struct drm_pending_vblank_event *event = cstate->event;
> +	if (event) {
> +		/* hrm, need to sort out a better way to send events for
> +		 * other-than-pageflip.. but modeset is not async, so:
> +		 */
> +		unsigned long flags;
> +		DRM_ERROR("event=%p\n", event);
> +		spin_lock_irqsave(&dev->event_lock, flags);
> +		drm_send_vblank_event(dev, crtc->id, event);
> +		cstate->event = NULL;
> +		spin_unlock_irqrestore(&dev->event_lock, flags);
> +	}
> +	swap(crtc->state, a->cstates[crtc->id]);
> +	crtc->base.propvals = &crtc->state->propvals;
> +}
> +
> +static struct drm_connector **get_connector_set(struct drm_device *dev,
> +		uint32_t *connector_ids, uint32_t num_connector_ids)
> +{
> +	struct drm_connector **connector_set = NULL;
> +	int i;
> +
> +	connector_set = kmalloc(num_connector_ids *
> +			sizeof(struct drm_connector *),
> +			GFP_KERNEL);
> +	if (!connector_set)
> +		return NULL;
> +
> +	for (i = 0; i < num_connector_ids; i++)
> +		connector_set[i] = drm_connector_find(dev, connector_ids[i]);
> +
> +	return connector_set;
> +}
> +
> +static struct drm_display_mode *get_mode(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
> +{
> +	struct drm_display_mode *mode = NULL;
> +	if (cstate->mode_valid) {
> +		struct drm_device *dev = crtc->dev;
> +		int ret;
> +
> +		mode = drm_mode_create(dev);
> +		if (!mode)
> +			return ERR_PTR(-ENOMEM);
> +
> +		ret = drm_crtc_convert_umode(mode, &cstate->mode);
> +		if (ret) {
> +			DRM_DEBUG_KMS("Invalid mode\n");
> +			drm_mode_destroy(dev, mode);
> +			return ERR_PTR(ret);
> +		}
> +
> +		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
> +	}
> +	return mode;
> +}
> +
> +static int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct drm_framebuffer *fb = cstate->fb;
> +	struct drm_connector **connector_set = get_connector_set(crtc->dev,
> +			cstate->connector_ids, cstate->num_connector_ids);
> +	struct drm_display_mode *mode = get_mode(crtc, cstate);
> +	struct drm_mode_set set = {
> +			.crtc = crtc,
> +			.x = cstate->x,
> +			.y = cstate->y,
> +			.mode = mode,
> +			.num_connectors = cstate->num_connector_ids,
> +			.connectors = connector_set,
> +			.fb = fb,
> +	};
> +	int ret;
> +
> +	if (IS_ERR(mode)) {
> +		ret = PTR_ERR(mode);
> +		return ret;
> +	}
> +
> +	drm_modeset_lock_all(dev);
> +
> +	ret = drm_mode_set_config_internal(&set);
> +	if (!ret)
> +		swap_crtc_state(crtc, cstate->state);
> +
> +	if (fb)
> +		drm_framebuffer_unreference(fb);
> +
> +	kfree(connector_set);
> +	drm_modeset_unlock_all(dev);
> +	if (mode)
> +		drm_mode_destroy(dev, mode);
> +	return ret;
> +}
> +
> +static int
> +drm_atomic_helper_commit_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	struct drm_framebuffer *old_fb = NULL, *fb = NULL;
> +	struct drm_atomic_helper_state *a = cstate->state;
> +	int ret = -EINVAL;
> +
> +	if (cstate->set_config) {
> +		cstate->set_config = false;
> +		return set_config(crtc, cstate);
> +	}
> +
> +	mutex_lock(&crtc->mutex);
> +	if (cstate->fb) {
> +		/* pageflip */
> +
> +		if (crtc->fb == NULL) {
> +			/* The framebuffer is currently unbound, presumably
> +			 * due to a hotplug event, that userspace has not
> +			 * yet discovered.
> +			 */
> +			ret = -EBUSY;
> +			goto out;
> +		}
> +
> +		if (crtc->funcs->page_flip == NULL)
> +			goto out;
> +
> +		old_fb = crtc->fb;
> +		fb = cstate->fb;
> +
> +		ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
> +		if (ret) {
> +			/* Keep the old fb, don't unref it. */
> +			old_fb = NULL;
> +		} else {
> +			cstate->event = NULL;
> +			swap_crtc_state(crtc, cstate->state);
> +			/* Unref only the old framebuffer. */
> +			fb = NULL;
> +		}
> +	} else {
> +		/* disable */
> +		struct drm_mode_set set = {
> +				.crtc = crtc,
> +				.fb = NULL,
> +		};
> +
> +		old_fb = crtc->state->fb;
> +		ret = drm_mode_set_config_internal(&set);
> +		if (!ret) {
> +			swap_crtc_state(crtc, cstate->state);
> +		}
> +	}
> +
> +out:
> +	mutex_unlock(&crtc->mutex);
> +
> +	if (fb)
> +		drm_framebuffer_unreference(fb);
> +	if (old_fb)
> +		drm_framebuffer_unreference(old_fb);
> +
> +	return ret;
> +}
> +
>  const struct drm_atomic_helper_funcs drm_atomic_helper_funcs = {
>  		.get_plane_state    = drm_atomic_helper_get_plane_state,
>  		.check_plane_state  = drm_plane_check_state,
>  		.commit_plane_state = drm_atomic_helper_commit_plane_state,
> +
> +		.get_crtc_state     = drm_atomic_helper_get_crtc_state,
> +		.check_crtc_state   = drm_crtc_check_state,
> +		.commit_crtc_state  = drm_atomic_helper_commit_crtc_state,
>  };
>  EXPORT_SYMBOL(drm_atomic_helper_funcs);
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index 3cf235e..1091d82 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -574,8 +574,6 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>  	struct drm_device *dev = fb->dev;
>  	struct drm_crtc *crtc;
>  	struct drm_plane *plane;
> -	struct drm_mode_set set;
> -	int ret;
>  
>  	WARN_ON(!list_empty(&fb->filp_head));
>  
> @@ -595,6 +593,7 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>  	 * in this manner.
>  	 */
>  	if (atomic_read(&fb->refcount.refcount) > 1) {
> +		struct drm_mode_config *config = &fb->dev->mode_config;
>  		void *state;
>  
>  		state = dev->driver->atomic_begin(dev, 0);
> @@ -603,22 +602,12 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>  			return;
>  		}
>  
> -		/* TODO once CRTC is converted to state/properties, we can push the
> -		 * locking down into drm_atomic_helper_commit(), since that is where
> -		 * the actual changes take place..
> -		 */
> -		drm_modeset_lock_all(dev);
> -
>  		/* remove from any CRTC */
>  		list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>  			if (crtc->fb == fb) {
>  				/* should turn off the crtc */
> -				memset(&set, 0, sizeof(struct drm_mode_set));
> -				set.crtc = crtc;
> -				set.fb = NULL;
> -				ret = drm_mode_set_config_internal(&set);
> -				if (ret)
> -					DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
> +				drm_mode_crtc_set_obj_prop(crtc, state,
> +					config->prop_fb_id, 0, NULL);
>  			}
>  		}
>  
> @@ -634,8 +623,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);
> @@ -656,11 +643,15 @@ EXPORT_SYMBOL(drm_framebuffer_remove);
>  int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
>  		   const struct drm_crtc_funcs *funcs)
>  {
> +	struct drm_mode_config *config = &dev->mode_config;
>  	int ret;
>  
> +	if (!crtc->state)
> +		crtc->state = kzalloc(sizeof(crtc->state), GFP_KERNEL);
> +
>  	crtc->dev = dev;
>  	crtc->funcs = funcs;
> -	crtc->invert_dimensions = false;
> +	crtc->state->invert_dimensions = false;
>  
>  	drm_modeset_lock_all(dev);
>  	mutex_init(&crtc->mutex);
> @@ -671,11 +662,17 @@ int drm_crtc_init(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++;
>  
> +	drm_object_attach_property(&crtc->base, config->prop_mode, 0);
> +	drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
> +	drm_object_attach_property(&crtc->base, config->prop_fb_id, 0);
> +	drm_object_attach_property(&crtc->base, config->prop_src_x, 0);
> +	drm_object_attach_property(&crtc->base, config->prop_src_y, 0);
> +
>   out:
>  	drm_modeset_unlock_all(dev);
>  
> @@ -704,6 +701,112 @@ void drm_crtc_cleanup(struct drm_crtc *crtc)
>  }
>  EXPORT_SYMBOL(drm_crtc_cleanup);
>  
> +int drm_crtc_check_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state)
> +{
> +	struct drm_framebuffer *fb = state->fb;
> +	int hdisplay, vdisplay;
> +
> +	/* disabling the crtc is allowed: */
> +	if (!(fb && state->mode_valid))
> +		return 0;
> +
> +	hdisplay = state->mode.hdisplay;
> +	vdisplay = state->mode.vdisplay;
> +
> +	if (state->invert_dimensions)
> +		swap(hdisplay, vdisplay);
> +
> +	/* For some reason crtc x/y offsets are signed internally. */
> +	if (state->x > INT_MAX || state->y > INT_MAX)
> +		return -ERANGE;
> +
> +	if (hdisplay > fb->width ||
> +	    vdisplay > fb->height ||
> +	    state->x > fb->width - hdisplay ||
> +	    state->y > fb->height - vdisplay) {
> +		DRM_DEBUG_KMS("Invalid fb size %ux%u for CRTC viewport %ux%u+%d+%d%s.\n",
> +			      fb->width, fb->height, hdisplay, vdisplay,
> +			      state->x, state->y,
> +			      state->invert_dimensions ? " (inverted)" : "");
> +		return -ENOSPC;
> +	}
> +
> +	if (crtc->enabled && !state->set_config) {
> +		if (crtc->state->fb->pixel_format != fb->pixel_format) {
> +			DRM_DEBUG_KMS("Page flip is not allowed to "
> +					"change frame buffer format.\n");
> +			return -EINVAL;
> +		}

Is there a reason to disallow this in the general case?  I realize some
hardware may not be able to handle a pixel format change at pageflip
time, but on hardware that can, this is likely to be useful when working
with compositors that may flip directly to client buffers that come in
different pixel formats than whatever the previous frame was using.

I'm guessing most drivers won't be able to use drm_crtc_check_state()
standalone since at the very least they'll need to wrap their own logic
on top of it to filter the list of pixel formats that their display
controller can actually handle.  Would it make more sense to push this
pixel format switching check up to that driver-specific wrapper level so
that we're not creating artificial limitations for some hardware?

> +	}
> +
> +	if (state->num_connector_ids == 0) {
> +		DRM_DEBUG_KMS("Count connectors is 0 but mode set\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_crtc_check_state);
> +
> +void drm_crtc_commit_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state)
> +{
> +	crtc->state = state;
> +	crtc->base.propvals = &state->propvals;
> +}
> +EXPORT_SYMBOL(drm_crtc_commit_state);
> +
> +int drm_crtc_set_property(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state,
> +		struct drm_property *property,
> +		uint64_t value, void *blob_data)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct drm_mode_config *config = &dev->mode_config;
> +
> +	drm_object_property_set_value(&crtc->base,
> +			&state->propvals, property, value, blob_data);
> +
> +	if (property == config->prop_mode) {
> +		if (!blob_data) {
> +			memset(&state->mode, 0, sizeof(state->mode));
> +			state->mode_valid = false;
> +		} else {
> +			/* check size: */
> +			if (value < sizeof(struct drm_mode_modeinfo))
> +				return -EINVAL;
> +			state->mode = *(struct drm_mode_modeinfo *)blob_data;
> +			state->mode_valid = true;
> +		}
> +		state->set_config = true;
> +	} else if (property == config->prop_connector_ids) {
> +		state->num_connector_ids = value / sizeof(state->connector_ids[0]);
> +		state->connector_ids = blob_data;
> +		state->set_config = true;
> +	} else if (property == config->prop_fb_id) {
> +		state->new_fb = true;
> +		state->fb = drm_framebuffer_lookup(dev, value);
> +	} else if (property == config->prop_src_x) {
> +		int x = *(int *)&value;
> +		if (state->x != x) {
> +			state->x = x;
> +			state->set_config = true;
> +		}
> +	} else if (property == config->prop_src_y) {
> +		int y = *(int *)&value;
> +		if (state->y != y) {
> +			state->y = y;
> +			state->set_config = true;
> +		}
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_crtc_set_property);
> +
>  /**
>   * drm_mode_probed_add - add a mode to a connector's probed mode list
>   * @connector: connector the new mode
> @@ -1265,6 +1368,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;
>  }
>  
> @@ -1529,7 +1642,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)
> @@ -1775,11 +1888,11 @@ int drm_mode_getcrtc(struct drm_device *dev,
>  	}
>  	crtc = obj_to_crtc(obj);
>  
> -	crtc_resp->x = crtc->x;
> -	crtc_resp->y = crtc->y;
> +	crtc_resp->x = crtc->state->x;
> +	crtc_resp->y = crtc->state->y;
>  	crtc_resp->gamma_size = crtc->gamma_size;
> -	if (crtc->fb)
> -		crtc_resp->fb_id = crtc->fb->base.id;
> +	if (crtc->state->fb)
> +		crtc_resp->fb_id = crtc->state->fb->base.id;
>  	else
>  		crtc_resp->fb_id = 0;
>  
> @@ -2209,22 +2322,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  	struct drm_mode_crtc *crtc_req = data;
>  	struct drm_mode_object *obj;
>  	struct drm_crtc *crtc;
> -	struct drm_connector **connector_set = NULL, *connector;
> -	struct drm_framebuffer *fb = NULL;
> -	struct drm_display_mode *mode = NULL;
> -	struct drm_mode_set set;
> -	uint32_t __user *set_connectors_ptr;
> +	uint32_t fb_id = -1;
> +	uint32_t *connector_ids = NULL;
> +	void *state = NULL;
>  	int ret;
>  	int i;
>  
>  	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>  		return -EINVAL;
>  
> -	/* For some reason crtc x/y offsets are signed internally. */
> -	if (crtc_req->x > INT_MAX || crtc_req->y > INT_MAX)
> -		return -ERANGE;
> -
> -	drm_modeset_lock_all(dev);
>  	obj = drm_mode_object_find(dev, crtc_req->crtc_id,
>  				   DRM_MODE_OBJECT_CRTC);
>  	if (!obj) {
> @@ -2236,7 +2342,6 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  	DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
>  
>  	if (crtc_req->mode_valid) {
> -		int hdisplay, vdisplay;
>  		/* If we have a mode we need a framebuffer. */
>  		/* If we pass -1, set the mode with the currently bound fb */
>  		if (crtc_req->fb_id == -1) {
> @@ -2245,67 +2350,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  				ret = -EINVAL;
>  				goto out;
>  			}
> -			fb = crtc->fb;
> -			/* Make refcounting symmetric with the lookup path. */
> -			drm_framebuffer_reference(fb);
> +			fb_id = crtc->base.id;
>  		} else {
> -			fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
> -			if (!fb) {
> -				DRM_DEBUG_KMS("Unknown FB ID%d\n",
> -						crtc_req->fb_id);
> -				ret = -EINVAL;
> -				goto out;
> -			}
> +			fb_id = crtc_req->fb_id;
>  		}
> -
> -		mode = drm_mode_create(dev);
> -		if (!mode) {
> -			ret = -ENOMEM;
> -			goto out;
> -		}
> -
> -		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);
> -
> -		hdisplay = mode->hdisplay;
> -		vdisplay = mode->vdisplay;
> -
> -		if (crtc->invert_dimensions)
> -			swap(hdisplay, vdisplay);
> -
> -		if (hdisplay > fb->width ||
> -		    vdisplay > fb->height ||
> -		    crtc_req->x > fb->width - hdisplay ||
> -		    crtc_req->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, crtc_req->x, crtc_req->y,
> -				      crtc->invert_dimensions ? " (inverted)" : "");
> -			ret = -ENOSPC;
> -			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) {
> @@ -2313,54 +2366,52 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>  			goto out;
>  		}
>  
> -		connector_set = kmalloc(crtc_req->count_connectors *
> -					sizeof(struct drm_connector *),
> +		connector_ids = kmalloc(crtc_req->count_connectors *
> +					sizeof(connector_ids[0]),
>  					GFP_KERNEL);
> -		if (!connector_set) {
> +		if (!connector_ids) {
>  			ret = -ENOMEM;
>  			goto out;
>  		}
>  
>  		for (i = 0; i < crtc_req->count_connectors; i++) {
> -			set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
> +			u32 out_id;
> +
>  			if (get_user(out_id, &set_connectors_ptr[i])) {
>  				ret = -EFAULT;
>  				goto out;
>  			}
> -
> -			obj = drm_mode_object_find(dev, out_id,
> -						   DRM_MODE_OBJECT_CONNECTOR);
> -			if (!obj) {
> -				DRM_DEBUG_KMS("Connector id %d unknown\n",
> -						out_id);
> -				ret = -EINVAL;
> -				goto out;
> -			}
> -			connector = obj_to_connector(obj);
> -			DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
> -					connector->base.id,
> -					drm_get_connector_name(connector));
> -
> -			connector_set[i] = connector;
> +			connector_ids[i] = out_id;
>  		}
>  	}
>  
> -	set.crtc = crtc;
> -	set.x = crtc_req->x;
> -	set.y = crtc_req->y;
> -	set.mode = mode;
> -	set.connectors = connector_set;
> -	set.num_connectors = crtc_req->count_connectors;
> -	set.fb = fb;
> -	ret = drm_mode_set_config_internal(&set);
> +	state = dev->driver->atomic_begin(dev, 0);
> +	if (IS_ERR(state))
> +		return PTR_ERR(state);
>  
> -out:
> -	if (fb)
> -		drm_framebuffer_unreference(fb);
> +	ret =
> +		drm_mode_set_obj_prop(obj, state,
> +			config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
> +		drm_mode_set_obj_prop(obj, state,
> +			config->prop_connector_ids,
> +			crtc_req->count_connectors * sizeof(connector_ids[0]),
> +			connector_ids) ||
> +		drm_mode_set_obj_prop(obj, state,
> +			config->prop_fb_id, fb_id, NULL) ||
> +		drm_mode_set_obj_prop(obj, state,
> +			config->prop_src_x, crtc_req->x, NULL) ||
> +		drm_mode_set_obj_prop(obj, state,
> +			config->prop_src_y, crtc_req->y, NULL) ||
> +		dev->driver->atomic_check(dev, state);
> +	if (ret)
> +		goto out;
>  
> -	kfree(connector_set);
> -	drm_mode_destroy(dev, mode);
> -	drm_modeset_unlock_all(dev);
> +	ret = dev->driver->atomic_commit(dev, state);
> +
> +out:
> +	if (state)
> +		dev->driver->atomic_end(dev, state);
> +	kfree(connector_ids);
>  	return ret;
>  }
>  
> @@ -3467,9 +3518,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;
>  }
> @@ -3795,16 +3843,60 @@ out:
>  	return ret;
>  }
>  
> +static struct drm_pending_vblank_event *create_vblank_event(
> +		struct drm_device *dev, struct drm_file *file_priv, uint64_t user_data)
> +{
> +	struct drm_pending_vblank_event *e = NULL;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dev->event_lock, flags);
> +	if (file_priv->event_space < sizeof e->event) {
> +		spin_unlock_irqrestore(&dev->event_lock, flags);
> +		goto out;
> +	}
> +	file_priv->event_space -= sizeof e->event;
> +	spin_unlock_irqrestore(&dev->event_lock, flags);
> +
> +	e = kzalloc(sizeof *e, GFP_KERNEL);
> +	if (e == NULL) {
> +		spin_lock_irqsave(&dev->event_lock, flags);
> +		file_priv->event_space += sizeof e->event;
> +		spin_unlock_irqrestore(&dev->event_lock, flags);
> +		goto out;
> +	}
> +
> +	e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
> +	e->event.base.length = sizeof e->event;
> +	e->event.user_data = user_data;
> +	e->base.event = &e->event.base;
> +	e->base.file_priv = file_priv;
> +	e->base.destroy =
> +		(void (*) (struct drm_pending_event *)) kfree;
> +
> +out:
> +	return e;
> +}
> +
> +static void destroy_vblank_event(struct drm_device *dev,
> +		struct drm_file *file_priv, struct drm_pending_vblank_event *e)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dev->event_lock, flags);
> +	file_priv->event_space += sizeof e->event;
> +	spin_unlock_irqrestore(&dev->event_lock, flags);
> +	kfree(e);
> +}
> +
>  int drm_mode_page_flip_ioctl(struct drm_device *dev,
>  			     void *data, struct drm_file *file_priv)
>  {
>  	struct drm_mode_crtc_page_flip *page_flip = data;
> +	struct drm_mode_config *config = &dev->mode_config;
>  	struct drm_mode_object *obj;
>  	struct drm_crtc *crtc;
> -	struct drm_framebuffer *fb = NULL, *old_fb = NULL;
>  	struct drm_pending_vblank_event *e = NULL;
> -	unsigned long flags;
> -	int hdisplay, vdisplay;
> +	void *state;
>  	int ret = -EINVAL;
>  
>  	if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
> @@ -3819,103 +3911,37 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
>  		return -EINVAL;
>  	crtc = obj_to_crtc(obj);
>  
> -	mutex_lock(&crtc->mutex);
> -	if (crtc->fb == NULL) {
> -		/* The framebuffer is currently unbound, presumably
> -		 * due to a hotplug event, that userspace has not
> -		 * yet discovered.
> -		 */
> -		ret = -EBUSY;
> -		goto out;
> -	}
> -
> -	if (crtc->funcs->page_flip == NULL)
> -		goto out;
> -
> -	fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
> -	if (!fb)
> -		goto out;
> -
> -	hdisplay = crtc->mode.hdisplay;
> -	vdisplay = crtc->mode.vdisplay;
> -
> -	if (crtc->invert_dimensions)
> -		swap(hdisplay, vdisplay);
> -
> -	if (hdisplay > fb->width ||
> -	    vdisplay > fb->height ||
> -	    crtc->x > fb->width - hdisplay ||
> -	    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, crtc->x, crtc->y,
> -			      crtc->invert_dimensions ? " (inverted)" : "");
> -		ret = -ENOSPC;
> -		goto out;
> -	}
> -
> -	if (crtc->fb->pixel_format != fb->pixel_format) {
> -		DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
> -		ret = -EINVAL;
> -		goto out;
> -	}
> +	state = dev->driver->atomic_begin(dev, page_flip->flags);
> +	if (IS_ERR(state))
> +		return PTR_ERR(state);
>  
>  	if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
> -		ret = -ENOMEM;
> -		spin_lock_irqsave(&dev->event_lock, flags);
> -		if (file_priv->event_space < sizeof e->event) {
> -			spin_unlock_irqrestore(&dev->event_lock, flags);
> +		e = create_vblank_event(dev, file_priv, page_flip->user_data);
> +		if (!e) {
> +			ret = -ENOMEM;
>  			goto out;
>  		}
> -		file_priv->event_space -= sizeof e->event;
> -		spin_unlock_irqrestore(&dev->event_lock, flags);
> -
> -		e = kzalloc(sizeof *e, GFP_KERNEL);
> -		if (e == NULL) {
> -			spin_lock_irqsave(&dev->event_lock, flags);
> -			file_priv->event_space += sizeof e->event;
> -			spin_unlock_irqrestore(&dev->event_lock, flags);
> +		ret = dev->driver->atomic_set_event(dev, state, obj, e);
> +		if (ret) {
>  			goto out;
>  		}
> -
> -		e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
> -		e->event.base.length = sizeof e->event;
> -		e->event.user_data = page_flip->user_data;
> -		e->base.event = &e->event.base;
> -		e->base.file_priv = file_priv;
> -		e->base.destroy =
> -			(void (*) (struct drm_pending_event *)) kfree;
>  	}
>  
> -	old_fb = crtc->fb;
> -	ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
> -	if (ret) {
> -		if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
> -			spin_lock_irqsave(&dev->event_lock, flags);
> -			file_priv->event_space += sizeof e->event;
> -			spin_unlock_irqrestore(&dev->event_lock, flags);
> -			kfree(e);
> -		}
> -		/* Keep the old fb, don't unref it. */
> -		old_fb = NULL;
> -	} else {
> -		/*
> -		 * Warn if the driver hasn't properly updated the crtc->fb
> -		 * field to reflect that the new framebuffer is now used.
> -		 * Failing to do so will screw with the reference counting
> -		 * on framebuffers.
> -		 */
> -		WARN_ON(crtc->fb != fb);
> -		/* Unref only the old framebuffer. */
> -		fb = NULL;
> -	}
> +	ret = drm_mode_set_obj_prop(obj, state,
> +			config->prop_fb_id, page_flip->fb_id, NULL);
> +	if (ret)
> +		goto out;
>  
> -out:
> -	if (fb)
> -		drm_framebuffer_unreference(fb);
> -	if (old_fb)
> -		drm_framebuffer_unreference(old_fb);
> -	mutex_unlock(&crtc->mutex);
> +	ret = dev->driver->atomic_check(dev, state);
> +	if (ret)
> +		goto out;
> +
> +	ret = dev->driver->atomic_commit(dev, state);
>  
> +out:
> +	if (ret && e)
> +		destroy_vblank_event(dev, file_priv, e);
> +	dev->driver->atomic_end(dev, state);
>  	return ret;
>  }
>  
> diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
> index 4ae55b8..c1ef671 100644
> --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c
> +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
> @@ -14,6 +14,7 @@
>  
>  #include <drm/drmP.h>
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
>  
>  #include "exynos_drm_crtc.h"
>  #include "exynos_drm_drv.h"
> @@ -286,7 +287,9 @@ static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
>  		return 0;
>  	}
>  
> -	return -EINVAL;
> +	return drm_crtc_set_property(crtc,
> +			drm_atomic_get_crtc_state(crtc, state),
> +			property, val, blob_data);
>  }
>  
>  static struct drm_crtc_funcs exynos_crtc_funcs = {
> diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
> index 8fbfa06..2b4bbf5 100644
> --- a/drivers/gpu/drm/gma500/cdv_intel_display.c
> +++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
> @@ -1022,6 +1022,7 @@ const struct drm_crtc_funcs cdv_intel_crtc_funcs = {
>  	.cursor_move = gma_crtc_cursor_move,
>  	.gamma_set = gma_crtc_gamma_set,
>  	.set_config = gma_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = gma_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
> index 97f8a03..34d83ff 100644
> --- a/drivers/gpu/drm/gma500/psb_intel_display.c
> +++ b/drivers/gpu/drm/gma500/psb_intel_display.c
> @@ -444,6 +444,7 @@ const struct drm_crtc_funcs psb_intel_crtc_funcs = {
>  	.cursor_move = gma_crtc_cursor_move,
>  	.gamma_set = gma_crtc_gamma_set,
>  	.set_config = gma_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = gma_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
> index d0eff24..517bf6c 100644
> --- a/drivers/gpu/drm/i915/intel_display.c
> +++ b/drivers/gpu/drm/i915/intel_display.c
> @@ -9277,6 +9277,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = {
>  	.cursor_move = intel_crtc_cursor_move,
>  	.gamma_set = intel_crtc_gamma_set,
>  	.set_config = intel_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = intel_crtc_destroy,
>  	.page_flip = intel_crtc_page_flip,
>  };
> diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
> index 503a414..3bda15b 100644
> --- a/drivers/gpu/drm/mgag200/mgag200_mode.c
> +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c
> @@ -1298,6 +1298,7 @@ static const struct drm_crtc_funcs mga_crtc_funcs = {
>  	.cursor_move = mga_crtc_cursor_move,
>  	.gamma_set = mga_crtc_gamma_set,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = mga_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
> index 1d6a6ee..1cc8a72 100644
> --- a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
> +++ b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
> @@ -403,8 +403,9 @@ static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
>  static int mdp4_crtc_set_property(struct drm_crtc *crtc, void *state,
>  		struct drm_property *property, uint64_t val, void *blob_data)
>  {
> -	// XXX
> -	return -EINVAL;
> +	return drm_crtc_set_property(crtc,
> +			drm_atomic_get_crtc_state(crtc, state),
> +			property, val, blob_data);
>  }
>  
>  #define CURSOR_WIDTH 64
> diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
> index d4fbf11..8fee163 100644
> --- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
> +++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
> @@ -1088,6 +1088,7 @@ static const struct drm_crtc_funcs nv04_crtc_funcs = {
>  	.cursor_move = nv04_crtc_cursor_move,
>  	.gamma_set = nv_crtc_gamma_set,
>  	.set_config = nouveau_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.page_flip = nouveau_crtc_page_flip,
>  	.destroy = nv_crtc_destroy,
>  };
> diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
> index f8e66c0..bfcdf8e 100644
> --- a/drivers/gpu/drm/nouveau/nv50_display.c
> +++ b/drivers/gpu/drm/nouveau/nv50_display.c
> @@ -1327,6 +1327,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = {
>  	.cursor_move = nv50_crtc_cursor_move,
>  	.gamma_set = nv50_crtc_gamma_set,
>  	.set_config = nouveau_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = nv50_crtc_destroy,
>  	.page_flip = nouveau_crtc_page_flip,
>  };
> diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c
> index d53a689..054d75e 100644
> --- a/drivers/gpu/drm/omapdrm/omap_crtc.c
> +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
> @@ -367,14 +367,19 @@ static int omap_crtc_set_property(struct drm_crtc *crtc, void *state,
>  {
>  	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
>  	struct omap_drm_private *priv = crtc->dev->dev_private;
> +	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
> +	int ret;
>  
>  	if (property == priv->rotation_prop) {
> -		crtc->invert_dimensions =
> +		cstate->invert_dimensions =
>  				!!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270)));
>  	}
>  
> -	return omap_plane_set_property(omap_crtc->plane, state,
> +	ret = omap_plane_set_property(omap_crtc->plane, state,
>  			property, val, blob_data);
> +	if (ret)
> +		ret = drm_crtc_set_property(crtc, cstate, property, val, blob_data);
> +	return ret;
>  }
>  
>  static const struct drm_crtc_funcs omap_crtc_funcs = {
> diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
> index 0e15e2c..8651a02 100644
> --- a/drivers/gpu/drm/omapdrm/omap_drv.c
> +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
> @@ -553,7 +553,7 @@ static void dev_lastclose(struct drm_device *dev)
>  		 */
>  		for (i = 0; i < priv->num_crtcs; i++) {
>  			drm_object_property_set_value(&priv->crtcs[i]->base,
> -					&priv->crtcs[i]->propvals,
> +					&priv->crtcs[i]->state->propvals,
>  					priv->rotation_prop, 0, NULL);
>  		}
>  
> diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
> index 9f12205..b138bb1 100644
> --- a/drivers/gpu/drm/qxl/qxl_display.c
> +++ b/drivers/gpu/drm/qxl/qxl_display.c
> @@ -29,6 +29,7 @@
>  #include "qxl_drv.h"
>  #include "qxl_object.h"
>  #include "drm_crtc_helper.h"
> +#include "drm_atomic_helper.h"
>  
>  static bool qxl_head_enabled(struct qxl_head *head)
>  {
> @@ -365,6 +366,7 @@ static const struct drm_crtc_funcs qxl_crtc_funcs = {
>  	.cursor_set2 = qxl_crtc_cursor_set2,
>  	.cursor_move = qxl_crtc_cursor_move,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = qxl_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c
> index 0d1aa05..70d1346 100644
> --- a/drivers/gpu/drm/radeon/radeon_display.c
> +++ b/drivers/gpu/drm/radeon/radeon_display.c
> @@ -31,6 +31,7 @@
>  #include <asm/div64.h>
>  
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
>  #include <drm/drm_edid.h>
>  
>  static void avivo_crtc_load_lut(struct drm_crtc *crtc)
> @@ -499,6 +500,7 @@ static const struct drm_crtc_funcs radeon_crtc_funcs = {
>  	.cursor_move = radeon_crtc_cursor_move,
>  	.gamma_set = radeon_crtc_gamma_set,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = radeon_crtc_destroy,
>  	.page_flip = radeon_crtc_page_flip,
>  };
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
> index a9d24e4..c840ba8 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
> @@ -17,6 +17,7 @@
>  #include <drm/drmP.h>
>  #include <drm/drm_crtc.h>
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
>  #include <drm/drm_fb_cma_helper.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> @@ -528,6 +529,7 @@ static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
>  static const struct drm_crtc_funcs crtc_funcs = {
>  	.destroy = drm_crtc_cleanup,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.page_flip = rcar_du_crtc_page_flip,
>  };
>  
> diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
> index e62b0f2..794ff95 100644
> --- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
> +++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
> @@ -17,6 +17,7 @@
>  #include <drm/drmP.h>
>  #include <drm/drm_crtc.h>
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
>  #include <drm/drm_fb_cma_helper.h>
>  #include <drm/drm_gem_cma_helper.h>
>  
> @@ -496,6 +497,7 @@ static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
>  static const struct drm_crtc_funcs crtc_funcs = {
>  	.destroy = drm_crtc_cleanup,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.page_flip = shmob_drm_crtc_page_flip,
>  };
>  
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
> index d36efc1..34d9804 100644
> --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
> @@ -411,6 +411,7 @@ static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>  static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
>  		.destroy        = tilcdc_crtc_destroy,
>  		.set_config     = drm_crtc_helper_set_config,
> +		.set_property   = drm_atomic_helper_crtc_set_property,
>  		.page_flip      = tilcdc_crtc_page_flip,
>  };
>  
> diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c
> index 2ae1eb7..e05b2ea 100644
> --- a/drivers/gpu/drm/udl/udl_modeset.c
> +++ b/drivers/gpu/drm/udl/udl_modeset.c
> @@ -14,6 +14,7 @@
>  #include <drm/drmP.h>
>  #include <drm/drm_crtc.h>
>  #include <drm/drm_crtc_helper.h>
> +#include <drm/drm_atomic_helper.h>
>  #include "udl_drv.h"
>  
>  /*
> @@ -383,6 +384,7 @@ static struct drm_crtc_helper_funcs udl_helper_funcs = {
>  
>  static const struct drm_crtc_funcs udl_crtc_funcs = {
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = udl_crtc_destroy,
>  };
>  
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> index 79f7e8e..5d25f21 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
> @@ -298,6 +298,7 @@ static struct drm_crtc_funcs vmw_legacy_crtc_funcs = {
>  	.cursor_move = vmw_du_crtc_cursor_move,
>  	.gamma_set = vmw_du_crtc_gamma_set,
>  	.destroy = vmw_ldu_crtc_destroy,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.set_config = vmw_ldu_crtc_set_config,
>  };
>  
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
> index 26387c3..f65fec7 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
> @@ -394,6 +394,7 @@ static struct drm_crtc_funcs vmw_screen_object_crtc_funcs = {
>  	.gamma_set = vmw_du_crtc_gamma_set,
>  	.destroy = vmw_sou_crtc_destroy,
>  	.set_config = vmw_sou_crtc_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.page_flip = vmw_du_page_flip,
>  };
>  
> diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c
> index 5766010..3a7533e 100644
> --- a/drivers/gpu/host1x/drm/dc.c
> +++ b/drivers/gpu/host1x/drm/dc.c
> @@ -259,6 +259,7 @@ static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
>  static const struct drm_crtc_funcs tegra_crtc_funcs = {
>  	.page_flip = tegra_dc_page_flip,
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = drm_crtc_cleanup,
>  };
>  
> diff --git a/drivers/staging/imx-drm/ipuv3-crtc.c b/drivers/staging/imx-drm/ipuv3-crtc.c
> index 6fd37a7..f97862e 100644
> --- a/drivers/staging/imx-drm/ipuv3-crtc.c
> +++ b/drivers/staging/imx-drm/ipuv3-crtc.c
> @@ -155,6 +155,7 @@ static int ipu_page_flip(struct drm_crtc *crtc,
>  
>  static const struct drm_crtc_funcs ipu_crtc_funcs = {
>  	.set_config = drm_crtc_helper_set_config,
> +	.set_property = drm_atomic_helper_crtc_set_property,
>  	.destroy = drm_crtc_cleanup,
>  	.page_flip = ipu_page_flip,
>  };
> diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
> index d0d29e1..f13f09c 100644
> --- a/include/drm/drm_atomic_helper.h
> +++ b/include/drm/drm_atomic_helper.h
> @@ -65,6 +65,10 @@ struct drm_atomic_helper_funcs {
>  	struct drm_plane_state *(*get_plane_state)(struct drm_plane *plane, void *state);
>  	int (*check_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
>  	int (*commit_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
> +
> +	struct drm_crtc_state *(*get_crtc_state)(struct drm_crtc *crtc, void *state);
> +	int (*check_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
> +	int (*commit_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
>  };
>  
>  const extern struct drm_atomic_helper_funcs drm_atomic_helper_funcs;
> @@ -108,6 +112,37 @@ drm_atomic_commit_plane_state(struct drm_plane *plane,
>  	return funcs->commit_plane_state(plane, pstate);
>  }
>  
> +int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
> +		struct drm_property *property, uint64_t val, void *blob_data);
> +void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate, void *state);
> +
> +static inline struct drm_crtc_state *
> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, void *state)
> +{
> +	const struct drm_atomic_helper_funcs *funcs =
> +			crtc->dev->driver->atomic_helpers;
> +	return funcs->get_crtc_state(crtc, state);
> +}
> +
> +static inline int
> +drm_atomic_check_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	const struct drm_atomic_helper_funcs *funcs =
> +			crtc->dev->driver->atomic_helpers;
> +	return funcs->check_crtc_state(crtc, cstate);
> +}
> +
> +static inline int
> +drm_atomic_commit_crtc_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *cstate)
> +{
> +	const struct drm_atomic_helper_funcs *funcs =
> +			crtc->dev->driver->atomic_helpers;
> +	return funcs->commit_crtc_state(crtc, cstate);
> +}
> +
>  /**
>   * struct drm_atomic_helper_state - the state object used by atomic helpers
>   */
> @@ -117,6 +152,8 @@ struct drm_atomic_helper_state {
>  	uint32_t flags;
>  	struct drm_plane **planes;
>  	struct drm_plane_state **pstates;
> +	struct drm_crtc **crtcs;
> +	struct drm_crtc_state **cstates;
>  };
>  
>  static inline void
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index ee46a6a..8227627 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -390,18 +390,51 @@ struct drm_crtc_funcs {
>  };
>  
>  /**
> + * drm_crtc_state - mutable crtc state
> + * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
> + *    invert the width/height of the crtc.  This is used if the driver
> + *    is performing 90 or 270 degree rotated scanout
> + * @set_config: needs modeset (crtc->set_config())
> + * @new_fb: has the fb been changed
> + * @mode_valid: a valid mode has been set
> + * @num_connector_ids: the number of connector-ids
> + * @connector_ids: array of connector ids
> + * @mode: current mode timings
> + * @fb: the framebuffer that the CRTC is currently bound to
> + * @x: x position on screen
> + * @y: y position on screen
> + * @event: pending pageflip event
> + * @propvals: property values
> + * @state: current global/toplevel state object (for atomic) while an
> + *    update is in progress, NULL otherwise.
> + */
> +struct drm_crtc_state {
> +	bool invert_dimensions : 1;
> +	bool set_config        : 1;
> +	bool new_fb            : 1;
> +	bool mode_valid        : 1;  /* drop this if when mode a refcnt'd ptr */
> +	uint8_t num_connector_ids;
> +	uint32_t *connector_ids;
> +	struct drm_mode_modeinfo mode;
> +	struct drm_framebuffer *fb;
> +	int x, y;
> +
> +	struct drm_pending_vblank_event *event;
> +
> +	struct drm_object_property_values propvals;
> +
> +	void *state;
> +};
> +
> +/**
>   * drm_crtc - central CRTC control structure
>   * @dev: parent DRM device
>   * @head: list management
> + * @id: CRTC number, 0..n
>   * @base: base KMS object for ID tracking etc.
> + * @state: the mutable state
>   * @enabled: is this CRTC enabled?
> - * @mode: current mode timings
>   * @hwmode: mode timings as programmed to hw regs
> - * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
> - *    invert the width/height of the crtc.  This is used if the driver
> - *    is performing 90 or 270 degree rotated scanout
> - * @x: x position on screen
> - * @y: y position on screen
>   * @funcs: CRTC control functions
>   * @gamma_size: size of gamma ramp
>   * @gamma_store: gamma ramp values
> @@ -418,6 +451,8 @@ struct drm_crtc {
>  	struct drm_device *dev;
>  	struct list_head head;
>  
> +	int id;
> +
>  	/**
>  	 * crtc mutex
>  	 *
> @@ -429,8 +464,7 @@ struct drm_crtc {
>  
>  	struct drm_mode_object base;
>  
> -	/* framebuffer the connector is currently bound to */
> -	struct drm_framebuffer *fb;
> +	struct drm_crtc_state *state;
>  
>  	/* Temporary tracking of the old fb while a modeset is ongoing. Used
>  	 * by drm_mode_set_config_internal to implement correct refcounting. */
> @@ -438,17 +472,11 @@ struct drm_crtc {
>  
>  	bool enabled;
>  
> -	/* Requested mode from modesetting. */
> -	struct drm_display_mode mode;
> -
>  	/* Programmed mode in hw, after adjustments for encoders,
>  	 * crtc, panel scaling etc. Needed for timestamping etc.
>  	 */
>  	struct drm_display_mode hwmode;
>  
> -	bool invert_dimensions;
> -
> -	int x, y;
>  	const struct drm_crtc_funcs *funcs;
>  
>  	/* CRTC gamma size for reporting to userspace */
> @@ -462,7 +490,15 @@ struct drm_crtc {
>  	void *helper_private;
>  
>  	struct drm_object_properties properties;
> -	struct drm_object_property_values propvals;
> +
> +	/* These are (temporary) duplicate information from what is in the
> +	 * drm_crtc_state struct..  keeping duplicate copy here makes the
> +	 * switch to atomic far less intrusive.  Once all the drivers and
> +	 * the crtc/fb helpers are updated, then we can remove these:
> +	 */
> +	struct drm_framebuffer *fb;
> +	int x, y;
> +	struct drm_display_mode mode;
>  };
>  
>  
> @@ -941,6 +977,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;
>  
> @@ -996,6 +1034,14 @@ extern int drm_crtc_init(struct drm_device *dev,
>  			 struct drm_crtc *crtc,
>  			 const struct drm_crtc_funcs *funcs);
>  extern void drm_crtc_cleanup(struct drm_crtc *crtc);
> +extern int drm_crtc_check_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state);
> +extern void drm_crtc_commit_state(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state);
> +extern int drm_crtc_set_property(struct drm_crtc *crtc,
> +		struct drm_crtc_state *state,
> +		struct drm_property *property,
> +		uint64_t value, void *blob_data);
>  
>  extern void drm_connector_ida_init(void);
>  extern void drm_connector_ida_destroy(void);
> @@ -1045,6 +1091,7 @@ extern const char *drm_get_tv_subconnector_name(int val);
>  extern const char *drm_get_tv_select_name(int val);
>  extern void drm_fb_release(struct drm_file *file_priv);
>  extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group);
> +extern int drm_crtc_convert_umode(struct drm_display_mode *out, const struct drm_mode_modeinfo *in);
>  extern bool drm_probe_ddc(struct i2c_adapter *adapter);
>  extern struct edid *drm_get_edid(struct drm_connector *connector,
>  				 struct i2c_adapter *adapter);
> -- 
> 1.8.3.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
Rob Clark Oct. 23, 2013, 1:13 a.m. UTC | #2
On Tue, Oct 22, 2013 at 6:27 PM, Matt Roper <matthew.d.roper@intel.com> wrote:
> On Mon, Oct 14, 2013 at 01:26:46PM -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.
>> ---
>>  drivers/gpu/drm/ast/ast_mode.c             |   1 +
>>  drivers/gpu/drm/cirrus/cirrus_mode.c       |   1 +
>>  drivers/gpu/drm/drm_atomic_helper.c        | 249 +++++++++++++++-
>>  drivers/gpu/drm/drm_crtc.c                 | 458 +++++++++++++++--------------
>>  drivers/gpu/drm/exynos/exynos_drm_crtc.c   |   5 +-
>>  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/mdp4/mdp4_crtc.c       |   5 +-
>>  drivers/gpu/drm/nouveau/dispnv04/crtc.c    |   1 +
>>  drivers/gpu/drm/nouveau/nv50_display.c     |   1 +
>>  drivers/gpu/drm/omapdrm/omap_crtc.c        |   9 +-
>>  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 +
>>  drivers/gpu/host1x/drm/dc.c                |   1 +
>>  drivers/staging/imx-drm/ipuv3-crtc.c       |   1 +
>>  include/drm/drm_atomic_helper.h            |  37 +++
>>  include/drm/drm_crtc.h                     |  77 ++++-
>>  26 files changed, 627 insertions(+), 238 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
>> index 7fc9f72..13f6943 100644
>> --- a/drivers/gpu/drm/ast/ast_mode.c
>> +++ b/drivers/gpu/drm/ast/ast_mode.c
>> @@ -619,6 +619,7 @@ static const struct drm_crtc_funcs ast_crtc_funcs = {
>>       .cursor_move = ast_cursor_move,
>>       .reset = ast_crtc_reset,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .gamma_set = ast_crtc_gamma_set,
>>       .destroy = ast_crtc_destroy,
>>  };
>> diff --git a/drivers/gpu/drm/cirrus/cirrus_mode.c b/drivers/gpu/drm/cirrus/cirrus_mode.c
>> index 60685b2..2d8b4ce 100644
>> --- a/drivers/gpu/drm/cirrus/cirrus_mode.c
>> +++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
>> @@ -363,6 +363,7 @@ static void cirrus_crtc_destroy(struct drm_crtc *crtc)
>>  static const struct drm_crtc_funcs cirrus_crtc_funcs = {
>>       .gamma_set = cirrus_crtc_gamma_set,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = cirrus_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
>> index 0618113..91b7580 100644
>> --- a/drivers/gpu/drm/drm_atomic_helper.c
>> +++ b/drivers/gpu/drm/drm_atomic_helper.c
>> @@ -40,11 +40,13 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
>>  {
>>       struct drm_atomic_helper_state *state;
>>       int nplanes = dev->mode_config.num_plane;
>> +     int ncrtcs  = dev->mode_config.num_crtc;
>>       int sz;
>>       void *ptr;
>>
>>       sz = sizeof(*state);
>>       sz += (sizeof(state->planes) + sizeof(state->pstates)) * nplanes;
>> +     sz += (sizeof(state->crtcs) + sizeof(state->cstates)) * ncrtcs;
>>
>>       ptr = kzalloc(sz, GFP_KERNEL);
>>
>> @@ -61,6 +63,12 @@ void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
>>       state->pstates = ptr;
>>       ptr = &state->pstates[nplanes];
>>
>> +     state->crtcs = ptr;
>> +     ptr = &state->crtcs[ncrtcs];
>> +
>> +     state->cstates = ptr;
>> +     ptr = &state->cstates[ncrtcs];
>> +
>>       return state;
>>  }
>>  EXPORT_SYMBOL(drm_atomic_helper_begin);
>> @@ -79,7 +87,16 @@ int drm_atomic_helper_set_event(struct drm_device *dev,
>>               void *state, struct drm_mode_object *obj,
>>               struct drm_pending_vblank_event *event)
>>  {
>> -     return -EINVAL;  /* for now */
>> +     switch (obj->type) {
>> +     case DRM_MODE_OBJECT_CRTC: {
>> +             struct drm_crtc_state *cstate =
>> +                     drm_atomic_get_crtc_state(obj_to_crtc(obj), state);
>> +             cstate->event = event;
>> +             return 0;
>> +     }
>> +     default:
>> +             return -EINVAL;
>> +     }
>>  }
>>  EXPORT_SYMBOL(drm_atomic_helper_set_event);
>>
>> @@ -98,6 +115,7 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
>>  {
>>       struct drm_atomic_helper_state *a = state;
>>       int nplanes = dev->mode_config.num_plane;
>> +     int ncrtcs = dev->mode_config.num_crtc;
>>       int i, ret = 0;
>>
>>       for (i = 0; i < nplanes; i++) {
>> @@ -108,6 +126,14 @@ int drm_atomic_helper_check(struct drm_device *dev, void *state)
>>               }
>>       }
>>
>> +     for (i = 0; i < ncrtcs; i++) {
>> +             if (a->crtcs[i]) {
>> +                     ret = drm_atomic_check_crtc_state(a->crtcs[i], a->cstates[i]);
>> +                     if (ret)
>> +                             break;
>> +             }
>> +     }
>> +
>>       return ret;
>>  }
>>  EXPORT_SYMBOL(drm_atomic_helper_check);
>> @@ -127,6 +153,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
>>  {
>>       struct drm_atomic_helper_state *a = state;
>>       int nplanes = dev->mode_config.num_plane;
>> +     int ncrtcs = dev->mode_config.num_crtc;
>>       int i, ret = 0;
>>
>>       for (i = 0; i < nplanes; i++) {
>> @@ -137,6 +164,14 @@ int drm_atomic_helper_commit(struct drm_device *dev, void *state)
>>               }
>>       }
>>
>> +     for (i = 0; i < ncrtcs; i++) {
>> +             if (a->crtcs[i]) {
>> +                     ret = drm_atomic_commit_crtc_state(a->crtcs[i], a->cstates[i]);
>> +                     if (ret)
>> +                             break;
>> +             }
>> +     }
>> +
>>       return ret;
>>  }
>>  EXPORT_SYMBOL(drm_atomic_helper_commit);
>> @@ -160,6 +195,7 @@ void _drm_atomic_helper_state_free(struct kref *kref)
>>               container_of(kref, struct drm_atomic_helper_state, refcount);
>>       struct drm_device *dev = state->dev;
>>       int nplanes = dev->mode_config.num_plane;
>> +     int ncrtcs = dev->mode_config.num_crtc;
>>       int i;
>>
>>       for (i = 0; i < nplanes; i++) {
>> @@ -169,6 +205,13 @@ void _drm_atomic_helper_state_free(struct kref *kref)
>>               }
>>       }
>>
>> +     for (i = 0; i < ncrtcs; i++) {
>> +             if (state->cstates[i]) {
>> +                     state->crtcs[i]->state->state = NULL;
>> +                     kfree(state->cstates[i]);
>> +             }
>> +     }
>> +
>>       kfree(state);
>>  }
>>  EXPORT_SYMBOL(_drm_atomic_helper_state_free);
>> @@ -260,9 +303,213 @@ drm_atomic_helper_commit_plane_state(struct drm_plane *plane,
>>       return ret;
>>  }
>>
>> +int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
>> +             struct drm_property *property, uint64_t val, void *blob_data)
>> +{
>> +     return drm_crtc_set_property(crtc,
>> +                     drm_atomic_get_crtc_state(crtc, state),
>> +                     property, val, blob_data);
>> +}
>> +EXPORT_SYMBOL(drm_atomic_helper_crtc_set_property);
>> +
>> +void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *cstate, void *state)
>> +{
>> +     /* snapshot current state: */
>> +     *cstate = *crtc->state;
>> +     cstate->state = state;
>> +}
>> +EXPORT_SYMBOL(drm_atomic_helper_init_crtc_state);
>> +
>> +static struct drm_crtc_state *
>> +drm_atomic_helper_get_crtc_state(struct drm_crtc *crtc, void *state)
>> +{
>> +     struct drm_atomic_helper_state *a = state;
>> +     struct drm_crtc_state *cstate = a->cstates[crtc->id];
>> +     if (!cstate) {
>> +             cstate = kzalloc(sizeof(*cstate), GFP_KERNEL);
>> +             drm_atomic_helper_init_crtc_state(crtc, cstate, state);
>> +             a->crtcs[crtc->id] = crtc;
>> +             a->cstates[crtc->id] = cstate;
>> +     }
>> +     return cstate;
>> +}
>> +
>> +static void
>> +swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_helper_state *a)
>> +{
>> +     struct drm_crtc_state *cstate = a->cstates[crtc->id];
>> +     struct drm_device *dev = crtc->dev;
>> +     struct drm_pending_vblank_event *event = cstate->event;
>> +     if (event) {
>> +             /* hrm, need to sort out a better way to send events for
>> +              * other-than-pageflip.. but modeset is not async, so:
>> +              */
>> +             unsigned long flags;
>> +             DRM_ERROR("event=%p\n", event);
>> +             spin_lock_irqsave(&dev->event_lock, flags);
>> +             drm_send_vblank_event(dev, crtc->id, event);
>> +             cstate->event = NULL;
>> +             spin_unlock_irqrestore(&dev->event_lock, flags);
>> +     }
>> +     swap(crtc->state, a->cstates[crtc->id]);
>> +     crtc->base.propvals = &crtc->state->propvals;
>> +}
>> +
>> +static struct drm_connector **get_connector_set(struct drm_device *dev,
>> +             uint32_t *connector_ids, uint32_t num_connector_ids)
>> +{
>> +     struct drm_connector **connector_set = NULL;
>> +     int i;
>> +
>> +     connector_set = kmalloc(num_connector_ids *
>> +                     sizeof(struct drm_connector *),
>> +                     GFP_KERNEL);
>> +     if (!connector_set)
>> +             return NULL;
>> +
>> +     for (i = 0; i < num_connector_ids; i++)
>> +             connector_set[i] = drm_connector_find(dev, connector_ids[i]);
>> +
>> +     return connector_set;
>> +}
>> +
>> +static struct drm_display_mode *get_mode(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
>> +{
>> +     struct drm_display_mode *mode = NULL;
>> +     if (cstate->mode_valid) {
>> +             struct drm_device *dev = crtc->dev;
>> +             int ret;
>> +
>> +             mode = drm_mode_create(dev);
>> +             if (!mode)
>> +                     return ERR_PTR(-ENOMEM);
>> +
>> +             ret = drm_crtc_convert_umode(mode, &cstate->mode);
>> +             if (ret) {
>> +                     DRM_DEBUG_KMS("Invalid mode\n");
>> +                     drm_mode_destroy(dev, mode);
>> +                     return ERR_PTR(ret);
>> +             }
>> +
>> +             drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
>> +     }
>> +     return mode;
>> +}
>> +
>> +static int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
>> +{
>> +     struct drm_device *dev = crtc->dev;
>> +     struct drm_framebuffer *fb = cstate->fb;
>> +     struct drm_connector **connector_set = get_connector_set(crtc->dev,
>> +                     cstate->connector_ids, cstate->num_connector_ids);
>> +     struct drm_display_mode *mode = get_mode(crtc, cstate);
>> +     struct drm_mode_set set = {
>> +                     .crtc = crtc,
>> +                     .x = cstate->x,
>> +                     .y = cstate->y,
>> +                     .mode = mode,
>> +                     .num_connectors = cstate->num_connector_ids,
>> +                     .connectors = connector_set,
>> +                     .fb = fb,
>> +     };
>> +     int ret;
>> +
>> +     if (IS_ERR(mode)) {
>> +             ret = PTR_ERR(mode);
>> +             return ret;
>> +     }
>> +
>> +     drm_modeset_lock_all(dev);
>> +
>> +     ret = drm_mode_set_config_internal(&set);
>> +     if (!ret)
>> +             swap_crtc_state(crtc, cstate->state);
>> +
>> +     if (fb)
>> +             drm_framebuffer_unreference(fb);
>> +
>> +     kfree(connector_set);
>> +     drm_modeset_unlock_all(dev);
>> +     if (mode)
>> +             drm_mode_destroy(dev, mode);
>> +     return ret;
>> +}
>> +
>> +static int
>> +drm_atomic_helper_commit_crtc_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *cstate)
>> +{
>> +     struct drm_framebuffer *old_fb = NULL, *fb = NULL;
>> +     struct drm_atomic_helper_state *a = cstate->state;
>> +     int ret = -EINVAL;
>> +
>> +     if (cstate->set_config) {
>> +             cstate->set_config = false;
>> +             return set_config(crtc, cstate);
>> +     }
>> +
>> +     mutex_lock(&crtc->mutex);
>> +     if (cstate->fb) {
>> +             /* pageflip */
>> +
>> +             if (crtc->fb == NULL) {
>> +                     /* The framebuffer is currently unbound, presumably
>> +                      * due to a hotplug event, that userspace has not
>> +                      * yet discovered.
>> +                      */
>> +                     ret = -EBUSY;
>> +                     goto out;
>> +             }
>> +
>> +             if (crtc->funcs->page_flip == NULL)
>> +                     goto out;
>> +
>> +             old_fb = crtc->fb;
>> +             fb = cstate->fb;
>> +
>> +             ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
>> +             if (ret) {
>> +                     /* Keep the old fb, don't unref it. */
>> +                     old_fb = NULL;
>> +             } else {
>> +                     cstate->event = NULL;
>> +                     swap_crtc_state(crtc, cstate->state);
>> +                     /* Unref only the old framebuffer. */
>> +                     fb = NULL;
>> +             }
>> +     } else {
>> +             /* disable */
>> +             struct drm_mode_set set = {
>> +                             .crtc = crtc,
>> +                             .fb = NULL,
>> +             };
>> +
>> +             old_fb = crtc->state->fb;
>> +             ret = drm_mode_set_config_internal(&set);
>> +             if (!ret) {
>> +                     swap_crtc_state(crtc, cstate->state);
>> +             }
>> +     }
>> +
>> +out:
>> +     mutex_unlock(&crtc->mutex);
>> +
>> +     if (fb)
>> +             drm_framebuffer_unreference(fb);
>> +     if (old_fb)
>> +             drm_framebuffer_unreference(old_fb);
>> +
>> +     return ret;
>> +}
>> +
>>  const struct drm_atomic_helper_funcs drm_atomic_helper_funcs = {
>>               .get_plane_state    = drm_atomic_helper_get_plane_state,
>>               .check_plane_state  = drm_plane_check_state,
>>               .commit_plane_state = drm_atomic_helper_commit_plane_state,
>> +
>> +             .get_crtc_state     = drm_atomic_helper_get_crtc_state,
>> +             .check_crtc_state   = drm_crtc_check_state,
>> +             .commit_crtc_state  = drm_atomic_helper_commit_crtc_state,
>>  };
>>  EXPORT_SYMBOL(drm_atomic_helper_funcs);
>> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
>> index 3cf235e..1091d82 100644
>> --- a/drivers/gpu/drm/drm_crtc.c
>> +++ b/drivers/gpu/drm/drm_crtc.c
>> @@ -574,8 +574,6 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>>       struct drm_device *dev = fb->dev;
>>       struct drm_crtc *crtc;
>>       struct drm_plane *plane;
>> -     struct drm_mode_set set;
>> -     int ret;
>>
>>       WARN_ON(!list_empty(&fb->filp_head));
>>
>> @@ -595,6 +593,7 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>>        * in this manner.
>>        */
>>       if (atomic_read(&fb->refcount.refcount) > 1) {
>> +             struct drm_mode_config *config = &fb->dev->mode_config;
>>               void *state;
>>
>>               state = dev->driver->atomic_begin(dev, 0);
>> @@ -603,22 +602,12 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
>>                       return;
>>               }
>>
>> -             /* TODO once CRTC is converted to state/properties, we can push the
>> -              * locking down into drm_atomic_helper_commit(), since that is where
>> -              * the actual changes take place..
>> -              */
>> -             drm_modeset_lock_all(dev);
>> -
>>               /* remove from any CRTC */
>>               list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
>>                       if (crtc->fb == fb) {
>>                               /* should turn off the crtc */
>> -                             memset(&set, 0, sizeof(struct drm_mode_set));
>> -                             set.crtc = crtc;
>> -                             set.fb = NULL;
>> -                             ret = drm_mode_set_config_internal(&set);
>> -                             if (ret)
>> -                                     DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
>> +                             drm_mode_crtc_set_obj_prop(crtc, state,
>> +                                     config->prop_fb_id, 0, NULL);
>>                       }
>>               }
>>
>> @@ -634,8 +623,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);
>> @@ -656,11 +643,15 @@ EXPORT_SYMBOL(drm_framebuffer_remove);
>>  int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
>>                  const struct drm_crtc_funcs *funcs)
>>  {
>> +     struct drm_mode_config *config = &dev->mode_config;
>>       int ret;
>>
>> +     if (!crtc->state)
>> +             crtc->state = kzalloc(sizeof(crtc->state), GFP_KERNEL);
>> +
>>       crtc->dev = dev;
>>       crtc->funcs = funcs;
>> -     crtc->invert_dimensions = false;
>> +     crtc->state->invert_dimensions = false;
>>
>>       drm_modeset_lock_all(dev);
>>       mutex_init(&crtc->mutex);
>> @@ -671,11 +662,17 @@ int drm_crtc_init(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++;
>>
>> +     drm_object_attach_property(&crtc->base, config->prop_mode, 0);
>> +     drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
>> +     drm_object_attach_property(&crtc->base, config->prop_fb_id, 0);
>> +     drm_object_attach_property(&crtc->base, config->prop_src_x, 0);
>> +     drm_object_attach_property(&crtc->base, config->prop_src_y, 0);
>> +
>>   out:
>>       drm_modeset_unlock_all(dev);
>>
>> @@ -704,6 +701,112 @@ void drm_crtc_cleanup(struct drm_crtc *crtc)
>>  }
>>  EXPORT_SYMBOL(drm_crtc_cleanup);
>>
>> +int drm_crtc_check_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state)
>> +{
>> +     struct drm_framebuffer *fb = state->fb;
>> +     int hdisplay, vdisplay;
>> +
>> +     /* disabling the crtc is allowed: */
>> +     if (!(fb && state->mode_valid))
>> +             return 0;
>> +
>> +     hdisplay = state->mode.hdisplay;
>> +     vdisplay = state->mode.vdisplay;
>> +
>> +     if (state->invert_dimensions)
>> +             swap(hdisplay, vdisplay);
>> +
>> +     /* For some reason crtc x/y offsets are signed internally. */
>> +     if (state->x > INT_MAX || state->y > INT_MAX)
>> +             return -ERANGE;
>> +
>> +     if (hdisplay > fb->width ||
>> +         vdisplay > fb->height ||
>> +         state->x > fb->width - hdisplay ||
>> +         state->y > fb->height - vdisplay) {
>> +             DRM_DEBUG_KMS("Invalid fb size %ux%u for CRTC viewport %ux%u+%d+%d%s.\n",
>> +                           fb->width, fb->height, hdisplay, vdisplay,
>> +                           state->x, state->y,
>> +                           state->invert_dimensions ? " (inverted)" : "");
>> +             return -ENOSPC;
>> +     }
>> +
>> +     if (crtc->enabled && !state->set_config) {
>> +             if (crtc->state->fb->pixel_format != fb->pixel_format) {
>> +                     DRM_DEBUG_KMS("Page flip is not allowed to "
>> +                                     "change frame buffer format.\n");
>> +                     return -EINVAL;
>> +             }
>
> Is there a reason to disallow this in the general case?  I realize some
> hardware may not be able to handle a pixel format change at pageflip
> time, but on hardware that can, this is likely to be useful when working
> with compositors that may flip directly to client buffers that come in
> different pixel formats than whatever the previous frame was using.

fwiw, this is just preserving the existing sanity checking that drm
core is already doing (which I thought was added for i915?).  Perhaps
a caps flag could be added (I know of a few drivers that could use
this), but that is subject of another patch.

> I'm guessing most drivers won't be able to use drm_crtc_check_state()
> standalone since at the very least they'll need to wrap their own logic
> on top of it to filter the list of pixel formats that their display
> controller can actually handle.  Would it make more sense to push this
> pixel format switching check up to that driver-specific wrapper level so
> that we're not creating artificial limitations for some hardware?

wrapping would be the way to go for hw specific checks.  But pixel
format is a generic thing (ie. all drivers support some particular set
of pixel formats).  My plan after atomic is done is to then split out
primary plane from crtc, and do better pixel format checking in the
plane->check_state().

BR,
-R

>> +     }
>> +
>> +     if (state->num_connector_ids == 0) {
>> +             DRM_DEBUG_KMS("Count connectors is 0 but mode set\n");
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +EXPORT_SYMBOL(drm_crtc_check_state);
>> +
>> +void drm_crtc_commit_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state)
>> +{
>> +     crtc->state = state;
>> +     crtc->base.propvals = &state->propvals;
>> +}
>> +EXPORT_SYMBOL(drm_crtc_commit_state);
>> +
>> +int drm_crtc_set_property(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state,
>> +             struct drm_property *property,
>> +             uint64_t value, void *blob_data)
>> +{
>> +     struct drm_device *dev = crtc->dev;
>> +     struct drm_mode_config *config = &dev->mode_config;
>> +
>> +     drm_object_property_set_value(&crtc->base,
>> +                     &state->propvals, property, value, blob_data);
>> +
>> +     if (property == config->prop_mode) {
>> +             if (!blob_data) {
>> +                     memset(&state->mode, 0, sizeof(state->mode));
>> +                     state->mode_valid = false;
>> +             } else {
>> +                     /* check size: */
>> +                     if (value < sizeof(struct drm_mode_modeinfo))
>> +                             return -EINVAL;
>> +                     state->mode = *(struct drm_mode_modeinfo *)blob_data;
>> +                     state->mode_valid = true;
>> +             }
>> +             state->set_config = true;
>> +     } else if (property == config->prop_connector_ids) {
>> +             state->num_connector_ids = value / sizeof(state->connector_ids[0]);
>> +             state->connector_ids = blob_data;
>> +             state->set_config = true;
>> +     } else if (property == config->prop_fb_id) {
>> +             state->new_fb = true;
>> +             state->fb = drm_framebuffer_lookup(dev, value);
>> +     } else if (property == config->prop_src_x) {
>> +             int x = *(int *)&value;
>> +             if (state->x != x) {
>> +                     state->x = x;
>> +                     state->set_config = true;
>> +             }
>> +     } else if (property == config->prop_src_y) {
>> +             int y = *(int *)&value;
>> +             if (state->y != y) {
>> +                     state->y = y;
>> +                     state->set_config = true;
>> +             }
>> +     } else {
>> +             return -EINVAL;
>> +     }
>> +
>> +     return 0;
>> +}
>> +EXPORT_SYMBOL(drm_crtc_set_property);
>> +
>>  /**
>>   * drm_mode_probed_add - add a mode to a connector's probed mode list
>>   * @connector: connector the new mode
>> @@ -1265,6 +1368,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;
>>  }
>>
>> @@ -1529,7 +1642,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)
>> @@ -1775,11 +1888,11 @@ int drm_mode_getcrtc(struct drm_device *dev,
>>       }
>>       crtc = obj_to_crtc(obj);
>>
>> -     crtc_resp->x = crtc->x;
>> -     crtc_resp->y = crtc->y;
>> +     crtc_resp->x = crtc->state->x;
>> +     crtc_resp->y = crtc->state->y;
>>       crtc_resp->gamma_size = crtc->gamma_size;
>> -     if (crtc->fb)
>> -             crtc_resp->fb_id = crtc->fb->base.id;
>> +     if (crtc->state->fb)
>> +             crtc_resp->fb_id = crtc->state->fb->base.id;
>>       else
>>               crtc_resp->fb_id = 0;
>>
>> @@ -2209,22 +2322,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>>       struct drm_mode_crtc *crtc_req = data;
>>       struct drm_mode_object *obj;
>>       struct drm_crtc *crtc;
>> -     struct drm_connector **connector_set = NULL, *connector;
>> -     struct drm_framebuffer *fb = NULL;
>> -     struct drm_display_mode *mode = NULL;
>> -     struct drm_mode_set set;
>> -     uint32_t __user *set_connectors_ptr;
>> +     uint32_t fb_id = -1;
>> +     uint32_t *connector_ids = NULL;
>> +     void *state = NULL;
>>       int ret;
>>       int i;
>>
>>       if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>               return -EINVAL;
>>
>> -     /* For some reason crtc x/y offsets are signed internally. */
>> -     if (crtc_req->x > INT_MAX || crtc_req->y > INT_MAX)
>> -             return -ERANGE;
>> -
>> -     drm_modeset_lock_all(dev);
>>       obj = drm_mode_object_find(dev, crtc_req->crtc_id,
>>                                  DRM_MODE_OBJECT_CRTC);
>>       if (!obj) {
>> @@ -2236,7 +2342,6 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>>       DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
>>
>>       if (crtc_req->mode_valid) {
>> -             int hdisplay, vdisplay;
>>               /* If we have a mode we need a framebuffer. */
>>               /* If we pass -1, set the mode with the currently bound fb */
>>               if (crtc_req->fb_id == -1) {
>> @@ -2245,67 +2350,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>>                               ret = -EINVAL;
>>                               goto out;
>>                       }
>> -                     fb = crtc->fb;
>> -                     /* Make refcounting symmetric with the lookup path. */
>> -                     drm_framebuffer_reference(fb);
>> +                     fb_id = crtc->base.id;
>>               } else {
>> -                     fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
>> -                     if (!fb) {
>> -                             DRM_DEBUG_KMS("Unknown FB ID%d\n",
>> -                                             crtc_req->fb_id);
>> -                             ret = -EINVAL;
>> -                             goto out;
>> -                     }
>> +                     fb_id = crtc_req->fb_id;
>>               }
>> -
>> -             mode = drm_mode_create(dev);
>> -             if (!mode) {
>> -                     ret = -ENOMEM;
>> -                     goto out;
>> -             }
>> -
>> -             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);
>> -
>> -             hdisplay = mode->hdisplay;
>> -             vdisplay = mode->vdisplay;
>> -
>> -             if (crtc->invert_dimensions)
>> -                     swap(hdisplay, vdisplay);
>> -
>> -             if (hdisplay > fb->width ||
>> -                 vdisplay > fb->height ||
>> -                 crtc_req->x > fb->width - hdisplay ||
>> -                 crtc_req->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, crtc_req->x, crtc_req->y,
>> -                                   crtc->invert_dimensions ? " (inverted)" : "");
>> -                     ret = -ENOSPC;
>> -                     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) {
>> @@ -2313,54 +2366,52 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
>>                       goto out;
>>               }
>>
>> -             connector_set = kmalloc(crtc_req->count_connectors *
>> -                                     sizeof(struct drm_connector *),
>> +             connector_ids = kmalloc(crtc_req->count_connectors *
>> +                                     sizeof(connector_ids[0]),
>>                                       GFP_KERNEL);
>> -             if (!connector_set) {
>> +             if (!connector_ids) {
>>                       ret = -ENOMEM;
>>                       goto out;
>>               }
>>
>>               for (i = 0; i < crtc_req->count_connectors; i++) {
>> -                     set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
>> +                     u32 out_id;
>> +
>>                       if (get_user(out_id, &set_connectors_ptr[i])) {
>>                               ret = -EFAULT;
>>                               goto out;
>>                       }
>> -
>> -                     obj = drm_mode_object_find(dev, out_id,
>> -                                                DRM_MODE_OBJECT_CONNECTOR);
>> -                     if (!obj) {
>> -                             DRM_DEBUG_KMS("Connector id %d unknown\n",
>> -                                             out_id);
>> -                             ret = -EINVAL;
>> -                             goto out;
>> -                     }
>> -                     connector = obj_to_connector(obj);
>> -                     DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
>> -                                     connector->base.id,
>> -                                     drm_get_connector_name(connector));
>> -
>> -                     connector_set[i] = connector;
>> +                     connector_ids[i] = out_id;
>>               }
>>       }
>>
>> -     set.crtc = crtc;
>> -     set.x = crtc_req->x;
>> -     set.y = crtc_req->y;
>> -     set.mode = mode;
>> -     set.connectors = connector_set;
>> -     set.num_connectors = crtc_req->count_connectors;
>> -     set.fb = fb;
>> -     ret = drm_mode_set_config_internal(&set);
>> +     state = dev->driver->atomic_begin(dev, 0);
>> +     if (IS_ERR(state))
>> +             return PTR_ERR(state);
>>
>> -out:
>> -     if (fb)
>> -             drm_framebuffer_unreference(fb);
>> +     ret =
>> +             drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
>> +             drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_connector_ids,
>> +                     crtc_req->count_connectors * sizeof(connector_ids[0]),
>> +                     connector_ids) ||
>> +             drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_fb_id, fb_id, NULL) ||
>> +             drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_src_x, crtc_req->x, NULL) ||
>> +             drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_src_y, crtc_req->y, NULL) ||
>> +             dev->driver->atomic_check(dev, state);
>> +     if (ret)
>> +             goto out;
>>
>> -     kfree(connector_set);
>> -     drm_mode_destroy(dev, mode);
>> -     drm_modeset_unlock_all(dev);
>> +     ret = dev->driver->atomic_commit(dev, state);
>> +
>> +out:
>> +     if (state)
>> +             dev->driver->atomic_end(dev, state);
>> +     kfree(connector_ids);
>>       return ret;
>>  }
>>
>> @@ -3467,9 +3518,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;
>>  }
>> @@ -3795,16 +3843,60 @@ out:
>>       return ret;
>>  }
>>
>> +static struct drm_pending_vblank_event *create_vblank_event(
>> +             struct drm_device *dev, struct drm_file *file_priv, uint64_t user_data)
>> +{
>> +     struct drm_pending_vblank_event *e = NULL;
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&dev->event_lock, flags);
>> +     if (file_priv->event_space < sizeof e->event) {
>> +             spin_unlock_irqrestore(&dev->event_lock, flags);
>> +             goto out;
>> +     }
>> +     file_priv->event_space -= sizeof e->event;
>> +     spin_unlock_irqrestore(&dev->event_lock, flags);
>> +
>> +     e = kzalloc(sizeof *e, GFP_KERNEL);
>> +     if (e == NULL) {
>> +             spin_lock_irqsave(&dev->event_lock, flags);
>> +             file_priv->event_space += sizeof e->event;
>> +             spin_unlock_irqrestore(&dev->event_lock, flags);
>> +             goto out;
>> +     }
>> +
>> +     e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
>> +     e->event.base.length = sizeof e->event;
>> +     e->event.user_data = user_data;
>> +     e->base.event = &e->event.base;
>> +     e->base.file_priv = file_priv;
>> +     e->base.destroy =
>> +             (void (*) (struct drm_pending_event *)) kfree;
>> +
>> +out:
>> +     return e;
>> +}
>> +
>> +static void destroy_vblank_event(struct drm_device *dev,
>> +             struct drm_file *file_priv, struct drm_pending_vblank_event *e)
>> +{
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&dev->event_lock, flags);
>> +     file_priv->event_space += sizeof e->event;
>> +     spin_unlock_irqrestore(&dev->event_lock, flags);
>> +     kfree(e);
>> +}
>> +
>>  int drm_mode_page_flip_ioctl(struct drm_device *dev,
>>                            void *data, struct drm_file *file_priv)
>>  {
>>       struct drm_mode_crtc_page_flip *page_flip = data;
>> +     struct drm_mode_config *config = &dev->mode_config;
>>       struct drm_mode_object *obj;
>>       struct drm_crtc *crtc;
>> -     struct drm_framebuffer *fb = NULL, *old_fb = NULL;
>>       struct drm_pending_vblank_event *e = NULL;
>> -     unsigned long flags;
>> -     int hdisplay, vdisplay;
>> +     void *state;
>>       int ret = -EINVAL;
>>
>>       if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
>> @@ -3819,103 +3911,37 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
>>               return -EINVAL;
>>       crtc = obj_to_crtc(obj);
>>
>> -     mutex_lock(&crtc->mutex);
>> -     if (crtc->fb == NULL) {
>> -             /* The framebuffer is currently unbound, presumably
>> -              * due to a hotplug event, that userspace has not
>> -              * yet discovered.
>> -              */
>> -             ret = -EBUSY;
>> -             goto out;
>> -     }
>> -
>> -     if (crtc->funcs->page_flip == NULL)
>> -             goto out;
>> -
>> -     fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
>> -     if (!fb)
>> -             goto out;
>> -
>> -     hdisplay = crtc->mode.hdisplay;
>> -     vdisplay = crtc->mode.vdisplay;
>> -
>> -     if (crtc->invert_dimensions)
>> -             swap(hdisplay, vdisplay);
>> -
>> -     if (hdisplay > fb->width ||
>> -         vdisplay > fb->height ||
>> -         crtc->x > fb->width - hdisplay ||
>> -         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, crtc->x, crtc->y,
>> -                           crtc->invert_dimensions ? " (inverted)" : "");
>> -             ret = -ENOSPC;
>> -             goto out;
>> -     }
>> -
>> -     if (crtc->fb->pixel_format != fb->pixel_format) {
>> -             DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
>> -             ret = -EINVAL;
>> -             goto out;
>> -     }
>> +     state = dev->driver->atomic_begin(dev, page_flip->flags);
>> +     if (IS_ERR(state))
>> +             return PTR_ERR(state);
>>
>>       if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
>> -             ret = -ENOMEM;
>> -             spin_lock_irqsave(&dev->event_lock, flags);
>> -             if (file_priv->event_space < sizeof e->event) {
>> -                     spin_unlock_irqrestore(&dev->event_lock, flags);
>> +             e = create_vblank_event(dev, file_priv, page_flip->user_data);
>> +             if (!e) {
>> +                     ret = -ENOMEM;
>>                       goto out;
>>               }
>> -             file_priv->event_space -= sizeof e->event;
>> -             spin_unlock_irqrestore(&dev->event_lock, flags);
>> -
>> -             e = kzalloc(sizeof *e, GFP_KERNEL);
>> -             if (e == NULL) {
>> -                     spin_lock_irqsave(&dev->event_lock, flags);
>> -                     file_priv->event_space += sizeof e->event;
>> -                     spin_unlock_irqrestore(&dev->event_lock, flags);
>> +             ret = dev->driver->atomic_set_event(dev, state, obj, e);
>> +             if (ret) {
>>                       goto out;
>>               }
>> -
>> -             e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
>> -             e->event.base.length = sizeof e->event;
>> -             e->event.user_data = page_flip->user_data;
>> -             e->base.event = &e->event.base;
>> -             e->base.file_priv = file_priv;
>> -             e->base.destroy =
>> -                     (void (*) (struct drm_pending_event *)) kfree;
>>       }
>>
>> -     old_fb = crtc->fb;
>> -     ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
>> -     if (ret) {
>> -             if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
>> -                     spin_lock_irqsave(&dev->event_lock, flags);
>> -                     file_priv->event_space += sizeof e->event;
>> -                     spin_unlock_irqrestore(&dev->event_lock, flags);
>> -                     kfree(e);
>> -             }
>> -             /* Keep the old fb, don't unref it. */
>> -             old_fb = NULL;
>> -     } else {
>> -             /*
>> -              * Warn if the driver hasn't properly updated the crtc->fb
>> -              * field to reflect that the new framebuffer is now used.
>> -              * Failing to do so will screw with the reference counting
>> -              * on framebuffers.
>> -              */
>> -             WARN_ON(crtc->fb != fb);
>> -             /* Unref only the old framebuffer. */
>> -             fb = NULL;
>> -     }
>> +     ret = drm_mode_set_obj_prop(obj, state,
>> +                     config->prop_fb_id, page_flip->fb_id, NULL);
>> +     if (ret)
>> +             goto out;
>>
>> -out:
>> -     if (fb)
>> -             drm_framebuffer_unreference(fb);
>> -     if (old_fb)
>> -             drm_framebuffer_unreference(old_fb);
>> -     mutex_unlock(&crtc->mutex);
>> +     ret = dev->driver->atomic_check(dev, state);
>> +     if (ret)
>> +             goto out;
>> +
>> +     ret = dev->driver->atomic_commit(dev, state);
>>
>> +out:
>> +     if (ret && e)
>> +             destroy_vblank_event(dev, file_priv, e);
>> +     dev->driver->atomic_end(dev, state);
>>       return ret;
>>  }
>>
>> diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
>> index 4ae55b8..c1ef671 100644
>> --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c
>> +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
>> @@ -14,6 +14,7 @@
>>
>>  #include <drm/drmP.h>
>>  #include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_atomic_helper.h>
>>
>>  #include "exynos_drm_crtc.h"
>>  #include "exynos_drm_drv.h"
>> @@ -286,7 +287,9 @@ static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
>>               return 0;
>>       }
>>
>> -     return -EINVAL;
>> +     return drm_crtc_set_property(crtc,
>> +                     drm_atomic_get_crtc_state(crtc, state),
>> +                     property, val, blob_data);
>>  }
>>
>>  static struct drm_crtc_funcs exynos_crtc_funcs = {
>> diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
>> index 8fbfa06..2b4bbf5 100644
>> --- a/drivers/gpu/drm/gma500/cdv_intel_display.c
>> +++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
>> @@ -1022,6 +1022,7 @@ const struct drm_crtc_funcs cdv_intel_crtc_funcs = {
>>       .cursor_move = gma_crtc_cursor_move,
>>       .gamma_set = gma_crtc_gamma_set,
>>       .set_config = gma_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = gma_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
>> index 97f8a03..34d83ff 100644
>> --- a/drivers/gpu/drm/gma500/psb_intel_display.c
>> +++ b/drivers/gpu/drm/gma500/psb_intel_display.c
>> @@ -444,6 +444,7 @@ const struct drm_crtc_funcs psb_intel_crtc_funcs = {
>>       .cursor_move = gma_crtc_cursor_move,
>>       .gamma_set = gma_crtc_gamma_set,
>>       .set_config = gma_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = gma_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
>> index d0eff24..517bf6c 100644
>> --- a/drivers/gpu/drm/i915/intel_display.c
>> +++ b/drivers/gpu/drm/i915/intel_display.c
>> @@ -9277,6 +9277,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = {
>>       .cursor_move = intel_crtc_cursor_move,
>>       .gamma_set = intel_crtc_gamma_set,
>>       .set_config = intel_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = intel_crtc_destroy,
>>       .page_flip = intel_crtc_page_flip,
>>  };
>> diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
>> index 503a414..3bda15b 100644
>> --- a/drivers/gpu/drm/mgag200/mgag200_mode.c
>> +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c
>> @@ -1298,6 +1298,7 @@ static const struct drm_crtc_funcs mga_crtc_funcs = {
>>       .cursor_move = mga_crtc_cursor_move,
>>       .gamma_set = mga_crtc_gamma_set,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = mga_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
>> index 1d6a6ee..1cc8a72 100644
>> --- a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
>> +++ b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
>> @@ -403,8 +403,9 @@ static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
>>  static int mdp4_crtc_set_property(struct drm_crtc *crtc, void *state,
>>               struct drm_property *property, uint64_t val, void *blob_data)
>>  {
>> -     // XXX
>> -     return -EINVAL;
>> +     return drm_crtc_set_property(crtc,
>> +                     drm_atomic_get_crtc_state(crtc, state),
>> +                     property, val, blob_data);
>>  }
>>
>>  #define CURSOR_WIDTH 64
>> diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
>> index d4fbf11..8fee163 100644
>> --- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
>> +++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
>> @@ -1088,6 +1088,7 @@ static const struct drm_crtc_funcs nv04_crtc_funcs = {
>>       .cursor_move = nv04_crtc_cursor_move,
>>       .gamma_set = nv_crtc_gamma_set,
>>       .set_config = nouveau_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .page_flip = nouveau_crtc_page_flip,
>>       .destroy = nv_crtc_destroy,
>>  };
>> diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
>> index f8e66c0..bfcdf8e 100644
>> --- a/drivers/gpu/drm/nouveau/nv50_display.c
>> +++ b/drivers/gpu/drm/nouveau/nv50_display.c
>> @@ -1327,6 +1327,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = {
>>       .cursor_move = nv50_crtc_cursor_move,
>>       .gamma_set = nv50_crtc_gamma_set,
>>       .set_config = nouveau_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = nv50_crtc_destroy,
>>       .page_flip = nouveau_crtc_page_flip,
>>  };
>> diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c
>> index d53a689..054d75e 100644
>> --- a/drivers/gpu/drm/omapdrm/omap_crtc.c
>> +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
>> @@ -367,14 +367,19 @@ static int omap_crtc_set_property(struct drm_crtc *crtc, void *state,
>>  {
>>       struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
>>       struct omap_drm_private *priv = crtc->dev->dev_private;
>> +     struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
>> +     int ret;
>>
>>       if (property == priv->rotation_prop) {
>> -             crtc->invert_dimensions =
>> +             cstate->invert_dimensions =
>>                               !!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270)));
>>       }
>>
>> -     return omap_plane_set_property(omap_crtc->plane, state,
>> +     ret = omap_plane_set_property(omap_crtc->plane, state,
>>                       property, val, blob_data);
>> +     if (ret)
>> +             ret = drm_crtc_set_property(crtc, cstate, property, val, blob_data);
>> +     return ret;
>>  }
>>
>>  static const struct drm_crtc_funcs omap_crtc_funcs = {
>> diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
>> index 0e15e2c..8651a02 100644
>> --- a/drivers/gpu/drm/omapdrm/omap_drv.c
>> +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
>> @@ -553,7 +553,7 @@ static void dev_lastclose(struct drm_device *dev)
>>                */
>>               for (i = 0; i < priv->num_crtcs; i++) {
>>                       drm_object_property_set_value(&priv->crtcs[i]->base,
>> -                                     &priv->crtcs[i]->propvals,
>> +                                     &priv->crtcs[i]->state->propvals,
>>                                       priv->rotation_prop, 0, NULL);
>>               }
>>
>> diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
>> index 9f12205..b138bb1 100644
>> --- a/drivers/gpu/drm/qxl/qxl_display.c
>> +++ b/drivers/gpu/drm/qxl/qxl_display.c
>> @@ -29,6 +29,7 @@
>>  #include "qxl_drv.h"
>>  #include "qxl_object.h"
>>  #include "drm_crtc_helper.h"
>> +#include "drm_atomic_helper.h"
>>
>>  static bool qxl_head_enabled(struct qxl_head *head)
>>  {
>> @@ -365,6 +366,7 @@ static const struct drm_crtc_funcs qxl_crtc_funcs = {
>>       .cursor_set2 = qxl_crtc_cursor_set2,
>>       .cursor_move = qxl_crtc_cursor_move,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = qxl_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c
>> index 0d1aa05..70d1346 100644
>> --- a/drivers/gpu/drm/radeon/radeon_display.c
>> +++ b/drivers/gpu/drm/radeon/radeon_display.c
>> @@ -31,6 +31,7 @@
>>  #include <asm/div64.h>
>>
>>  #include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_atomic_helper.h>
>>  #include <drm/drm_edid.h>
>>
>>  static void avivo_crtc_load_lut(struct drm_crtc *crtc)
>> @@ -499,6 +500,7 @@ static const struct drm_crtc_funcs radeon_crtc_funcs = {
>>       .cursor_move = radeon_crtc_cursor_move,
>>       .gamma_set = radeon_crtc_gamma_set,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = radeon_crtc_destroy,
>>       .page_flip = radeon_crtc_page_flip,
>>  };
>> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
>> index a9d24e4..c840ba8 100644
>> --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
>> +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
>> @@ -17,6 +17,7 @@
>>  #include <drm/drmP.h>
>>  #include <drm/drm_crtc.h>
>>  #include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_atomic_helper.h>
>>  #include <drm/drm_fb_cma_helper.h>
>>  #include <drm/drm_gem_cma_helper.h>
>>
>> @@ -528,6 +529,7 @@ static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
>>  static const struct drm_crtc_funcs crtc_funcs = {
>>       .destroy = drm_crtc_cleanup,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .page_flip = rcar_du_crtc_page_flip,
>>  };
>>
>> diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
>> index e62b0f2..794ff95 100644
>> --- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
>> +++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
>> @@ -17,6 +17,7 @@
>>  #include <drm/drmP.h>
>>  #include <drm/drm_crtc.h>
>>  #include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_atomic_helper.h>
>>  #include <drm/drm_fb_cma_helper.h>
>>  #include <drm/drm_gem_cma_helper.h>
>>
>> @@ -496,6 +497,7 @@ static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
>>  static const struct drm_crtc_funcs crtc_funcs = {
>>       .destroy = drm_crtc_cleanup,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .page_flip = shmob_drm_crtc_page_flip,
>>  };
>>
>> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>> index d36efc1..34d9804 100644
>> --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>> +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>> @@ -411,6 +411,7 @@ static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>>  static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
>>               .destroy        = tilcdc_crtc_destroy,
>>               .set_config     = drm_crtc_helper_set_config,
>> +             .set_property   = drm_atomic_helper_crtc_set_property,
>>               .page_flip      = tilcdc_crtc_page_flip,
>>  };
>>
>> diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c
>> index 2ae1eb7..e05b2ea 100644
>> --- a/drivers/gpu/drm/udl/udl_modeset.c
>> +++ b/drivers/gpu/drm/udl/udl_modeset.c
>> @@ -14,6 +14,7 @@
>>  #include <drm/drmP.h>
>>  #include <drm/drm_crtc.h>
>>  #include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_atomic_helper.h>
>>  #include "udl_drv.h"
>>
>>  /*
>> @@ -383,6 +384,7 @@ static struct drm_crtc_helper_funcs udl_helper_funcs = {
>>
>>  static const struct drm_crtc_funcs udl_crtc_funcs = {
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = udl_crtc_destroy,
>>  };
>>
>> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
>> index 79f7e8e..5d25f21 100644
>> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
>> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
>> @@ -298,6 +298,7 @@ static struct drm_crtc_funcs vmw_legacy_crtc_funcs = {
>>       .cursor_move = vmw_du_crtc_cursor_move,
>>       .gamma_set = vmw_du_crtc_gamma_set,
>>       .destroy = vmw_ldu_crtc_destroy,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .set_config = vmw_ldu_crtc_set_config,
>>  };
>>
>> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
>> index 26387c3..f65fec7 100644
>> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
>> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
>> @@ -394,6 +394,7 @@ static struct drm_crtc_funcs vmw_screen_object_crtc_funcs = {
>>       .gamma_set = vmw_du_crtc_gamma_set,
>>       .destroy = vmw_sou_crtc_destroy,
>>       .set_config = vmw_sou_crtc_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .page_flip = vmw_du_page_flip,
>>  };
>>
>> diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c
>> index 5766010..3a7533e 100644
>> --- a/drivers/gpu/host1x/drm/dc.c
>> +++ b/drivers/gpu/host1x/drm/dc.c
>> @@ -259,6 +259,7 @@ static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
>>  static const struct drm_crtc_funcs tegra_crtc_funcs = {
>>       .page_flip = tegra_dc_page_flip,
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = drm_crtc_cleanup,
>>  };
>>
>> diff --git a/drivers/staging/imx-drm/ipuv3-crtc.c b/drivers/staging/imx-drm/ipuv3-crtc.c
>> index 6fd37a7..f97862e 100644
>> --- a/drivers/staging/imx-drm/ipuv3-crtc.c
>> +++ b/drivers/staging/imx-drm/ipuv3-crtc.c
>> @@ -155,6 +155,7 @@ static int ipu_page_flip(struct drm_crtc *crtc,
>>
>>  static const struct drm_crtc_funcs ipu_crtc_funcs = {
>>       .set_config = drm_crtc_helper_set_config,
>> +     .set_property = drm_atomic_helper_crtc_set_property,
>>       .destroy = drm_crtc_cleanup,
>>       .page_flip = ipu_page_flip,
>>  };
>> diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
>> index d0d29e1..f13f09c 100644
>> --- a/include/drm/drm_atomic_helper.h
>> +++ b/include/drm/drm_atomic_helper.h
>> @@ -65,6 +65,10 @@ struct drm_atomic_helper_funcs {
>>       struct drm_plane_state *(*get_plane_state)(struct drm_plane *plane, void *state);
>>       int (*check_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
>>       int (*commit_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
>> +
>> +     struct drm_crtc_state *(*get_crtc_state)(struct drm_crtc *crtc, void *state);
>> +     int (*check_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
>> +     int (*commit_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
>>  };
>>
>>  const extern struct drm_atomic_helper_funcs drm_atomic_helper_funcs;
>> @@ -108,6 +112,37 @@ drm_atomic_commit_plane_state(struct drm_plane *plane,
>>       return funcs->commit_plane_state(plane, pstate);
>>  }
>>
>> +int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
>> +             struct drm_property *property, uint64_t val, void *blob_data);
>> +void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *cstate, void *state);
>> +
>> +static inline struct drm_crtc_state *
>> +drm_atomic_get_crtc_state(struct drm_crtc *crtc, void *state)
>> +{
>> +     const struct drm_atomic_helper_funcs *funcs =
>> +                     crtc->dev->driver->atomic_helpers;
>> +     return funcs->get_crtc_state(crtc, state);
>> +}
>> +
>> +static inline int
>> +drm_atomic_check_crtc_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *cstate)
>> +{
>> +     const struct drm_atomic_helper_funcs *funcs =
>> +                     crtc->dev->driver->atomic_helpers;
>> +     return funcs->check_crtc_state(crtc, cstate);
>> +}
>> +
>> +static inline int
>> +drm_atomic_commit_crtc_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *cstate)
>> +{
>> +     const struct drm_atomic_helper_funcs *funcs =
>> +                     crtc->dev->driver->atomic_helpers;
>> +     return funcs->commit_crtc_state(crtc, cstate);
>> +}
>> +
>>  /**
>>   * struct drm_atomic_helper_state - the state object used by atomic helpers
>>   */
>> @@ -117,6 +152,8 @@ struct drm_atomic_helper_state {
>>       uint32_t flags;
>>       struct drm_plane **planes;
>>       struct drm_plane_state **pstates;
>> +     struct drm_crtc **crtcs;
>> +     struct drm_crtc_state **cstates;
>>  };
>>
>>  static inline void
>> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
>> index ee46a6a..8227627 100644
>> --- a/include/drm/drm_crtc.h
>> +++ b/include/drm/drm_crtc.h
>> @@ -390,18 +390,51 @@ struct drm_crtc_funcs {
>>  };
>>
>>  /**
>> + * drm_crtc_state - mutable crtc state
>> + * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
>> + *    invert the width/height of the crtc.  This is used if the driver
>> + *    is performing 90 or 270 degree rotated scanout
>> + * @set_config: needs modeset (crtc->set_config())
>> + * @new_fb: has the fb been changed
>> + * @mode_valid: a valid mode has been set
>> + * @num_connector_ids: the number of connector-ids
>> + * @connector_ids: array of connector ids
>> + * @mode: current mode timings
>> + * @fb: the framebuffer that the CRTC is currently bound to
>> + * @x: x position on screen
>> + * @y: y position on screen
>> + * @event: pending pageflip event
>> + * @propvals: property values
>> + * @state: current global/toplevel state object (for atomic) while an
>> + *    update is in progress, NULL otherwise.
>> + */
>> +struct drm_crtc_state {
>> +     bool invert_dimensions : 1;
>> +     bool set_config        : 1;
>> +     bool new_fb            : 1;
>> +     bool mode_valid        : 1;  /* drop this if when mode a refcnt'd ptr */
>> +     uint8_t num_connector_ids;
>> +     uint32_t *connector_ids;
>> +     struct drm_mode_modeinfo mode;
>> +     struct drm_framebuffer *fb;
>> +     int x, y;
>> +
>> +     struct drm_pending_vblank_event *event;
>> +
>> +     struct drm_object_property_values propvals;
>> +
>> +     void *state;
>> +};
>> +
>> +/**
>>   * drm_crtc - central CRTC control structure
>>   * @dev: parent DRM device
>>   * @head: list management
>> + * @id: CRTC number, 0..n
>>   * @base: base KMS object for ID tracking etc.
>> + * @state: the mutable state
>>   * @enabled: is this CRTC enabled?
>> - * @mode: current mode timings
>>   * @hwmode: mode timings as programmed to hw regs
>> - * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
>> - *    invert the width/height of the crtc.  This is used if the driver
>> - *    is performing 90 or 270 degree rotated scanout
>> - * @x: x position on screen
>> - * @y: y position on screen
>>   * @funcs: CRTC control functions
>>   * @gamma_size: size of gamma ramp
>>   * @gamma_store: gamma ramp values
>> @@ -418,6 +451,8 @@ struct drm_crtc {
>>       struct drm_device *dev;
>>       struct list_head head;
>>
>> +     int id;
>> +
>>       /**
>>        * crtc mutex
>>        *
>> @@ -429,8 +464,7 @@ struct drm_crtc {
>>
>>       struct drm_mode_object base;
>>
>> -     /* framebuffer the connector is currently bound to */
>> -     struct drm_framebuffer *fb;
>> +     struct drm_crtc_state *state;
>>
>>       /* Temporary tracking of the old fb while a modeset is ongoing. Used
>>        * by drm_mode_set_config_internal to implement correct refcounting. */
>> @@ -438,17 +472,11 @@ struct drm_crtc {
>>
>>       bool enabled;
>>
>> -     /* Requested mode from modesetting. */
>> -     struct drm_display_mode mode;
>> -
>>       /* Programmed mode in hw, after adjustments for encoders,
>>        * crtc, panel scaling etc. Needed for timestamping etc.
>>        */
>>       struct drm_display_mode hwmode;
>>
>> -     bool invert_dimensions;
>> -
>> -     int x, y;
>>       const struct drm_crtc_funcs *funcs;
>>
>>       /* CRTC gamma size for reporting to userspace */
>> @@ -462,7 +490,15 @@ struct drm_crtc {
>>       void *helper_private;
>>
>>       struct drm_object_properties properties;
>> -     struct drm_object_property_values propvals;
>> +
>> +     /* These are (temporary) duplicate information from what is in the
>> +      * drm_crtc_state struct..  keeping duplicate copy here makes the
>> +      * switch to atomic far less intrusive.  Once all the drivers and
>> +      * the crtc/fb helpers are updated, then we can remove these:
>> +      */
>> +     struct drm_framebuffer *fb;
>> +     int x, y;
>> +     struct drm_display_mode mode;
>>  };
>>
>>
>> @@ -941,6 +977,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;
>>
>> @@ -996,6 +1034,14 @@ extern int drm_crtc_init(struct drm_device *dev,
>>                        struct drm_crtc *crtc,
>>                        const struct drm_crtc_funcs *funcs);
>>  extern void drm_crtc_cleanup(struct drm_crtc *crtc);
>> +extern int drm_crtc_check_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state);
>> +extern void drm_crtc_commit_state(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state);
>> +extern int drm_crtc_set_property(struct drm_crtc *crtc,
>> +             struct drm_crtc_state *state,
>> +             struct drm_property *property,
>> +             uint64_t value, void *blob_data);
>>
>>  extern void drm_connector_ida_init(void);
>>  extern void drm_connector_ida_destroy(void);
>> @@ -1045,6 +1091,7 @@ extern const char *drm_get_tv_subconnector_name(int val);
>>  extern const char *drm_get_tv_select_name(int val);
>>  extern void drm_fb_release(struct drm_file *file_priv);
>>  extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group);
>> +extern int drm_crtc_convert_umode(struct drm_display_mode *out, const struct drm_mode_modeinfo *in);
>>  extern bool drm_probe_ddc(struct i2c_adapter *adapter);
>>  extern struct edid *drm_get_edid(struct drm_connector *connector,
>>                                struct i2c_adapter *adapter);
>> --
>> 1.8.3.1
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> http://lists.freedesktop.org/mailman/listinfo/dri-devel
>
> --
> Matt Roper
> Intel Corporation
> Embedded Media & Graphics Driver Group
> (916) 356-2795
diff mbox

Patch

diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
index 7fc9f72..13f6943 100644
--- a/drivers/gpu/drm/ast/ast_mode.c
+++ b/drivers/gpu/drm/ast/ast_mode.c
@@ -619,6 +619,7 @@  static const struct drm_crtc_funcs ast_crtc_funcs = {
 	.cursor_move = ast_cursor_move,
 	.reset = ast_crtc_reset,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.gamma_set = ast_crtc_gamma_set,
 	.destroy = ast_crtc_destroy,
 };
diff --git a/drivers/gpu/drm/cirrus/cirrus_mode.c b/drivers/gpu/drm/cirrus/cirrus_mode.c
index 60685b2..2d8b4ce 100644
--- a/drivers/gpu/drm/cirrus/cirrus_mode.c
+++ b/drivers/gpu/drm/cirrus/cirrus_mode.c
@@ -363,6 +363,7 @@  static void cirrus_crtc_destroy(struct drm_crtc *crtc)
 static const struct drm_crtc_funcs cirrus_crtc_funcs = {
 	.gamma_set = cirrus_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = cirrus_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 0618113..91b7580 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -40,11 +40,13 @@  void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
 {
 	struct drm_atomic_helper_state *state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs  = dev->mode_config.num_crtc;
 	int sz;
 	void *ptr;
 
 	sz = sizeof(*state);
 	sz += (sizeof(state->planes) + sizeof(state->pstates)) * nplanes;
+	sz += (sizeof(state->crtcs) + sizeof(state->cstates)) * ncrtcs;
 
 	ptr = kzalloc(sz, GFP_KERNEL);
 
@@ -61,6 +63,12 @@  void *drm_atomic_helper_begin(struct drm_device *dev, uint32_t flags)
 	state->pstates = ptr;
 	ptr = &state->pstates[nplanes];
 
+	state->crtcs = ptr;
+	ptr = &state->crtcs[ncrtcs];
+
+	state->cstates = ptr;
+	ptr = &state->cstates[ncrtcs];
+
 	return state;
 }
 EXPORT_SYMBOL(drm_atomic_helper_begin);
@@ -79,7 +87,16 @@  int drm_atomic_helper_set_event(struct drm_device *dev,
 		void *state, struct drm_mode_object *obj,
 		struct drm_pending_vblank_event *event)
 {
-	return -EINVAL;  /* for now */
+	switch (obj->type) {
+	case DRM_MODE_OBJECT_CRTC: {
+		struct drm_crtc_state *cstate =
+			drm_atomic_get_crtc_state(obj_to_crtc(obj), state);
+		cstate->event = event;
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
 }
 EXPORT_SYMBOL(drm_atomic_helper_set_event);
 
@@ -98,6 +115,7 @@  int drm_atomic_helper_check(struct drm_device *dev, void *state)
 {
 	struct drm_atomic_helper_state *a = state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i, ret = 0;
 
 	for (i = 0; i < nplanes; i++) {
@@ -108,6 +126,14 @@  int drm_atomic_helper_check(struct drm_device *dev, void *state)
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		if (a->crtcs[i]) {
+			ret = drm_atomic_check_crtc_state(a->crtcs[i], a->cstates[i]);
+			if (ret)
+				break;
+		}
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_helper_check);
@@ -127,6 +153,7 @@  int drm_atomic_helper_commit(struct drm_device *dev, void *state)
 {
 	struct drm_atomic_helper_state *a = state;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i, ret = 0;
 
 	for (i = 0; i < nplanes; i++) {
@@ -137,6 +164,14 @@  int drm_atomic_helper_commit(struct drm_device *dev, void *state)
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		if (a->crtcs[i]) {
+			ret = drm_atomic_commit_crtc_state(a->crtcs[i], a->cstates[i]);
+			if (ret)
+				break;
+		}
+	}
+
 	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit);
@@ -160,6 +195,7 @@  void _drm_atomic_helper_state_free(struct kref *kref)
 		container_of(kref, struct drm_atomic_helper_state, refcount);
 	struct drm_device *dev = state->dev;
 	int nplanes = dev->mode_config.num_plane;
+	int ncrtcs = dev->mode_config.num_crtc;
 	int i;
 
 	for (i = 0; i < nplanes; i++) {
@@ -169,6 +205,13 @@  void _drm_atomic_helper_state_free(struct kref *kref)
 		}
 	}
 
+	for (i = 0; i < ncrtcs; i++) {
+		if (state->cstates[i]) {
+			state->crtcs[i]->state->state = NULL;
+			kfree(state->cstates[i]);
+		}
+	}
+
 	kfree(state);
 }
 EXPORT_SYMBOL(_drm_atomic_helper_state_free);
@@ -260,9 +303,213 @@  drm_atomic_helper_commit_plane_state(struct drm_plane *plane,
 	return ret;
 }
 
+int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
+		struct drm_property *property, uint64_t val, void *blob_data)
+{
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
+}
+EXPORT_SYMBOL(drm_atomic_helper_crtc_set_property);
+
+void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate, void *state)
+{
+	/* snapshot current state: */
+	*cstate = *crtc->state;
+	cstate->state = state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_init_crtc_state);
+
+static struct drm_crtc_state *
+drm_atomic_helper_get_crtc_state(struct drm_crtc *crtc, void *state)
+{
+	struct drm_atomic_helper_state *a = state;
+	struct drm_crtc_state *cstate = a->cstates[crtc->id];
+	if (!cstate) {
+		cstate = kzalloc(sizeof(*cstate), GFP_KERNEL);
+		drm_atomic_helper_init_crtc_state(crtc, cstate, state);
+		a->crtcs[crtc->id] = crtc;
+		a->cstates[crtc->id] = cstate;
+	}
+	return cstate;
+}
+
+static void
+swap_crtc_state(struct drm_crtc *crtc, struct drm_atomic_helper_state *a)
+{
+	struct drm_crtc_state *cstate = a->cstates[crtc->id];
+	struct drm_device *dev = crtc->dev;
+	struct drm_pending_vblank_event *event = cstate->event;
+	if (event) {
+		/* hrm, need to sort out a better way to send events for
+		 * other-than-pageflip.. but modeset is not async, so:
+		 */
+		unsigned long flags;
+		DRM_ERROR("event=%p\n", event);
+		spin_lock_irqsave(&dev->event_lock, flags);
+		drm_send_vblank_event(dev, crtc->id, event);
+		cstate->event = NULL;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+	swap(crtc->state, a->cstates[crtc->id]);
+	crtc->base.propvals = &crtc->state->propvals;
+}
+
+static struct drm_connector **get_connector_set(struct drm_device *dev,
+		uint32_t *connector_ids, uint32_t num_connector_ids)
+{
+	struct drm_connector **connector_set = NULL;
+	int i;
+
+	connector_set = kmalloc(num_connector_ids *
+			sizeof(struct drm_connector *),
+			GFP_KERNEL);
+	if (!connector_set)
+		return NULL;
+
+	for (i = 0; i < num_connector_ids; i++)
+		connector_set[i] = drm_connector_find(dev, connector_ids[i]);
+
+	return connector_set;
+}
+
+static struct drm_display_mode *get_mode(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_display_mode *mode = NULL;
+	if (cstate->mode_valid) {
+		struct drm_device *dev = crtc->dev;
+		int ret;
+
+		mode = drm_mode_create(dev);
+		if (!mode)
+			return ERR_PTR(-ENOMEM);
+
+		ret = drm_crtc_convert_umode(mode, &cstate->mode);
+		if (ret) {
+			DRM_DEBUG_KMS("Invalid mode\n");
+			drm_mode_destroy(dev, mode);
+			return ERR_PTR(ret);
+		}
+
+		drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+	}
+	return mode;
+}
+
+static int set_config(struct drm_crtc *crtc, struct drm_crtc_state *cstate)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_framebuffer *fb = cstate->fb;
+	struct drm_connector **connector_set = get_connector_set(crtc->dev,
+			cstate->connector_ids, cstate->num_connector_ids);
+	struct drm_display_mode *mode = get_mode(crtc, cstate);
+	struct drm_mode_set set = {
+			.crtc = crtc,
+			.x = cstate->x,
+			.y = cstate->y,
+			.mode = mode,
+			.num_connectors = cstate->num_connector_ids,
+			.connectors = connector_set,
+			.fb = fb,
+	};
+	int ret;
+
+	if (IS_ERR(mode)) {
+		ret = PTR_ERR(mode);
+		return ret;
+	}
+
+	drm_modeset_lock_all(dev);
+
+	ret = drm_mode_set_config_internal(&set);
+	if (!ret)
+		swap_crtc_state(crtc, cstate->state);
+
+	if (fb)
+		drm_framebuffer_unreference(fb);
+
+	kfree(connector_set);
+	drm_modeset_unlock_all(dev);
+	if (mode)
+		drm_mode_destroy(dev, mode);
+	return ret;
+}
+
+static int
+drm_atomic_helper_commit_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	struct drm_framebuffer *old_fb = NULL, *fb = NULL;
+	struct drm_atomic_helper_state *a = cstate->state;
+	int ret = -EINVAL;
+
+	if (cstate->set_config) {
+		cstate->set_config = false;
+		return set_config(crtc, cstate);
+	}
+
+	mutex_lock(&crtc->mutex);
+	if (cstate->fb) {
+		/* pageflip */
+
+		if (crtc->fb == NULL) {
+			/* The framebuffer is currently unbound, presumably
+			 * due to a hotplug event, that userspace has not
+			 * yet discovered.
+			 */
+			ret = -EBUSY;
+			goto out;
+		}
+
+		if (crtc->funcs->page_flip == NULL)
+			goto out;
+
+		old_fb = crtc->fb;
+		fb = cstate->fb;
+
+		ret = crtc->funcs->page_flip(crtc, fb, cstate->event, a->flags);
+		if (ret) {
+			/* Keep the old fb, don't unref it. */
+			old_fb = NULL;
+		} else {
+			cstate->event = NULL;
+			swap_crtc_state(crtc, cstate->state);
+			/* Unref only the old framebuffer. */
+			fb = NULL;
+		}
+	} else {
+		/* disable */
+		struct drm_mode_set set = {
+				.crtc = crtc,
+				.fb = NULL,
+		};
+
+		old_fb = crtc->state->fb;
+		ret = drm_mode_set_config_internal(&set);
+		if (!ret) {
+			swap_crtc_state(crtc, cstate->state);
+		}
+	}
+
+out:
+	mutex_unlock(&crtc->mutex);
+
+	if (fb)
+		drm_framebuffer_unreference(fb);
+	if (old_fb)
+		drm_framebuffer_unreference(old_fb);
+
+	return ret;
+}
+
 const struct drm_atomic_helper_funcs drm_atomic_helper_funcs = {
 		.get_plane_state    = drm_atomic_helper_get_plane_state,
 		.check_plane_state  = drm_plane_check_state,
 		.commit_plane_state = drm_atomic_helper_commit_plane_state,
+
+		.get_crtc_state     = drm_atomic_helper_get_crtc_state,
+		.check_crtc_state   = drm_crtc_check_state,
+		.commit_crtc_state  = drm_atomic_helper_commit_crtc_state,
 };
 EXPORT_SYMBOL(drm_atomic_helper_funcs);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 3cf235e..1091d82 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -574,8 +574,6 @@  void drm_framebuffer_remove(struct drm_framebuffer *fb)
 	struct drm_device *dev = fb->dev;
 	struct drm_crtc *crtc;
 	struct drm_plane *plane;
-	struct drm_mode_set set;
-	int ret;
 
 	WARN_ON(!list_empty(&fb->filp_head));
 
@@ -595,6 +593,7 @@  void drm_framebuffer_remove(struct drm_framebuffer *fb)
 	 * in this manner.
 	 */
 	if (atomic_read(&fb->refcount.refcount) > 1) {
+		struct drm_mode_config *config = &fb->dev->mode_config;
 		void *state;
 
 		state = dev->driver->atomic_begin(dev, 0);
@@ -603,22 +602,12 @@  void drm_framebuffer_remove(struct drm_framebuffer *fb)
 			return;
 		}
 
-		/* TODO once CRTC is converted to state/properties, we can push the
-		 * locking down into drm_atomic_helper_commit(), since that is where
-		 * the actual changes take place..
-		 */
-		drm_modeset_lock_all(dev);
-
 		/* remove from any CRTC */
 		list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 			if (crtc->fb == fb) {
 				/* should turn off the crtc */
-				memset(&set, 0, sizeof(struct drm_mode_set));
-				set.crtc = crtc;
-				set.fb = NULL;
-				ret = drm_mode_set_config_internal(&set);
-				if (ret)
-					DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
+				drm_mode_crtc_set_obj_prop(crtc, state,
+					config->prop_fb_id, 0, NULL);
 			}
 		}
 
@@ -634,8 +623,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);
@@ -656,11 +643,15 @@  EXPORT_SYMBOL(drm_framebuffer_remove);
 int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
 		   const struct drm_crtc_funcs *funcs)
 {
+	struct drm_mode_config *config = &dev->mode_config;
 	int ret;
 
+	if (!crtc->state)
+		crtc->state = kzalloc(sizeof(crtc->state), GFP_KERNEL);
+
 	crtc->dev = dev;
 	crtc->funcs = funcs;
-	crtc->invert_dimensions = false;
+	crtc->state->invert_dimensions = false;
 
 	drm_modeset_lock_all(dev);
 	mutex_init(&crtc->mutex);
@@ -671,11 +662,17 @@  int drm_crtc_init(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++;
 
+	drm_object_attach_property(&crtc->base, config->prop_mode, 0);
+	drm_object_attach_property(&crtc->base, config->prop_connector_ids, 0);
+	drm_object_attach_property(&crtc->base, config->prop_fb_id, 0);
+	drm_object_attach_property(&crtc->base, config->prop_src_x, 0);
+	drm_object_attach_property(&crtc->base, config->prop_src_y, 0);
+
  out:
 	drm_modeset_unlock_all(dev);
 
@@ -704,6 +701,112 @@  void drm_crtc_cleanup(struct drm_crtc *crtc)
 }
 EXPORT_SYMBOL(drm_crtc_cleanup);
 
+int drm_crtc_check_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	int hdisplay, vdisplay;
+
+	/* disabling the crtc is allowed: */
+	if (!(fb && state->mode_valid))
+		return 0;
+
+	hdisplay = state->mode.hdisplay;
+	vdisplay = state->mode.vdisplay;
+
+	if (state->invert_dimensions)
+		swap(hdisplay, vdisplay);
+
+	/* For some reason crtc x/y offsets are signed internally. */
+	if (state->x > INT_MAX || state->y > INT_MAX)
+		return -ERANGE;
+
+	if (hdisplay > fb->width ||
+	    vdisplay > fb->height ||
+	    state->x > fb->width - hdisplay ||
+	    state->y > fb->height - vdisplay) {
+		DRM_DEBUG_KMS("Invalid fb size %ux%u for CRTC viewport %ux%u+%d+%d%s.\n",
+			      fb->width, fb->height, hdisplay, vdisplay,
+			      state->x, state->y,
+			      state->invert_dimensions ? " (inverted)" : "");
+		return -ENOSPC;
+	}
+
+	if (crtc->enabled && !state->set_config) {
+		if (crtc->state->fb->pixel_format != fb->pixel_format) {
+			DRM_DEBUG_KMS("Page flip is not allowed to "
+					"change frame buffer format.\n");
+			return -EINVAL;
+		}
+	}
+
+	if (state->num_connector_ids == 0) {
+		DRM_DEBUG_KMS("Count connectors is 0 but mode set\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_check_state);
+
+void drm_crtc_commit_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state)
+{
+	crtc->state = state;
+	crtc->base.propvals = &state->propvals;
+}
+EXPORT_SYMBOL(drm_crtc_commit_state);
+
+int drm_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_crtc_state *state,
+		struct drm_property *property,
+		uint64_t value, void *blob_data)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_mode_config *config = &dev->mode_config;
+
+	drm_object_property_set_value(&crtc->base,
+			&state->propvals, property, value, blob_data);
+
+	if (property == config->prop_mode) {
+		if (!blob_data) {
+			memset(&state->mode, 0, sizeof(state->mode));
+			state->mode_valid = false;
+		} else {
+			/* check size: */
+			if (value < sizeof(struct drm_mode_modeinfo))
+				return -EINVAL;
+			state->mode = *(struct drm_mode_modeinfo *)blob_data;
+			state->mode_valid = true;
+		}
+		state->set_config = true;
+	} else if (property == config->prop_connector_ids) {
+		state->num_connector_ids = value / sizeof(state->connector_ids[0]);
+		state->connector_ids = blob_data;
+		state->set_config = true;
+	} else if (property == config->prop_fb_id) {
+		state->new_fb = true;
+		state->fb = drm_framebuffer_lookup(dev, value);
+	} else if (property == config->prop_src_x) {
+		int x = *(int *)&value;
+		if (state->x != x) {
+			state->x = x;
+			state->set_config = true;
+		}
+	} else if (property == config->prop_src_y) {
+		int y = *(int *)&value;
+		if (state->y != y) {
+			state->y = y;
+			state->set_config = true;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_set_property);
+
 /**
  * drm_mode_probed_add - add a mode to a connector's probed mode list
  * @connector: connector the new mode
@@ -1265,6 +1368,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;
 }
 
@@ -1529,7 +1642,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)
@@ -1775,11 +1888,11 @@  int drm_mode_getcrtc(struct drm_device *dev,
 	}
 	crtc = obj_to_crtc(obj);
 
-	crtc_resp->x = crtc->x;
-	crtc_resp->y = crtc->y;
+	crtc_resp->x = crtc->state->x;
+	crtc_resp->y = crtc->state->y;
 	crtc_resp->gamma_size = crtc->gamma_size;
-	if (crtc->fb)
-		crtc_resp->fb_id = crtc->fb->base.id;
+	if (crtc->state->fb)
+		crtc_resp->fb_id = crtc->state->fb->base.id;
 	else
 		crtc_resp->fb_id = 0;
 
@@ -2209,22 +2322,15 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 	struct drm_mode_crtc *crtc_req = data;
 	struct drm_mode_object *obj;
 	struct drm_crtc *crtc;
-	struct drm_connector **connector_set = NULL, *connector;
-	struct drm_framebuffer *fb = NULL;
-	struct drm_display_mode *mode = NULL;
-	struct drm_mode_set set;
-	uint32_t __user *set_connectors_ptr;
+	uint32_t fb_id = -1;
+	uint32_t *connector_ids = NULL;
+	void *state = NULL;
 	int ret;
 	int i;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return -EINVAL;
 
-	/* For some reason crtc x/y offsets are signed internally. */
-	if (crtc_req->x > INT_MAX || crtc_req->y > INT_MAX)
-		return -ERANGE;
-
-	drm_modeset_lock_all(dev);
 	obj = drm_mode_object_find(dev, crtc_req->crtc_id,
 				   DRM_MODE_OBJECT_CRTC);
 	if (!obj) {
@@ -2236,7 +2342,6 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 	DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
 
 	if (crtc_req->mode_valid) {
-		int hdisplay, vdisplay;
 		/* If we have a mode we need a framebuffer. */
 		/* If we pass -1, set the mode with the currently bound fb */
 		if (crtc_req->fb_id == -1) {
@@ -2245,67 +2350,15 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 				ret = -EINVAL;
 				goto out;
 			}
-			fb = crtc->fb;
-			/* Make refcounting symmetric with the lookup path. */
-			drm_framebuffer_reference(fb);
+			fb_id = crtc->base.id;
 		} else {
-			fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
-			if (!fb) {
-				DRM_DEBUG_KMS("Unknown FB ID%d\n",
-						crtc_req->fb_id);
-				ret = -EINVAL;
-				goto out;
-			}
+			fb_id = crtc_req->fb_id;
 		}
-
-		mode = drm_mode_create(dev);
-		if (!mode) {
-			ret = -ENOMEM;
-			goto out;
-		}
-
-		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);
-
-		hdisplay = mode->hdisplay;
-		vdisplay = mode->vdisplay;
-
-		if (crtc->invert_dimensions)
-			swap(hdisplay, vdisplay);
-
-		if (hdisplay > fb->width ||
-		    vdisplay > fb->height ||
-		    crtc_req->x > fb->width - hdisplay ||
-		    crtc_req->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, crtc_req->x, crtc_req->y,
-				      crtc->invert_dimensions ? " (inverted)" : "");
-			ret = -ENOSPC;
-			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) {
@@ -2313,54 +2366,52 @@  int drm_mode_setcrtc(struct drm_device *dev, void *data,
 			goto out;
 		}
 
-		connector_set = kmalloc(crtc_req->count_connectors *
-					sizeof(struct drm_connector *),
+		connector_ids = kmalloc(crtc_req->count_connectors *
+					sizeof(connector_ids[0]),
 					GFP_KERNEL);
-		if (!connector_set) {
+		if (!connector_ids) {
 			ret = -ENOMEM;
 			goto out;
 		}
 
 		for (i = 0; i < crtc_req->count_connectors; i++) {
-			set_connectors_ptr = (uint32_t __user *)(unsigned long)crtc_req->set_connectors_ptr;
+			u32 out_id;
+
 			if (get_user(out_id, &set_connectors_ptr[i])) {
 				ret = -EFAULT;
 				goto out;
 			}
-
-			obj = drm_mode_object_find(dev, out_id,
-						   DRM_MODE_OBJECT_CONNECTOR);
-			if (!obj) {
-				DRM_DEBUG_KMS("Connector id %d unknown\n",
-						out_id);
-				ret = -EINVAL;
-				goto out;
-			}
-			connector = obj_to_connector(obj);
-			DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
-					connector->base.id,
-					drm_get_connector_name(connector));
-
-			connector_set[i] = connector;
+			connector_ids[i] = out_id;
 		}
 	}
 
-	set.crtc = crtc;
-	set.x = crtc_req->x;
-	set.y = crtc_req->y;
-	set.mode = mode;
-	set.connectors = connector_set;
-	set.num_connectors = crtc_req->count_connectors;
-	set.fb = fb;
-	ret = drm_mode_set_config_internal(&set);
+	state = dev->driver->atomic_begin(dev, 0);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
 
-out:
-	if (fb)
-		drm_framebuffer_unreference(fb);
+	ret =
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_mode, sizeof(crtc_req->mode), &crtc_req->mode) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_connector_ids,
+			crtc_req->count_connectors * sizeof(connector_ids[0]),
+			connector_ids) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_fb_id, fb_id, NULL) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_src_x, crtc_req->x, NULL) ||
+		drm_mode_set_obj_prop(obj, state,
+			config->prop_src_y, crtc_req->y, NULL) ||
+		dev->driver->atomic_check(dev, state);
+	if (ret)
+		goto out;
 
-	kfree(connector_set);
-	drm_mode_destroy(dev, mode);
-	drm_modeset_unlock_all(dev);
+	ret = dev->driver->atomic_commit(dev, state);
+
+out:
+	if (state)
+		dev->driver->atomic_end(dev, state);
+	kfree(connector_ids);
 	return ret;
 }
 
@@ -3467,9 +3518,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;
 }
@@ -3795,16 +3843,60 @@  out:
 	return ret;
 }
 
+static struct drm_pending_vblank_event *create_vblank_event(
+		struct drm_device *dev, struct drm_file *file_priv, uint64_t user_data)
+{
+	struct drm_pending_vblank_event *e = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (file_priv->event_space < sizeof e->event) {
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+		goto out;
+	}
+	file_priv->event_space -= sizeof e->event;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+
+	e = kzalloc(sizeof *e, GFP_KERNEL);
+	if (e == NULL) {
+		spin_lock_irqsave(&dev->event_lock, flags);
+		file_priv->event_space += sizeof e->event;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+		goto out;
+	}
+
+	e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
+	e->event.base.length = sizeof e->event;
+	e->event.user_data = user_data;
+	e->base.event = &e->event.base;
+	e->base.file_priv = file_priv;
+	e->base.destroy =
+		(void (*) (struct drm_pending_event *)) kfree;
+
+out:
+	return e;
+}
+
+static void destroy_vblank_event(struct drm_device *dev,
+		struct drm_file *file_priv, struct drm_pending_vblank_event *e)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	file_priv->event_space += sizeof e->event;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+	kfree(e);
+}
+
 int drm_mode_page_flip_ioctl(struct drm_device *dev,
 			     void *data, struct drm_file *file_priv)
 {
 	struct drm_mode_crtc_page_flip *page_flip = data;
+	struct drm_mode_config *config = &dev->mode_config;
 	struct drm_mode_object *obj;
 	struct drm_crtc *crtc;
-	struct drm_framebuffer *fb = NULL, *old_fb = NULL;
 	struct drm_pending_vblank_event *e = NULL;
-	unsigned long flags;
-	int hdisplay, vdisplay;
+	void *state;
 	int ret = -EINVAL;
 
 	if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS ||
@@ -3819,103 +3911,37 @@  int drm_mode_page_flip_ioctl(struct drm_device *dev,
 		return -EINVAL;
 	crtc = obj_to_crtc(obj);
 
-	mutex_lock(&crtc->mutex);
-	if (crtc->fb == NULL) {
-		/* The framebuffer is currently unbound, presumably
-		 * due to a hotplug event, that userspace has not
-		 * yet discovered.
-		 */
-		ret = -EBUSY;
-		goto out;
-	}
-
-	if (crtc->funcs->page_flip == NULL)
-		goto out;
-
-	fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
-	if (!fb)
-		goto out;
-
-	hdisplay = crtc->mode.hdisplay;
-	vdisplay = crtc->mode.vdisplay;
-
-	if (crtc->invert_dimensions)
-		swap(hdisplay, vdisplay);
-
-	if (hdisplay > fb->width ||
-	    vdisplay > fb->height ||
-	    crtc->x > fb->width - hdisplay ||
-	    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, crtc->x, crtc->y,
-			      crtc->invert_dimensions ? " (inverted)" : "");
-		ret = -ENOSPC;
-		goto out;
-	}
-
-	if (crtc->fb->pixel_format != fb->pixel_format) {
-		DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
-		ret = -EINVAL;
-		goto out;
-	}
+	state = dev->driver->atomic_begin(dev, page_flip->flags);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
 
 	if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
-		ret = -ENOMEM;
-		spin_lock_irqsave(&dev->event_lock, flags);
-		if (file_priv->event_space < sizeof e->event) {
-			spin_unlock_irqrestore(&dev->event_lock, flags);
+		e = create_vblank_event(dev, file_priv, page_flip->user_data);
+		if (!e) {
+			ret = -ENOMEM;
 			goto out;
 		}
-		file_priv->event_space -= sizeof e->event;
-		spin_unlock_irqrestore(&dev->event_lock, flags);
-
-		e = kzalloc(sizeof *e, GFP_KERNEL);
-		if (e == NULL) {
-			spin_lock_irqsave(&dev->event_lock, flags);
-			file_priv->event_space += sizeof e->event;
-			spin_unlock_irqrestore(&dev->event_lock, flags);
+		ret = dev->driver->atomic_set_event(dev, state, obj, e);
+		if (ret) {
 			goto out;
 		}
-
-		e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
-		e->event.base.length = sizeof e->event;
-		e->event.user_data = page_flip->user_data;
-		e->base.event = &e->event.base;
-		e->base.file_priv = file_priv;
-		e->base.destroy =
-			(void (*) (struct drm_pending_event *)) kfree;
 	}
 
-	old_fb = crtc->fb;
-	ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
-	if (ret) {
-		if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
-			spin_lock_irqsave(&dev->event_lock, flags);
-			file_priv->event_space += sizeof e->event;
-			spin_unlock_irqrestore(&dev->event_lock, flags);
-			kfree(e);
-		}
-		/* Keep the old fb, don't unref it. */
-		old_fb = NULL;
-	} else {
-		/*
-		 * Warn if the driver hasn't properly updated the crtc->fb
-		 * field to reflect that the new framebuffer is now used.
-		 * Failing to do so will screw with the reference counting
-		 * on framebuffers.
-		 */
-		WARN_ON(crtc->fb != fb);
-		/* Unref only the old framebuffer. */
-		fb = NULL;
-	}
+	ret = drm_mode_set_obj_prop(obj, state,
+			config->prop_fb_id, page_flip->fb_id, NULL);
+	if (ret)
+		goto out;
 
-out:
-	if (fb)
-		drm_framebuffer_unreference(fb);
-	if (old_fb)
-		drm_framebuffer_unreference(old_fb);
-	mutex_unlock(&crtc->mutex);
+	ret = dev->driver->atomic_check(dev, state);
+	if (ret)
+		goto out;
+
+	ret = dev->driver->atomic_commit(dev, state);
 
+out:
+	if (ret && e)
+		destroy_vblank_event(dev, file_priv, e);
+	dev->driver->atomic_end(dev, state);
 	return ret;
 }
 
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
index 4ae55b8..c1ef671 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c
@@ -14,6 +14,7 @@ 
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 
 #include "exynos_drm_crtc.h"
 #include "exynos_drm_drv.h"
@@ -286,7 +287,9 @@  static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
 		return 0;
 	}
 
-	return -EINVAL;
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
 }
 
 static struct drm_crtc_funcs exynos_crtc_funcs = {
diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c
index 8fbfa06..2b4bbf5 100644
--- a/drivers/gpu/drm/gma500/cdv_intel_display.c
+++ b/drivers/gpu/drm/gma500/cdv_intel_display.c
@@ -1022,6 +1022,7 @@  const struct drm_crtc_funcs cdv_intel_crtc_funcs = {
 	.cursor_move = gma_crtc_cursor_move,
 	.gamma_set = gma_crtc_gamma_set,
 	.set_config = gma_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = gma_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c
index 97f8a03..34d83ff 100644
--- a/drivers/gpu/drm/gma500/psb_intel_display.c
+++ b/drivers/gpu/drm/gma500/psb_intel_display.c
@@ -444,6 +444,7 @@  const struct drm_crtc_funcs psb_intel_crtc_funcs = {
 	.cursor_move = gma_crtc_cursor_move,
 	.gamma_set = gma_crtc_gamma_set,
 	.set_config = gma_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = gma_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index d0eff24..517bf6c 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -9277,6 +9277,7 @@  static const struct drm_crtc_funcs intel_crtc_funcs = {
 	.cursor_move = intel_crtc_cursor_move,
 	.gamma_set = intel_crtc_gamma_set,
 	.set_config = intel_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = intel_crtc_destroy,
 	.page_flip = intel_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
index 503a414..3bda15b 100644
--- a/drivers/gpu/drm/mgag200/mgag200_mode.c
+++ b/drivers/gpu/drm/mgag200/mgag200_mode.c
@@ -1298,6 +1298,7 @@  static const struct drm_crtc_funcs mga_crtc_funcs = {
 	.cursor_move = mga_crtc_cursor_move,
 	.gamma_set = mga_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = mga_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
index 1d6a6ee..1cc8a72 100644
--- a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
+++ b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c
@@ -403,8 +403,9 @@  static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
 static int mdp4_crtc_set_property(struct drm_crtc *crtc, void *state,
 		struct drm_property *property, uint64_t val, void *blob_data)
 {
-	// XXX
-	return -EINVAL;
+	return drm_crtc_set_property(crtc,
+			drm_atomic_get_crtc_state(crtc, state),
+			property, val, blob_data);
 }
 
 #define CURSOR_WIDTH 64
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
index d4fbf11..8fee163 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
@@ -1088,6 +1088,7 @@  static const struct drm_crtc_funcs nv04_crtc_funcs = {
 	.cursor_move = nv04_crtc_cursor_move,
 	.gamma_set = nv_crtc_gamma_set,
 	.set_config = nouveau_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = nouveau_crtc_page_flip,
 	.destroy = nv_crtc_destroy,
 };
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
index f8e66c0..bfcdf8e 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -1327,6 +1327,7 @@  static const struct drm_crtc_funcs nv50_crtc_func = {
 	.cursor_move = nv50_crtc_cursor_move,
 	.gamma_set = nv50_crtc_gamma_set,
 	.set_config = nouveau_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = nv50_crtc_destroy,
 	.page_flip = nouveau_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c
index d53a689..054d75e 100644
--- a/drivers/gpu/drm/omapdrm/omap_crtc.c
+++ b/drivers/gpu/drm/omapdrm/omap_crtc.c
@@ -367,14 +367,19 @@  static int omap_crtc_set_property(struct drm_crtc *crtc, void *state,
 {
 	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
 	struct omap_drm_private *priv = crtc->dev->dev_private;
+	struct drm_crtc_state *cstate = drm_atomic_get_crtc_state(crtc, state);
+	int ret;
 
 	if (property == priv->rotation_prop) {
-		crtc->invert_dimensions =
+		cstate->invert_dimensions =
 				!!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270)));
 	}
 
-	return omap_plane_set_property(omap_crtc->plane, state,
+	ret = omap_plane_set_property(omap_crtc->plane, state,
 			property, val, blob_data);
+	if (ret)
+		ret = drm_crtc_set_property(crtc, cstate, property, val, blob_data);
+	return ret;
 }
 
 static const struct drm_crtc_funcs omap_crtc_funcs = {
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 0e15e2c..8651a02 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -553,7 +553,7 @@  static void dev_lastclose(struct drm_device *dev)
 		 */
 		for (i = 0; i < priv->num_crtcs; i++) {
 			drm_object_property_set_value(&priv->crtcs[i]->base,
-					&priv->crtcs[i]->propvals,
+					&priv->crtcs[i]->state->propvals,
 					priv->rotation_prop, 0, NULL);
 		}
 
diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
index 9f12205..b138bb1 100644
--- a/drivers/gpu/drm/qxl/qxl_display.c
+++ b/drivers/gpu/drm/qxl/qxl_display.c
@@ -29,6 +29,7 @@ 
 #include "qxl_drv.h"
 #include "qxl_object.h"
 #include "drm_crtc_helper.h"
+#include "drm_atomic_helper.h"
 
 static bool qxl_head_enabled(struct qxl_head *head)
 {
@@ -365,6 +366,7 @@  static const struct drm_crtc_funcs qxl_crtc_funcs = {
 	.cursor_set2 = qxl_crtc_cursor_set2,
 	.cursor_move = qxl_crtc_cursor_move,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = qxl_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c
index 0d1aa05..70d1346 100644
--- a/drivers/gpu/drm/radeon/radeon_display.c
+++ b/drivers/gpu/drm/radeon/radeon_display.c
@@ -31,6 +31,7 @@ 
 #include <asm/div64.h>
 
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
 
 static void avivo_crtc_load_lut(struct drm_crtc *crtc)
@@ -499,6 +500,7 @@  static const struct drm_crtc_funcs radeon_crtc_funcs = {
 	.cursor_move = radeon_crtc_cursor_move,
 	.gamma_set = radeon_crtc_gamma_set,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = radeon_crtc_destroy,
 	.page_flip = radeon_crtc_page_flip,
 };
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index a9d24e4..c840ba8 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -17,6 +17,7 @@ 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -528,6 +529,7 @@  static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
 static const struct drm_crtc_funcs crtc_funcs = {
 	.destroy = drm_crtc_cleanup,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = rcar_du_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
index e62b0f2..794ff95 100644
--- a/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
+++ b/drivers/gpu/drm/shmobile/shmob_drm_crtc.c
@@ -17,6 +17,7 @@ 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_cma_helper.h>
 
@@ -496,6 +497,7 @@  static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
 static const struct drm_crtc_funcs crtc_funcs = {
 	.destroy = drm_crtc_cleanup,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = shmob_drm_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
index d36efc1..34d9804 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -411,6 +411,7 @@  static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
 static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
 		.destroy        = tilcdc_crtc_destroy,
 		.set_config     = drm_crtc_helper_set_config,
+		.set_property   = drm_atomic_helper_crtc_set_property,
 		.page_flip      = tilcdc_crtc_page_flip,
 };
 
diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c
index 2ae1eb7..e05b2ea 100644
--- a/drivers/gpu/drm/udl/udl_modeset.c
+++ b/drivers/gpu/drm/udl/udl_modeset.c
@@ -14,6 +14,7 @@ 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
 #include "udl_drv.h"
 
 /*
@@ -383,6 +384,7 @@  static struct drm_crtc_helper_funcs udl_helper_funcs = {
 
 static const struct drm_crtc_funcs udl_crtc_funcs = {
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = udl_crtc_destroy,
 };
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
index 79f7e8e..5d25f21 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ldu.c
@@ -298,6 +298,7 @@  static struct drm_crtc_funcs vmw_legacy_crtc_funcs = {
 	.cursor_move = vmw_du_crtc_cursor_move,
 	.gamma_set = vmw_du_crtc_gamma_set,
 	.destroy = vmw_ldu_crtc_destroy,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.set_config = vmw_ldu_crtc_set_config,
 };
 
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
index 26387c3..f65fec7 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_scrn.c
@@ -394,6 +394,7 @@  static struct drm_crtc_funcs vmw_screen_object_crtc_funcs = {
 	.gamma_set = vmw_du_crtc_gamma_set,
 	.destroy = vmw_sou_crtc_destroy,
 	.set_config = vmw_sou_crtc_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.page_flip = vmw_du_page_flip,
 };
 
diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c
index 5766010..3a7533e 100644
--- a/drivers/gpu/host1x/drm/dc.c
+++ b/drivers/gpu/host1x/drm/dc.c
@@ -259,6 +259,7 @@  static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
 static const struct drm_crtc_funcs tegra_crtc_funcs = {
 	.page_flip = tegra_dc_page_flip,
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = drm_crtc_cleanup,
 };
 
diff --git a/drivers/staging/imx-drm/ipuv3-crtc.c b/drivers/staging/imx-drm/ipuv3-crtc.c
index 6fd37a7..f97862e 100644
--- a/drivers/staging/imx-drm/ipuv3-crtc.c
+++ b/drivers/staging/imx-drm/ipuv3-crtc.c
@@ -155,6 +155,7 @@  static int ipu_page_flip(struct drm_crtc *crtc,
 
 static const struct drm_crtc_funcs ipu_crtc_funcs = {
 	.set_config = drm_crtc_helper_set_config,
+	.set_property = drm_atomic_helper_crtc_set_property,
 	.destroy = drm_crtc_cleanup,
 	.page_flip = ipu_page_flip,
 };
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index d0d29e1..f13f09c 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -65,6 +65,10 @@  struct drm_atomic_helper_funcs {
 	struct drm_plane_state *(*get_plane_state)(struct drm_plane *plane, void *state);
 	int (*check_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
 	int (*commit_plane_state)(struct drm_plane *plane, struct drm_plane_state *pstate);
+
+	struct drm_crtc_state *(*get_crtc_state)(struct drm_crtc *crtc, void *state);
+	int (*check_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
+	int (*commit_crtc_state)(struct drm_crtc *crtc, struct drm_crtc_state *cstate);
 };
 
 const extern struct drm_atomic_helper_funcs drm_atomic_helper_funcs;
@@ -108,6 +112,37 @@  drm_atomic_commit_plane_state(struct drm_plane *plane,
 	return funcs->commit_plane_state(plane, pstate);
 }
 
+int drm_atomic_helper_crtc_set_property(struct drm_crtc *crtc, void *state,
+		struct drm_property *property, uint64_t val, void *blob_data);
+void drm_atomic_helper_init_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate, void *state);
+
+static inline struct drm_crtc_state *
+drm_atomic_get_crtc_state(struct drm_crtc *crtc, void *state)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->get_crtc_state(crtc, state);
+}
+
+static inline int
+drm_atomic_check_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->check_crtc_state(crtc, cstate);
+}
+
+static inline int
+drm_atomic_commit_crtc_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *cstate)
+{
+	const struct drm_atomic_helper_funcs *funcs =
+			crtc->dev->driver->atomic_helpers;
+	return funcs->commit_crtc_state(crtc, cstate);
+}
+
 /**
  * struct drm_atomic_helper_state - the state object used by atomic helpers
  */
@@ -117,6 +152,8 @@  struct drm_atomic_helper_state {
 	uint32_t flags;
 	struct drm_plane **planes;
 	struct drm_plane_state **pstates;
+	struct drm_crtc **crtcs;
+	struct drm_crtc_state **cstates;
 };
 
 static inline void
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index ee46a6a..8227627 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -390,18 +390,51 @@  struct drm_crtc_funcs {
 };
 
 /**
+ * drm_crtc_state - mutable crtc state
+ * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
+ *    invert the width/height of the crtc.  This is used if the driver
+ *    is performing 90 or 270 degree rotated scanout
+ * @set_config: needs modeset (crtc->set_config())
+ * @new_fb: has the fb been changed
+ * @mode_valid: a valid mode has been set
+ * @num_connector_ids: the number of connector-ids
+ * @connector_ids: array of connector ids
+ * @mode: current mode timings
+ * @fb: the framebuffer that the CRTC is currently bound to
+ * @x: x position on screen
+ * @y: y position on screen
+ * @event: pending pageflip event
+ * @propvals: property values
+ * @state: current global/toplevel state object (for atomic) while an
+ *    update is in progress, NULL otherwise.
+ */
+struct drm_crtc_state {
+	bool invert_dimensions : 1;
+	bool set_config        : 1;
+	bool new_fb            : 1;
+	bool mode_valid        : 1;  /* drop this if when mode a refcnt'd ptr */
+	uint8_t num_connector_ids;
+	uint32_t *connector_ids;
+	struct drm_mode_modeinfo mode;
+	struct drm_framebuffer *fb;
+	int x, y;
+
+	struct drm_pending_vblank_event *event;
+
+	struct drm_object_property_values propvals;
+
+	void *state;
+};
+
+/**
  * drm_crtc - central CRTC control structure
  * @dev: parent DRM device
  * @head: list management
+ * @id: CRTC number, 0..n
  * @base: base KMS object for ID tracking etc.
+ * @state: the mutable state
  * @enabled: is this CRTC enabled?
- * @mode: current mode timings
  * @hwmode: mode timings as programmed to hw regs
- * @invert_dimensions: for purposes of error checking crtc vs fb sizes,
- *    invert the width/height of the crtc.  This is used if the driver
- *    is performing 90 or 270 degree rotated scanout
- * @x: x position on screen
- * @y: y position on screen
  * @funcs: CRTC control functions
  * @gamma_size: size of gamma ramp
  * @gamma_store: gamma ramp values
@@ -418,6 +451,8 @@  struct drm_crtc {
 	struct drm_device *dev;
 	struct list_head head;
 
+	int id;
+
 	/**
 	 * crtc mutex
 	 *
@@ -429,8 +464,7 @@  struct drm_crtc {
 
 	struct drm_mode_object base;
 
-	/* framebuffer the connector is currently bound to */
-	struct drm_framebuffer *fb;
+	struct drm_crtc_state *state;
 
 	/* Temporary tracking of the old fb while a modeset is ongoing. Used
 	 * by drm_mode_set_config_internal to implement correct refcounting. */
@@ -438,17 +472,11 @@  struct drm_crtc {
 
 	bool enabled;
 
-	/* Requested mode from modesetting. */
-	struct drm_display_mode mode;
-
 	/* Programmed mode in hw, after adjustments for encoders,
 	 * crtc, panel scaling etc. Needed for timestamping etc.
 	 */
 	struct drm_display_mode hwmode;
 
-	bool invert_dimensions;
-
-	int x, y;
 	const struct drm_crtc_funcs *funcs;
 
 	/* CRTC gamma size for reporting to userspace */
@@ -462,7 +490,15 @@  struct drm_crtc {
 	void *helper_private;
 
 	struct drm_object_properties properties;
-	struct drm_object_property_values propvals;
+
+	/* These are (temporary) duplicate information from what is in the
+	 * drm_crtc_state struct..  keeping duplicate copy here makes the
+	 * switch to atomic far less intrusive.  Once all the drivers and
+	 * the crtc/fb helpers are updated, then we can remove these:
+	 */
+	struct drm_framebuffer *fb;
+	int x, y;
+	struct drm_display_mode mode;
 };
 
 
@@ -941,6 +977,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;
 
@@ -996,6 +1034,14 @@  extern int drm_crtc_init(struct drm_device *dev,
 			 struct drm_crtc *crtc,
 			 const struct drm_crtc_funcs *funcs);
 extern void drm_crtc_cleanup(struct drm_crtc *crtc);
+extern int drm_crtc_check_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state);
+extern void drm_crtc_commit_state(struct drm_crtc *crtc,
+		struct drm_crtc_state *state);
+extern int drm_crtc_set_property(struct drm_crtc *crtc,
+		struct drm_crtc_state *state,
+		struct drm_property *property,
+		uint64_t value, void *blob_data);
 
 extern void drm_connector_ida_init(void);
 extern void drm_connector_ida_destroy(void);
@@ -1045,6 +1091,7 @@  extern const char *drm_get_tv_subconnector_name(int val);
 extern const char *drm_get_tv_select_name(int val);
 extern void drm_fb_release(struct drm_file *file_priv);
 extern int drm_mode_group_init_legacy_group(struct drm_device *dev, struct drm_mode_group *group);
+extern int drm_crtc_convert_umode(struct drm_display_mode *out, const struct drm_mode_modeinfo *in);
 extern bool drm_probe_ddc(struct i2c_adapter *adapter);
 extern struct edid *drm_get_edid(struct drm_connector *connector,
 				 struct i2c_adapter *adapter);