diff mbox

[RFC,11/11] drm/omap: update for atomic age

Message ID 1347508811-15029-2-git-send-email-rob.clark@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Rob Clark Sept. 13, 2012, 4 a.m. UTC
From: Rob Clark <rob@ti.com>

---
 drivers/staging/omapdrm/Makefile      |    1 +
 drivers/staging/omapdrm/omap_atomic.c |  339 +++++++++++++++++++++++++++++++++
 drivers/staging/omapdrm/omap_atomic.h |   52 +++++
 drivers/staging/omapdrm/omap_crtc.c   |  246 +++++++++++-------------
 drivers/staging/omapdrm/omap_drv.c    |   21 +-
 drivers/staging/omapdrm/omap_drv.h    |   45 +++--
 drivers/staging/omapdrm/omap_fb.c     |   44 +++--
 drivers/staging/omapdrm/omap_plane.c  |  296 +++++++++++++++-------------
 8 files changed, 727 insertions(+), 317 deletions(-)
 create mode 100644 drivers/staging/omapdrm/omap_atomic.c
 create mode 100644 drivers/staging/omapdrm/omap_atomic.h
diff mbox

Patch

diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile
index d85e058..7d45e4d 100644
--- a/drivers/staging/omapdrm/Makefile
+++ b/drivers/staging/omapdrm/Makefile
@@ -13,6 +13,7 @@  omapdrm-y := omap_drv.o \
 	omap_connector.o \
 	omap_fb.o \
 	omap_fbdev.o \
+	omap_atomic.o \
 	omap_gem.o \
 	omap_gem_dmabuf.o \
 	omap_dmm_tiler.o \
diff --git a/drivers/staging/omapdrm/omap_atomic.c b/drivers/staging/omapdrm/omap_atomic.c
new file mode 100644
index 0000000..27e5751
--- /dev/null
+++ b/drivers/staging/omapdrm/omap_atomic.c
@@ -0,0 +1,339 @@ 
+/*
+ * drivers/staging/omapdrm/omap_atomic.c
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <rob.clark@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "omap_drv.h"
+#include "omap_atomic.h"
+
+struct omap_atomic_state {
+	struct drm_device *dev;
+
+	/* for page-flips, this is the CRTC involved: */
+	struct drm_crtc *crtc;
+	int pipe;
+
+	int num_dirty_planes, num_dirty_crtcs;
+	struct omap_plane_state *plane_state[8];
+	struct omap_crtc_state  *crtc_state[8];
+
+	int num_pending_fbs;
+	atomic_t num_ready_fbs;
+	struct drm_framebuffer  *pending_fbs[8];
+
+	/* for handling page flips without caring about what
+	 * the callback is called from.  Possibly we should just
+	 * make omap_gem always call the cb from the worker so
+	 * we don't have to care about this..
+	 */
+	struct work_struct commit_work;
+};
+
+static void commit_worker(struct work_struct *work);
+
+static int crtc2pipe(struct drm_device *dev, struct drm_crtc *crtc)
+{
+	struct omap_drm_private *priv = dev->dev_private;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(priv->crtcs); i++)
+		if (priv->crtcs[i] == crtc)
+			return i;
+
+	BUG();  /* bogus CRTC ptr */
+	return -1;
+}
+
+void *omap_atomic_begin(struct drm_device *dev, struct drm_crtc *crtc)
+{
+	struct omap_drm_private *priv = dev->dev_private;
+	struct omap_atomic_state *omap_state;
+	int pipe = 0;
+
+	if (crtc) {
+		pipe = crtc2pipe(dev, crtc);
+		if (priv->event[pipe]) {
+			dev_err(dev->dev, "pending page-flip!\n");
+			return ERR_PTR(-EBUSY);
+		}
+		WARN_ON(priv->crtc_atomic[pipe]);
+		priv->crtc_atomic[pipe] = true;
+	} else {
+		WARN_ON(priv->global_atomic);
+		priv->global_atomic = true;
+	}
+
+	omap_state = kzalloc(sizeof(*omap_state), GFP_KERNEL);
+	if (!omap_state) {
+		dev_err(dev->dev, "failed to allocate state\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	omap_state->dev = dev;
+	omap_state->crtc = crtc;
+	omap_state->pipe = pipe;
+
+	INIT_WORK(&omap_state->commit_work, commit_worker);
+
+	DBG("state=%p, crtc=%p", omap_state, crtc);
+
+	return omap_state;
+}
+
+static void release_state(struct omap_atomic_state *omap_state)
+{
+	int i;
+
+	DBG("state=%p", omap_state);
+
+	for (i = 0; i < omap_state->num_pending_fbs; i++)
+		drm_framebuffer_unreference(omap_state->pending_fbs[i]);
+
+	/*
+	 * omap_plane_commit()/omap_crtc_commit() have taken ownership
+	 * of their respective state objects, so don't need to kfree()
+	 * 'em here
+	 */
+
+	kfree(omap_state);
+}
+
+int omap_atomic_check(struct drm_device *dev, void *state)
+{
+	struct omap_atomic_state *omap_state = state;
+	struct omap_drm_private *priv = dev->dev_private;
+	int i, ret = 0;
+
+	for (i = 0; (i < ARRAY_SIZE(omap_state->plane_state)) && !ret; i++)
+		if (omap_state->plane_state[i])
+			ret = omap_plane_check_state(priv->planes[i],
+					omap_state->plane_state[i]);
+
+	for (i = 0; (i < ARRAY_SIZE(omap_state->crtc_state)) && !ret; i++)
+		if (omap_state->crtc_state[i])
+			ret = omap_crtc_check_state(priv->crtcs[i],
+					omap_state->crtc_state[i]);
+
+	DBG("state=%p, ret=%d", omap_state, ret);
+
+	if (ret)
+		release_state(omap_state);
+
+	return ret;
+}
+
+static void commit_state(struct omap_atomic_state *omap_state)
+{
+	struct drm_device *dev = omap_state->dev;
+	struct omap_drm_private *priv = dev->dev_private;
+	int i;
+
+	DBG("state=%p", omap_state);
+
+	for (i = 0; i < ARRAY_SIZE(omap_state->plane_state); i++) {
+		struct omap_plane_state *plane_state =
+				omap_state->plane_state[i];
+		if (plane_state)
+			omap_plane_commit_state(priv->planes[i], plane_state);
+	}
+
+	if (omap_state->crtc) {
+		int pipe = omap_state->pipe;
+		priv->crtc_atomic[pipe] = false;
+	} else {
+		priv->global_atomic = false;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(omap_state->crtc_state); i++) {
+		struct omap_crtc_state *crtc_state =
+				omap_state->crtc_state[i];
+		if (crtc_state)
+			omap_crtc_commit_state(priv->crtcs[i], crtc_state);
+	}
+
+	release_state(omap_state);
+}
+
+static void commit_worker(struct work_struct *work)
+{
+	struct omap_atomic_state *omap_state =
+			container_of(work, struct omap_atomic_state, commit_work);
+	struct drm_device *dev = omap_state->dev;
+
+	mutex_lock(&dev->mode_config.mutex);
+	DBG("state=%p", omap_state);
+	commit_state(omap_state);
+	mutex_unlock(&dev->mode_config.mutex);
+}
+
+static void commit_cb(void *state)
+{
+	struct omap_atomic_state *omap_state = state;
+	struct omap_drm_private *priv = omap_state->dev->dev_private;
+	int num_ready_fbs = atomic_inc_return(&omap_state->num_ready_fbs);
+
+	if (num_ready_fbs == omap_state->num_pending_fbs)
+		queue_work(priv->wq, &omap_state->commit_work);
+}
+
+static void commit_async(struct drm_device *dev, void *state,
+		struct drm_pending_vblank_event *event)
+{
+	struct omap_atomic_state *omap_state = state;
+	struct omap_drm_private *priv = omap_state->dev->dev_private;
+	int i;
+
+	if (event) {
+		int pipe = omap_state->pipe;
+		WARN_ON(priv->event[pipe]);
+		priv->event[pipe] = event;
+	}
+
+	if (!omap_state->num_pending_fbs) {
+		commit_state(omap_state);
+		return;
+	}
+
+	for (i = 0; i < omap_state->num_pending_fbs; i++) {
+		struct drm_gem_object *bo;
+		bo = omap_framebuffer_bo(omap_state->pending_fbs[i], 0);
+		omap_gem_op_async(bo, OMAP_GEM_READ, commit_cb, omap_state);
+	}
+}
+
+int omap_atomic_commit(struct drm_device *dev, void *state,
+		struct drm_pending_vblank_event *event)
+{
+	struct omap_atomic_state *omap_state = state;
+
+	DBG("state=%p, event=%p", omap_state, event);
+
+	if (omap_state->crtc) {
+		/* async page-flip */
+		commit_async(dev, state, event);
+	} else {
+		/* sync mode-set, etc */
+		WARN_ON(event);  /* this should not happen */
+		commit_state(omap_state);
+	}
+
+	return 0;
+}
+
+void omap_atomic_end(struct drm_device *dev, void *state)
+{
+	/*
+	 * State is freed either if atomic_check() fails or
+	 * when async pageflip completes, so we don't need
+	 * to do anything here.
+	 */
+}
+
+struct omap_plane_state *omap_atomic_plane_state(void *state, int id)
+{
+	struct omap_atomic_state *omap_state = state;
+	struct omap_drm_private *priv = omap_state->dev->dev_private;
+	struct omap_plane_state *plane_state = omap_state->plane_state[id];
+	int i;
+
+	if (!plane_state) {
+		struct drm_plane *plane = priv->planes[id];
+
+		plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL);
+
+		/* snapshot current state: */
+		*plane_state = *to_omap_plane_state(plane->state);
+
+		omap_state->plane_state[id] = plane_state;
+	}
+
+	/* updating a plane implicitly dirties the crtc: */
+	for (i = 0; i < priv->num_crtcs; i++) {
+		if (priv->crtcs[i] == plane_state->base.crtc) {
+			omap_atomic_crtc_state(state, i);
+			break;
+		}
+	}
+
+	return plane_state;
+}
+
+struct omap_crtc_state *omap_atomic_crtc_state(void *state, int id)
+{
+	struct omap_atomic_state *omap_state = state;
+	struct omap_drm_private *priv = omap_state->dev->dev_private;
+	struct omap_crtc_state *crtc_state = omap_state->crtc_state[id];
+
+	if (!crtc_state) {
+		struct drm_crtc *crtc = priv->crtcs[id];
+
+		crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL);
+
+		/* snapshot current state: */
+		*crtc_state = *to_omap_crtc_state(crtc->state);
+
+		omap_state->crtc_state[id] = crtc_state;
+	}
+
+	return crtc_state;
+}
+
+/* when fb is changed, that gets recorded in the state, so that pageflips
+ * can defer until all fb's are ready
+ */
+void omap_atomic_add_fb(void *state, struct drm_framebuffer *fb)
+{
+	struct omap_atomic_state *omap_state = state;
+	drm_framebuffer_reference(fb);
+	omap_state->pending_fbs[omap_state->num_pending_fbs++] = fb;
+}
+
+/* possibly this could be in drm core? */
+static void send_page_flip_event(struct drm_device *dev, int crtc,
+		struct drm_pending_vblank_event *event)
+{
+	unsigned long flags;
+	struct timeval now;
+
+	DBG("%p", event);
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	event->event.sequence = drm_vblank_count_and_time(dev, crtc, &now);
+	event->event.tv_sec = now.tv_sec;
+	event->event.tv_usec = now.tv_usec;
+	list_add_tail(&event->base.link,
+			&event->base.file_priv->event_list);
+	wake_up_interruptible(&event->base.file_priv->event_wait);
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+/* called when plane is updated.. so we can keep track of when to send
+ * page-flip events
+ */
+void omap_atomic_plane_update(struct drm_device *dev, int id)
+{
+	struct omap_drm_private *priv = dev->dev_private;
+	int pipe = crtc2pipe(dev, priv->planes[id]->state->crtc);
+
+	DBG("id=%d", id);
+
+	if (priv->event[pipe]) {
+		/* wakeup userspace */
+		send_page_flip_event(dev, pipe, priv->event[pipe]);
+		priv->event[pipe] = NULL;
+	}
+}
diff --git a/drivers/staging/omapdrm/omap_atomic.h b/drivers/staging/omapdrm/omap_atomic.h
new file mode 100644
index 0000000..6ba596a
--- /dev/null
+++ b/drivers/staging/omapdrm/omap_atomic.h
@@ -0,0 +1,52 @@ 
+/*
+ * drivers/staging/omapdrm/omap_atomic.h
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <rob.clark@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __OMAP_ATOMIC_H__
+#define __OMAP_ATOMIC_H__
+
+#include "drm_mode.h"
+#include "drm_crtc.h"
+
+struct omap_plane_state {
+	struct drm_plane_state base;
+	uint8_t rotation;
+	uint8_t zorder;
+	uint8_t enabled;
+};
+#define to_omap_plane_state(x) container_of(x, struct omap_plane_state, base)
+
+struct omap_crtc_state {
+	struct drm_crtc_state base;
+};
+#define to_omap_crtc_state(x) container_of(x, struct omap_crtc_state, base)
+
+void *omap_atomic_begin(struct drm_device *dev, struct drm_crtc *crtc);
+int omap_atomic_check(struct drm_device *dev, void *state);
+int omap_atomic_commit(struct drm_device *dev, void *state,
+		struct drm_pending_vblank_event *event);
+void omap_atomic_end(struct drm_device *dev, void *state);
+
+struct omap_plane_state *omap_atomic_plane_state(void *state, int id);
+struct omap_crtc_state *omap_atomic_crtc_state(void *state, int id);
+
+void omap_atomic_add_fb(void *state, struct drm_framebuffer *fb);
+
+void omap_atomic_plane_update(struct drm_device *dev, int id);
+
+#endif /* __OMAP_ATOMIC_H__ */
diff --git a/drivers/staging/omapdrm/omap_crtc.c b/drivers/staging/omapdrm/omap_crtc.c
index b0d22c3..805c75c 100644
--- a/drivers/staging/omapdrm/omap_crtc.c
+++ b/drivers/staging/omapdrm/omap_crtc.c
@@ -32,7 +32,6 @@  struct omap_crtc {
 	const char *name;
 	int pipe;
 	enum omap_channel channel;
-	struct omap_overlay_manager_info info;
 
 	struct omap_video_timings timings;
 	bool enabled, timings_valid;
@@ -48,25 +47,17 @@  struct omap_crtc {
 
 	/* for handling queued and in-progress applies: */
 	struct work_struct apply_work;
-
-	/* if there is a pending flip, these will be non-null: */
-	struct drm_pending_vblank_event *event;
-
-	/* for handling page flips without caring about what
-	 * the callback is called from.  Possibly we should just
-	 * make omap_gem always call the cb from the worker so
-	 * we don't have to care about this..
-	 *
-	 * XXX maybe fold into apply_work??
-	 */
-	struct work_struct page_flip_work;
 };
 
+static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+		struct drm_framebuffer *old_fb);
+
 static void omap_crtc_destroy(struct drm_crtc *crtc)
 {
 	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
 	omap_crtc->plane->funcs->destroy(omap_crtc->plane);
 	drm_crtc_cleanup(crtc);
+	kfree(crtc->state);
 	kfree(omap_crtc);
 }
 
@@ -89,7 +80,7 @@  static void omap_crtc_dpms(struct drm_crtc *crtc, int mode)
 		/* and any attached overlay planes: */
 		for (i = 0; i < priv->num_planes; i++) {
 			struct drm_plane *plane = priv->planes[i];
-			if (plane->crtc == crtc)
+			if (plane->state->crtc == crtc)
 				WARN_ON(omap_plane_dpms(plane, mode));
 		}
 	}
@@ -127,11 +118,7 @@  static int omap_crtc_mode_set(struct drm_crtc *crtc,
 		}
 	}
 
-	return omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb,
-			0, 0, mode->hdisplay, mode->vdisplay,
-			x << 16, y << 16,
-			mode->hdisplay << 16, mode->vdisplay << 16,
-			NULL, NULL);
+	return omap_crtc_mode_set_base(crtc, x, y, old_fb);
 }
 
 static void omap_crtc_prepare(struct drm_crtc *crtc)
@@ -153,134 +140,83 @@  static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
 {
 	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
 	struct drm_plane *plane = omap_crtc->plane;
-	struct drm_display_mode *mode = &crtc->mode;
-
-	return omap_plane_mode_set(plane, crtc, crtc->fb,
-			0, 0, mode->hdisplay, mode->vdisplay,
-			x << 16, y << 16,
-			mode->hdisplay << 16, mode->vdisplay << 16,
-			NULL, NULL);
-}
-
-static void omap_crtc_load_lut(struct drm_crtc *crtc)
-{
-}
-
-/* possibly this could be in drm core? */
-static void send_page_flip_event(struct drm_device *dev, int crtc,
-		struct drm_pending_vblank_event *event)
-{
-	unsigned long flags;
-	struct timeval now;
-
-	DBG("%p", event);
-
-	spin_lock_irqsave(&dev->event_lock, flags);
-	event->event.sequence = drm_vblank_count_and_time(dev, crtc, &now);
-	event->event.tv_sec = now.tv_sec;
-	event->event.tv_usec = now.tv_usec;
-	list_add_tail(&event->base.link,
-			&event->base.file_priv->event_list);
-	wake_up_interruptible(&event->base.file_priv->event_wait);
-	spin_unlock_irqrestore(&dev->event_lock, flags);
-}
-
-static void vblank_cb(void *arg)
-{
-	struct drm_crtc *crtc = arg;
-	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
-	struct drm_pending_vblank_event *event = omap_crtc->event;
-
-	WARN_ON(!event);
-
-	omap_crtc->event = NULL;
-
-	/* wakeup userspace */
-	if (event)
-		send_page_flip_event(crtc->dev, omap_crtc->pipe, event);
-}
-
-static void page_flip_worker(struct work_struct *work)
-{
-	struct omap_crtc *omap_crtc =
-			container_of(work, struct omap_crtc, page_flip_work);
-	struct drm_crtc *crtc = &omap_crtc->base;
 	struct drm_device *dev = crtc->dev;
+	struct drm_mode_config *config = &dev->mode_config;
 	struct drm_display_mode *mode = &crtc->mode;
-	struct drm_gem_object *bo;
-
-	mutex_lock(&dev->mode_config.mutex);
-	omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb,
-			0, 0, mode->hdisplay, mode->vdisplay,
-			crtc->x << 16, crtc->y << 16,
-			mode->hdisplay << 16, mode->vdisplay << 16,
-			vblank_cb, crtc);
-	mutex_unlock(&dev->mode_config.mutex);
-
-	bo = omap_framebuffer_bo(crtc->fb, 0);
-	drm_gem_object_unreference_unlocked(bo);
+	void *state;
+	int ret;
+
+	/* for now, until property based atomic mode-set: */
+	state = omap_atomic_begin(dev, NULL);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
+
+	ret =
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_crtc_id, crtc->base.id) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_fb_id, crtc->state->fb->base.id) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_crtc_x, 0) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_crtc_y, 0) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_crtc_w, mode->hdisplay) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_crtc_h, mode->vdisplay) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_src_w, mode->hdisplay << 16) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_src_h, mode->vdisplay << 16) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_src_x, x << 16) ||
+		drm_mode_plane_set_obj_prop(plane, state,
+				config->prop_src_y, y << 16) ||
+		dev->driver->atomic_check(dev, state);
+
+	if (!ret)
+		ret = omap_atomic_commit(dev, state, NULL);
+
+	omap_atomic_end(dev, state);
+
+	return ret;
 }
 
-static void page_flip_cb(void *arg)
-{
-	struct drm_crtc *crtc = arg;
-	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
-	struct omap_drm_private *priv = crtc->dev->dev_private;
-
-	/* avoid assumptions about what ctxt we are called from: */
-	queue_work(priv->wq, &omap_crtc->page_flip_work);
-}
-
-static int omap_crtc_page_flip_locked(struct drm_crtc *crtc,
-		 struct drm_framebuffer *fb,
-		 struct drm_pending_vblank_event *event)
+static void omap_crtc_load_lut(struct drm_crtc *crtc)
 {
-	struct drm_device *dev = crtc->dev;
-	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
-	struct drm_gem_object *bo;
-
-	DBG("%d -> %d (event=%p)", crtc->fb ? crtc->fb->base.id : -1,
-			fb->base.id, event);
-
-	if (omap_crtc->event) {
-		dev_err(dev->dev, "already a pending flip\n");
-		return -EINVAL;
-	}
-
-	omap_crtc->event = event;
-	crtc->fb = fb;
-
-	/*
-	 * Hold a reference temporarily until the crtc is updated
-	 * and takes the reference to the bo.  This avoids it
-	 * getting freed from under us:
-	 */
-	bo = omap_framebuffer_bo(fb, 0);
-	drm_gem_object_reference(bo);
-
-	omap_gem_op_async(bo, OMAP_GEM_READ, page_flip_cb, crtc);
-
-	return 0;
 }
 
-static int omap_crtc_set_property(struct drm_crtc *crtc,
+static int omap_crtc_set_property(struct drm_crtc *crtc, void *state,
 		struct drm_property *property, uint64_t val)
 {
 	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
 	struct omap_drm_private *priv = crtc->dev->dev_private;
+	struct omap_crtc_state *crtc_state =
+			omap_atomic_crtc_state(state, omap_crtc->pipe);
+	int ret;
+
+	DBG("%s: %s = %llx", omap_crtc->name, property->name, val);
+
+	ret = drm_crtc_set_property(crtc, &crtc_state->base, property, val);
+	if (!ret) {
+		/* we need to set fb property on our private plane too:
+		 */
+		struct drm_mode_config *config = &crtc->dev->mode_config;
+		if (property != config->prop_fb_id)
+			return ret;
+	}
 
 	if (property == priv->rotation_prop) {
-		crtc->invert_dimensions =
+		crtc_state->base.invert_dimensions =
 				!!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270)));
 	}
 
-	return omap_plane_set_property(omap_crtc->plane, property, val);
+	return omap_plane_set_property(omap_crtc->plane, state, property, val);
 }
 
 static const struct drm_crtc_funcs omap_crtc_funcs = {
 	.set_config = drm_crtc_helper_set_config,
 	.destroy = omap_crtc_destroy,
-	.page_flip = omap_crtc_page_flip_locked,
 	.set_property = omap_crtc_set_property,
 };
 
@@ -306,6 +242,23 @@  enum omap_channel omap_crtc_channel(struct drm_crtc *crtc)
 	return omap_crtc->channel;
 }
 
+int omap_crtc_check_state(struct drm_crtc *crtc,
+		struct omap_crtc_state *crtc_state)
+{
+	return drm_crtc_check_state(crtc, &crtc_state->base);
+}
+
+void omap_crtc_commit_state(struct drm_crtc *crtc,
+		struct omap_crtc_state *crtc_state)
+{
+
+	struct omap_crtc *omap_crtc = to_omap_crtc(crtc);
+	struct omap_crtc_state *old_state = to_omap_crtc_state(crtc->state);
+	drm_crtc_commit_state(crtc, &crtc_state->base);
+	kfree(old_state);
+	omap_crtc_apply(crtc, &omap_crtc->apply);
+}
+
 static void omap_crtc_irq(struct omap_drm_irq *irq, uint32_t irqstatus)
 {
 	struct omap_crtc *omap_crtc =
@@ -345,6 +298,9 @@  static void apply_worker(struct work_struct *work)
 		list_del(&apply->pending_node);
 	}
 
+	if (pipe_in_atomic(dev, omap_crtc->pipe))
+		goto out;
+
 	need_apply = !list_empty(&omap_crtc->queued_applies);
 
 	/* then handle the next round of of queued apply's: */
@@ -368,6 +324,8 @@  static void apply_worker(struct work_struct *work)
 			omap_crtc_irq(&omap_crtc->irq, 0);
 		}
 	}
+
+out:
 	dispc_runtime_put();
 	mutex_unlock(&dev->mode_config.mutex);
 }
@@ -381,11 +339,14 @@  int omap_crtc_apply(struct drm_crtc *crtc,
 	WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
 
 	/* no need to queue it again if it is already queued: */
-	if (apply->queued)
-		return 0;
+	if (! apply->queued) {
+		apply->queued = true;
+		list_add_tail(&apply->queued_node,
+				&omap_crtc->queued_applies);
+	}
 
-	apply->queued = true;
-	list_add_tail(&apply->queued_node, &omap_crtc->queued_applies);
+	if (pipe_in_atomic(dev, omap_crtc->pipe))
+		return 0;
 
 	/*
 	 * If there are no currently pending updates, then go ahead and
@@ -404,6 +365,7 @@  static void omap_crtc_pre_apply(struct omap_drm_apply *apply)
 {
 	struct omap_crtc *omap_crtc =
 			container_of(apply, struct omap_crtc, apply);
+	struct omap_overlay_manager_info info = {0};
 
 	DBG("%s: enabled=%d, timings_valid=%d", omap_crtc->name,
 			omap_crtc->enabled,
@@ -414,7 +376,12 @@  static void omap_crtc_pre_apply(struct omap_drm_apply *apply)
 		return;
 	}
 
-	dispc_mgr_setup(omap_crtc->channel, &omap_crtc->info);
+	info.default_color = 0x00000000;
+	info.trans_key = 0x00000000;
+	info.trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST;
+	info.trans_enabled = false;
+
+	dispc_mgr_setup(omap_crtc->channel, &info);
 	dispc_mgr_set_timings(omap_crtc->channel,
 			&omap_crtc->timings);
 	dispc_mgr_enable(omap_crtc->channel, true);
@@ -437,7 +404,6 @@  struct drm_crtc *omap_crtc_init(struct drm_device *dev,
 {
 	struct drm_crtc *crtc = NULL;
 	struct omap_crtc *omap_crtc;
-	struct omap_overlay_manager_info *info;
 
 	DBG("%s", channel_names[channel]);
 
@@ -450,7 +416,13 @@  struct drm_crtc *omap_crtc_init(struct drm_device *dev,
 
 	crtc = &omap_crtc->base;
 
-	INIT_WORK(&omap_crtc->page_flip_work, page_flip_worker);
+	crtc->state = kzalloc(sizeof(struct omap_crtc_state), GFP_KERNEL);
+
+	if (!crtc->state) {
+		dev_err(dev->dev, "could not allocate CRTC state\n");
+		goto fail;
+	}
+
 	INIT_WORK(&omap_crtc->apply_work, apply_worker);
 
 	INIT_LIST_HEAD(&omap_crtc->pending_applies);
@@ -464,16 +436,10 @@  struct drm_crtc *omap_crtc_init(struct drm_device *dev,
 
 	omap_crtc->channel = channel;
 	omap_crtc->plane = plane;
-	omap_crtc->plane->crtc = crtc;
+	omap_crtc->plane->state->crtc = crtc;
 	omap_crtc->name = channel_names[channel];
 	omap_crtc->pipe = id;
 
-	/* TODO: fix hard-coded setup.. add properties! */
-	info->default_color = 0x00000000;
-	info->trans_key = 0x00000000;
-	info->trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST;
-	info->trans_enabled = false;
-
 	drm_crtc_init(dev, crtc, &omap_crtc_funcs);
 	drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs);
 
diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c
index 04577bc..fe5fd15 100644
--- a/drivers/staging/omapdrm/omap_drv.c
+++ b/drivers/staging/omapdrm/omap_drv.c
@@ -18,6 +18,7 @@ 
  */
 
 #include "omap_drv.h"
+#include "omap_atomic.h"
 
 #include "drm_crtc_helper.h"
 #include "drm_fb_helper.h"
@@ -441,7 +442,9 @@  static void dev_lastclose(struct drm_device *dev)
 	 * default state on lastclose?
 	 */
 	for (i = 0; i < priv->num_planes; i++) {
-		drm_object_property_set_value(&priv->planes[i]->base,
+		struct drm_plane *plane = priv->planes[i];
+		drm_object_property_set_value(&plane->base,
+				&plane->state->propvals,
 				priv->rotation_prop, 0);
 	}
 
@@ -454,7 +457,19 @@  static void dev_lastclose(struct drm_device *dev)
 
 static void dev_preclose(struct drm_device *dev, struct drm_file *file)
 {
+	struct omap_drm_private *priv = dev->dev_private;
+	int i;
+
 	DBG("preclose: dev=%p", dev);
+
+	/*
+	 * Clear out pending events before they get destroyed in
+	 * drm_events_release(), so that if we later get a vblank
+	 * we don't deref a bogus ptr
+	 */
+	for (i = 0; i < ARRAY_SIZE(priv->event); i++)
+		if (priv->event[i] && priv->event[i]->base.file_priv == file)
+			priv->event[i] = NULL;
 }
 
 static void dev_postclose(struct drm_device *dev, struct drm_file *file)
@@ -511,6 +526,10 @@  static struct drm_driver omap_drm_driver = {
 		.dumb_create = omap_gem_dumb_create,
 		.dumb_map_offset = omap_gem_dumb_map_offset,
 		.dumb_destroy = omap_gem_dumb_destroy,
+		.atomic_begin = omap_atomic_begin,
+		.atomic_check = omap_atomic_check,
+		.atomic_commit = omap_atomic_commit,
+		.atomic_end = omap_atomic_end,
 		.ioctls = ioctls,
 		.num_ioctls = DRM_OMAP_NUM_IOCTLS,
 		.fops = &omapdriver_fops,
diff --git a/drivers/staging/omapdrm/omap_drv.h b/drivers/staging/omapdrm/omap_drv.h
index 40b626b..d86ccb0 100644
--- a/drivers/staging/omapdrm/omap_drv.h
+++ b/drivers/staging/omapdrm/omap_drv.h
@@ -27,6 +27,7 @@ 
 #include <drm/drm_crtc_helper.h>
 #include <linux/platform_data/omap_drm.h>
 #include "omap_drm.h"
+#include "omap_atomic.h"
 
 /* APIs we need from dispc.. TODO omapdss should export these */
 void dispc_clear_irqs(u32 mask);
@@ -80,15 +81,6 @@  void hdmi_dump_regs(struct seq_file *s);
  */
 #define MAX_MAPPERS 2
 
-/* parameters which describe (unrotated) coordinates of scanout within a fb: */
-struct omap_drm_window {
-	uint32_t rotation;
-	int32_t  crtc_x, crtc_y;		/* signed because can be offscreen */
-	uint32_t crtc_w, crtc_h;
-	uint32_t src_x, src_y;
-	uint32_t src_w, src_h;
-};
-
 /* Once GO bit is set, we can't make further updates to shadowed registers
  * until the GO bit is cleared.  So various parts in the kms code that need
  * to update shadowed registers queue up a pair of callbacks, pre_apply
@@ -144,9 +136,16 @@  struct omap_drm_private {
 	struct drm_property *zorder_prop;
 
 	/* irq handling: */
-	struct list_head irq_list;    /* list of omap_drm_irq */
+	struct list_head irq_list;   /* list of omap_drm_irq */
 	uint32_t vblank_mask;         /* irq bits set for userspace vblank */
 	struct omap_drm_irq error_handler;
+
+	/* atomic: */
+	bool global_atomic;           /* in global atomic update (ie. modeset) */
+	bool crtc_atomic[8];          /* in per-crtc atomic update (ie. pageflip) */
+
+	/* pending vblank event per CRTC: */
+	struct drm_pending_vblank_event *event[8];
 };
 
 /* this should probably be in drm-core to standardize amongst drivers */
@@ -179,6 +178,10 @@  void omap_fbdev_free(struct drm_device *dev);
 
 const struct omap_video_timings *omap_crtc_timings(struct drm_crtc *crtc);
 enum omap_channel omap_crtc_channel(struct drm_crtc *crtc);
+int omap_crtc_check_state(struct drm_crtc *crtc,
+		struct omap_crtc_state *crtc_state);
+void omap_crtc_commit_state(struct drm_crtc *crtc,
+		struct omap_crtc_state *crtc_state);
 int omap_crtc_apply(struct drm_crtc *crtc,
 		struct omap_drm_apply *apply);
 struct drm_crtc *omap_crtc_init(struct drm_device *dev,
@@ -187,17 +190,14 @@  struct drm_crtc *omap_crtc_init(struct drm_device *dev,
 struct drm_plane *omap_plane_init(struct drm_device *dev,
 		int plane_id, bool private_plane);
 int omap_plane_dpms(struct drm_plane *plane, int mode);
-int omap_plane_mode_set(struct drm_plane *plane,
-		struct drm_crtc *crtc, struct drm_framebuffer *fb,
-		int crtc_x, int crtc_y,
-		unsigned int crtc_w, unsigned int crtc_h,
-		uint32_t src_x, uint32_t src_y,
-		uint32_t src_w, uint32_t src_h,
-		void (*fxn)(void *), void *arg);
 void omap_plane_install_properties(struct drm_plane *plane,
 		struct drm_mode_object *obj);
-int omap_plane_set_property(struct drm_plane *plane,
+int omap_plane_set_property(struct drm_plane *plane, void *state,
 		struct drm_property *property, uint64_t val);
+int omap_plane_check_state(struct drm_plane *plane,
+		struct omap_plane_state *plane_state);
+void omap_plane_commit_state(struct drm_plane *plane,
+		struct omap_plane_state *plane_state);
 
 struct drm_encoder *omap_encoder_init(struct drm_device *dev);
 struct drm_encoder *omap_connector_attached_encoder(
@@ -223,7 +223,7 @@  int omap_framebuffer_replace(struct drm_framebuffer *a,
 		struct drm_framebuffer *b, void *arg,
 		void (*unpin)(void *arg, struct drm_gem_object *bo));
 void omap_framebuffer_update_scanout(struct drm_framebuffer *fb,
-		struct omap_drm_window *win, struct omap_overlay_info *info);
+		struct drm_plane_state *state, struct omap_overlay_info *info);
 struct drm_connector *omap_framebuffer_get_next_connector(
 		struct drm_framebuffer *fb, struct drm_connector *from);
 void omap_framebuffer_flush(struct drm_framebuffer *fb,
@@ -309,6 +309,13 @@  static inline uint32_t pipe2vbl(int crtc)
 	return dispc_mgr_get_vsync_irq(channel);
 }
 
+/* is the specified pipe/crtc blocked for atomic update? */
+static inline bool pipe_in_atomic(struct drm_device *dev, int pipe)
+{
+	struct omap_drm_private *priv = dev->dev_private;
+	return priv->global_atomic || priv->crtc_atomic[pipe];
+}
+
 /* should these be made into common util helpers?
  */
 
diff --git a/drivers/staging/omapdrm/omap_fb.c b/drivers/staging/omapdrm/omap_fb.c
index 8025670..669afd9 100644
--- a/drivers/staging/omapdrm/omap_fb.c
+++ b/drivers/staging/omapdrm/omap_fb.c
@@ -154,33 +154,34 @@  static uint32_t get_linear_addr(struct plane *plane,
 /* update ovl info for scanout, handles cases of multi-planar fb's, etc.
  */
 void omap_framebuffer_update_scanout(struct drm_framebuffer *fb,
-		struct omap_drm_window *win, struct omap_overlay_info *info)
+		struct drm_plane_state *state, struct omap_overlay_info *info)
 {
 	struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb);
+	struct omap_plane_state *plane_state = to_omap_plane_state(state);
 	const struct format *format = omap_fb->format;
 	struct plane *plane = &omap_fb->planes[0];
 	uint32_t x, y, orient = 0;
 
 	info->color_mode = format->dss_format;
 
-	info->pos_x      = win->crtc_x;
-	info->pos_y      = win->crtc_y;
-	info->out_width  = win->crtc_w;
-	info->out_height = win->crtc_h;
-	info->width      = win->src_w;
-	info->height     = win->src_h;
+	info->pos_x      = state->crtc_x;
+	info->pos_y      = state->crtc_y;
+	info->out_width  = state->crtc_w;
+	info->out_height = state->crtc_h;
+	info->width      = state->src_w >> 16;
+	info->height     = state->src_h >> 16;
 
-	x = win->src_x;
-	y = win->src_y;
+	x = state->src_x >> 16;
+	y = state->src_y >> 16;
 
 	if (omap_gem_flags(plane->bo) & OMAP_BO_TILED) {
-		uint32_t w = win->src_w;
-		uint32_t h = win->src_h;
+		uint32_t w = state->src_w >> 16;
+		uint32_t h = state->src_h >> 16;
 
-		switch (win->rotation & 0xf) {
+		switch (plane_state->rotation & 0xf) {
 		default:
 			dev_err(fb->dev->dev, "invalid rotation: %02x",
-					(uint32_t)win->rotation);
+					plane_state->rotation);
 			/* fallthru to default to no rotation */
 		case 0:
 		case BIT(DRM_ROTATE_0):
@@ -197,10 +198,10 @@  void omap_framebuffer_update_scanout(struct drm_framebuffer *fb,
 			break;
 		}
 
-		if (win->rotation & BIT(DRM_REFLECT_X))
+		if (plane_state->rotation & BIT(DRM_REFLECT_X))
 			orient ^= MASK_X_INVERT;
 
-		if (win->rotation & BIT(DRM_REFLECT_Y))
+		if (plane_state->rotation & BIT(DRM_REFLECT_Y))
 			orient ^= MASK_Y_INVERT;
 
 		/* adjust x,y offset for flip/invert: */
@@ -220,6 +221,9 @@  void omap_framebuffer_update_scanout(struct drm_framebuffer *fb,
 		info->screen_width  = plane->pitch;
 	}
 
+	/* always do the rotation in tiler (or none at all): */
+	info->rotation = OMAP_DSS_ROT_0;
+
 	/* convert to pixels: */
 	info->screen_width /= format->planes[0].stride_bpp;
 
@@ -316,7 +320,7 @@  struct drm_connector *omap_framebuffer_get_next_connector(
 		if (connector != from) {
 			struct drm_encoder *encoder = connector->encoder;
 			struct drm_crtc *crtc = encoder ? encoder->crtc : NULL;
-			if (crtc && crtc->fb == fb) {
+			if (crtc && crtc->state->fb == fb) {
 				return connector;
 			}
 		}
@@ -342,10 +346,10 @@  void omap_framebuffer_flush(struct drm_framebuffer *fb,
 			 * could do the coordinate translation..
 			 */
 			struct drm_crtc *crtc = connector->encoder->crtc;
-			int cx = max(0, x - crtc->x);
-			int cy = max(0, y - crtc->y);
-			int cw = w + (x - crtc->x) - cx;
-			int ch = h + (y - crtc->y) - cy;
+			int cx = max(0, x - crtc->state->x);
+			int cy = max(0, y - crtc->state->y);
+			int cw = w + (x - crtc->state->x) - cx;
+			int ch = h + (y - crtc->state->y) - cy;
 
 			omap_connector_flush(connector, cx, cy, cw, ch);
 		}
diff --git a/drivers/staging/omapdrm/omap_plane.c b/drivers/staging/omapdrm/omap_plane.c
index 61b53f8..ec179be 100644
--- a/drivers/staging/omapdrm/omap_plane.c
+++ b/drivers/staging/omapdrm/omap_plane.c
@@ -32,24 +32,14 @@ 
  * plane funcs
  */
 
-struct callback {
-	void (*fxn)(void *);
-	void *arg;
-};
-
 #define to_omap_plane(x) container_of(x, struct omap_plane, base)
 
 struct omap_plane {
 	struct drm_plane base;
 	int id;  /* TODO rename omap_plane -> omap_plane_id in omapdss so I can use the enum */
 	const char *name;
-	struct omap_overlay_info info;
 	struct omap_drm_apply apply;
 
-	/* position/orientation of scanout within the fb: */
-	struct omap_drm_window win;
-	bool enabled;
-
 	/* last fb that we pinned: */
 	struct drm_framebuffer *pinned_fb;
 
@@ -58,9 +48,6 @@  struct omap_plane {
 
 	/* set of bo's pending unpin until next post_apply() */
 	DECLARE_KFIFO_PTR(unpin_fifo, struct drm_gem_object *);
-
-	// XXX maybe get rid of this and handle vblank in crtc too?
-	struct callback apply_done_cb;
 };
 
 static void unpin(void *arg, struct drm_gem_object *bo)
@@ -112,24 +99,53 @@  static int update_pin(struct drm_plane *plane, struct drm_framebuffer *fb)
 	return 0;
 }
 
+static inline bool is_enabled(struct drm_plane_state *state)
+{
+	return to_omap_plane_state(state)->enabled &&
+			state->crtc && state->fb;
+}
+
+/* TODO get rid of this and convert dispc code to use drm state
+ * structs directly..
+ */
+static void state2info(struct omap_plane_state *plane_state,
+		struct omap_overlay_info *info)
+{
+
+	memset(info, 0, sizeof(*info));
+
+	info->global_alpha = 0xff;
+	/* TODO: we should calculate valid zorder from all the planes: */
+	info->zorder = plane_state->zorder;
+
+	/* update scanout: */
+	omap_framebuffer_update_scanout(plane_state->base.fb,
+			&plane_state->base, info);
+
+	DBG("%dx%d -> %dx%d (%d)", info->width, info->height,
+			info->out_width, info->out_height, info->screen_width);
+	DBG("%d,%d %08x %08x", info->pos_x, info->pos_y,
+			info->paddr, info->p_uv_addr);
+}
+
 static void omap_plane_pre_apply(struct omap_drm_apply *apply)
 {
 	struct omap_plane *omap_plane =
 			container_of(apply, struct omap_plane, apply);
-	struct omap_drm_window *win = &omap_plane->win;
 	struct drm_plane *plane = &omap_plane->base;
 	struct drm_device *dev = plane->dev;
-	struct omap_overlay_info *info = &omap_plane->info;
-	struct drm_crtc *crtc = plane->crtc;
+	struct omap_overlay_info info;
+	struct drm_crtc *crtc = plane->state->crtc;
+	struct drm_framebuffer *fb = plane->state->fb;
 	enum omap_channel channel;
-	bool enabled = omap_plane->enabled && crtc;
-	bool ilace, replication;
+	bool enabled = is_enabled(plane->state);
+	bool replication;
 	int ret;
 
 	DBG("%s, enabled=%d", omap_plane->name, enabled);
 
 	/* if fb has changed, pin new fb: */
-	update_pin(plane, enabled ? plane->fb : NULL);
+	update_pin(plane, enabled ? fb : NULL);
 
 	if (!enabled) {
 		dispc_ovl_enable(omap_plane->id, false);
@@ -138,21 +154,13 @@  static void omap_plane_pre_apply(struct omap_drm_apply *apply)
 
 	channel = omap_crtc_channel(crtc);
 
-	/* update scanout: */
-	omap_framebuffer_update_scanout(plane->fb, win, info);
-
-	DBG("%dx%d -> %dx%d (%d)", info->width, info->height,
-			info->out_width, info->out_height,
-			info->screen_width);
-	DBG("%d,%d %08x %08x", info->pos_x, info->pos_y,
-			info->paddr, info->p_uv_addr);
+	state2info(to_omap_plane_state(plane->state), &info);
 
 	/* TODO: */
-	ilace = false;
 	replication = false;
 
 	/* and finally, update omapdss: */
-	ret = dispc_ovl_setup(omap_plane->id, channel, info, ilace,
+	ret = dispc_ovl_setup(omap_plane->id, channel, &info,
 			replication, omap_crtc_timings(crtc));
 	if (ret) {
 		dev_err(dev->dev, "dispc_ovl_setup failed: %d\n", ret);
@@ -168,116 +176,48 @@  static void omap_plane_post_apply(struct omap_drm_apply *apply)
 	struct omap_plane *omap_plane =
 			container_of(apply, struct omap_plane, apply);
 	struct drm_plane *plane = &omap_plane->base;
-	struct omap_overlay_info *info = &omap_plane->info;
 	struct drm_gem_object *bo = NULL;
-	struct callback cb;
-
-	cb = omap_plane->apply_done_cb;
-	omap_plane->apply_done_cb.fxn = NULL;
 
 	while (kfifo_get(&omap_plane->unpin_fifo, &bo)) {
 		omap_gem_put_paddr(bo);
 		drm_gem_object_unreference_unlocked(bo);
 	}
 
-	if (cb.fxn)
-		cb.fxn(cb.arg);
+	omap_atomic_plane_update(plane->dev, omap_plane->id);
 
-	if (omap_plane->enabled) {
-		omap_framebuffer_flush(plane->fb, info->pos_x, info->pos_y,
-				info->out_width, info->out_height);
-	}
-}
-
-static int apply(struct drm_plane *plane)
-{
-	if (plane->crtc) {
-		struct omap_plane *omap_plane = to_omap_plane(plane);
-		return omap_crtc_apply(plane->crtc, &omap_plane->apply);
+	if (is_enabled(plane->state)) {
+		struct drm_plane_state *state = plane->state;
+		omap_framebuffer_flush(plane->state->fb,
+				state->crtc_x, state->crtc_y,
+				state->crtc_w, state->crtc_h);
 	}
-	return 0;
-}
-
-int omap_plane_mode_set(struct drm_plane *plane,
-		struct drm_crtc *crtc, struct drm_framebuffer *fb,
-		int crtc_x, int crtc_y,
-		unsigned int crtc_w, unsigned int crtc_h,
-		uint32_t src_x, uint32_t src_y,
-		uint32_t src_w, uint32_t src_h,
-		void (*fxn)(void *), void *arg)
-{
-	struct omap_plane *omap_plane = to_omap_plane(plane);
-	struct omap_drm_window *win = &omap_plane->win;
-
-	win->crtc_x = crtc_x;
-	win->crtc_y = crtc_y;
-	win->crtc_w = crtc_w;
-	win->crtc_h = crtc_h;
-
-	/* src values are in Q16 fixed point, convert to integer: */
-	win->src_x = src_x >> 16;
-	win->src_y = src_y >> 16;
-	win->src_w = src_w >> 16;
-	win->src_h = src_h >> 16;
-
-	if (fxn) {
-		/* omap_crtc should ensure that a new page flip
-		 * isn't permitted while there is one pending:
-		 */
-		BUG_ON(omap_plane->apply_done_cb.fxn);
-
-		omap_plane->apply_done_cb.fxn = fxn;
-		omap_plane->apply_done_cb.arg = arg;
-	}
-
-	plane->fb = fb;
-	plane->crtc = crtc;
-
-	return apply(plane);
-}
-
-static int omap_plane_update(struct drm_plane *plane,
-		struct drm_crtc *crtc, struct drm_framebuffer *fb,
-		int crtc_x, int crtc_y,
-		unsigned int crtc_w, unsigned int crtc_h,
-		uint32_t src_x, uint32_t src_y,
-		uint32_t src_w, uint32_t src_h)
-{
-	struct omap_plane *omap_plane = to_omap_plane(plane);
-	omap_plane->enabled = true;
-	return omap_plane_mode_set(plane, crtc, fb,
-			crtc_x, crtc_y, crtc_w, crtc_h,
-			src_x, src_y, src_w, src_h,
-			NULL, NULL);
-}
-
-static int omap_plane_disable(struct drm_plane *plane)
-{
-	struct omap_plane *omap_plane = to_omap_plane(plane);
-	omap_plane->win.rotation = BIT(DRM_ROTATE_0);
-	return omap_plane_dpms(plane, DRM_MODE_DPMS_OFF);
 }
 
 static void omap_plane_destroy(struct drm_plane *plane)
 {
 	struct omap_plane *omap_plane = to_omap_plane(plane);
 	DBG("%s", omap_plane->name);
-	omap_plane_disable(plane);
+	omap_plane_dpms(plane, DRM_MODE_DPMS_OFF);
 	drm_plane_cleanup(plane);
 	WARN_ON(!kfifo_is_empty(&omap_plane->unpin_fifo));
 	kfifo_free(&omap_plane->unpin_fifo);
+	kfree(plane->state);
 	kfree(omap_plane);
 }
 
 int omap_plane_dpms(struct drm_plane *plane, int mode)
 {
 	struct omap_plane *omap_plane = to_omap_plane(plane);
+	struct drm_plane_state *state = plane->state;
 	bool enabled = (mode == DRM_MODE_DPMS_ON);
 	int ret = 0;
 
-	if (enabled != omap_plane->enabled) {
-		omap_plane->enabled = enabled;
-		ret = apply(plane);
+	DBG("%s: mode=%d", omap_plane->name, mode);
+
+	if (enabled != is_enabled(state)) {
+		to_omap_plane_state(state)->enabled = enabled;
+		if (state->crtc)
+			ret = omap_crtc_apply(state->crtc, &omap_plane->apply);
 	}
 
 	return ret;
@@ -319,29 +259,114 @@  void omap_plane_install_properties(struct drm_plane *plane,
 	drm_object_attach_property(obj, prop, 0);
 }
 
-int omap_plane_set_property(struct drm_plane *plane,
+int omap_plane_set_property(struct drm_plane *plane, void *state,
 		struct drm_property *property, uint64_t val)
 {
 	struct omap_plane *omap_plane = to_omap_plane(plane);
 	struct omap_drm_private *priv = plane->dev->dev_private;
-	int ret = -EINVAL;
+	struct omap_plane_state *plane_state =
+			omap_atomic_plane_state(state, omap_plane->id);
+	int ret;
+
+	DBG("%s: %s = %llx", omap_plane->name, property->name, val);
+
+	ret = drm_plane_set_property(plane, &plane_state->base, property, val);
+	if (!ret) {
+		/* if this property is handled by base, we are nearly done..
+		 * we just need to register an fb property w/ atomic so that
+		 * commit can be deferred until the fb is ready
+		 */
+		struct drm_mode_config *config = &plane->dev->mode_config;
+		if ((property == config->prop_fb_id) && val) {
+			struct drm_mode_object *obj =
+					drm_property_get_obj(property, val);
+			omap_atomic_add_fb(state, obj_to_fb(obj));
+		}
+		return ret;
+	}
+
+	/* if it is not a base plane property, see if it is one of ours: */
 
 	if (property == priv->rotation_prop) {
 		DBG("%s: rotation: %02x", omap_plane->name, (uint32_t)val);
-		omap_plane->win.rotation = val;
-		ret = apply(plane);
+		plane_state->rotation = val;
 	} else if (property == priv->zorder_prop) {
 		DBG("%s: zorder: %02x", omap_plane->name, (uint32_t)val);
-		omap_plane->info.zorder = val;
-		ret = apply(plane);
+		plane_state->zorder = val;
+	} else {
+		return -EINVAL;
 	}
 
-	return ret;
+	return 0;
+}
+
+int omap_plane_check_state(struct drm_plane *plane,
+		struct omap_plane_state *plane_state)
+{
+	struct omap_plane *omap_plane = to_omap_plane(plane);
+	struct drm_crtc *crtc = plane_state->base.crtc;
+	struct omap_overlay_info info;
+	int ret, x_predecim, y_predecim;
+	bool replication;
+
+	if (!is_enabled(&plane_state->base))
+		return 0;
+
+	ret = drm_plane_check_state(plane, &plane_state->base);
+	if (ret)
+		return ret;
+
+	state2info(plane_state, &info);
+
+	/* TODO: */
+	replication = false;
+
+	ret = dispc_ovl_check(omap_plane->id, omap_crtc_channel(crtc),
+			&info, replication, omap_crtc_timings(crtc),
+			&x_predecim, &y_predecim);
+	if (ret) {
+		DBG("%s: dispc_ovl_check failed: %d", omap_plane->name, ret);
+		return ret;
+	}
+
+	/* TODO add some properties to set max pre-decimation.. but
+	 * until then, we'd rather fallback to GPU than decimate:
+	 */
+	if ((x_predecim > 1) || (y_predecim > 1)) {
+		DBG("%s: x_predecim=%d, y_predecim=%d", omap_plane->name,
+				x_predecim, y_predecim);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+void omap_plane_commit_state(struct drm_plane *plane,
+		struct omap_plane_state *plane_state)
+{
+	struct omap_plane *omap_plane = to_omap_plane(plane);
+	struct omap_plane_state *old_state = to_omap_plane_state(plane->state);
+	struct drm_crtc *crtc;
+
+	DBG("%s", omap_plane->name);
+
+	crtc = plane_state->base.crtc;
+
+	/* TODO we need to handle crtc switch.. we should reject that
+	 * at the check() stage if we are still waiting for GO to clear
+	 * on the outgoing crtc..
+	 */
+	if (!crtc)
+		crtc = plane->state->crtc;
+
+	drm_plane_commit_state(plane, &plane_state->base);
+	kfree(old_state);
+
+	if (crtc)
+		omap_crtc_apply(crtc, &omap_plane->apply);
 }
 
 static const struct drm_plane_funcs omap_plane_funcs = {
-		.update_plane = omap_plane_update,
-		.disable_plane = omap_plane_disable,
 		.destroy = omap_plane_destroy,
 		.set_property = omap_plane_set_property,
 };
@@ -360,7 +385,6 @@  struct drm_plane *omap_plane_init(struct drm_device *dev,
 	struct omap_drm_private *priv = dev->dev_private;
 	struct drm_plane *plane = NULL;
 	struct omap_plane *omap_plane;
-	struct omap_overlay_info *info;
 	int ret;
 
 	DBG("%s: priv=%d", plane_names[id], private_plane);
@@ -385,6 +409,13 @@  struct drm_plane *omap_plane_init(struct drm_device *dev,
 
 	plane = &omap_plane->base;
 
+	plane->state = kzalloc(sizeof(struct omap_plane_state), GFP_KERNEL);
+
+	if (!plane->state) {
+		dev_err(dev->dev, "could not allocate CRTC state\n");
+		goto fail;
+	}
+
 	omap_plane->apply.pre_apply  = omap_plane_pre_apply;
 	omap_plane->apply.post_apply = omap_plane_post_apply;
 
@@ -393,24 +424,15 @@  struct drm_plane *omap_plane_init(struct drm_device *dev,
 
 	omap_plane_install_properties(plane, &plane->base);
 
-	/* get our starting configuration, set defaults for parameters
-	 * we don't currently use, etc:
-	 */
-	info = &omap_plane->info;
-	info->rotation_type = OMAP_DSS_ROT_DMA;
-	info->rotation = OMAP_DSS_ROT_0;
-	info->global_alpha = 0xff;
-	info->mirror = 0;
-
 	/* Set defaults depending on whether we are a CRTC or overlay
 	 * layer.
-	 * TODO add ioctl to give userspace an API to change this.. this
-	 * will come in a subsequent patch.
 	 */
-	if (private_plane)
-		omap_plane->info.zorder = 0;
-	else
-		omap_plane->info.zorder = id;
+	if (!private_plane) {
+		struct omap_plane_state *plane_state =
+				to_omap_plane_state(plane->state);
+		plane_state->zorder = id;
+		plane_state->enabled = true;
+	}
 
 	return plane;