diff --git a/tests/Makefile.am b/tests/Makefile.am
index d274a3e..42d9854 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,7 +1,7 @@
 SUBDIRS = util kms modeprint proptest modetest vbltest
 
 if HAVE_LIBKMS
-SUBDIRS += kmstest
+SUBDIRS += kmstest planetest
 endif
 
 if HAVE_RADEON
diff --git a/tests/planetest/Makefile.am b/tests/planetest/Makefile.am
new file mode 100644
index 0000000..b82d05b
--- /dev/null
+++ b/tests/planetest/Makefile.am
@@ -0,0 +1,30 @@
+include Makefile.sources
+
+AM_CFLAGS = $(filter-out -Wpointer-arith, $(WARN_CFLAGS))
+
+AM_CFLAGS += \
+	-I$(top_srcdir)/include/drm \
+	-I$(top_srcdir)/libkms/ \
+	-I$(top_srcdir)
+
+PLANETEST_COMMON_LDADD = \
+	$(top_builddir)/libdrm.la \
+	$(top_builddir)/libkms/libkms.la \
+	-lpthread
+
+if HAVE_INSTALL_TESTS
+bin_PROGRAMS = \
+	atomictest \
+	planetest
+else
+noinst_PROGRAMS = \
+	atomictest \
+	planetest
+endif
+
+atomictest_CFLAGS=-DUSE_ATOMIC_API ${AM_CFLAGS}
+atomictest_SOURCES=${PLANETEST_COMMON_FILES} ${ATOMICTEST_FILES}
+planetest_SOURCES=${PLANETEST_COMMON_FILES} ${PLANETEST_FILES}
+
+atomictest_LDADD=${PLANETEST_COMMON_LDADD}
+planetest_LDADD=${PLANETEST_COMMON_LDADD}
diff --git a/tests/planetest/Makefile.sources b/tests/planetest/Makefile.sources
new file mode 100644
index 0000000..3cbeb2b
--- /dev/null
+++ b/tests/planetest/Makefile.sources
@@ -0,0 +1,13 @@
+PLANETEST_COMMON_FILES := \
+	bo.c \
+	bo.h \
+	dev.c \
+	dev.h \
+	modeset.c \
+	modeset.h
+
+ATOMICTEST_FILES := \
+	atomictest.c
+
+PLANETEST_FILES := \
+	planetest.c
diff --git a/tests/planetest/atomictest.c b/tests/planetest/atomictest.c
new file mode 100644
index 0000000..891d242
--- /dev/null
+++ b/tests/planetest/atomictest.c
@@ -0,0 +1,151 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+
+#include <xf86drm.h>
+
+#include "dev.h"
+#include "bo.h"
+#include "modeset.h"
+
+static int terminate = 0;
+
+static void sigint_handler(int arg)
+{
+	terminate = 1;
+}
+
+static void
+page_flip_handler(int fd, unsigned int sequence, unsigned int tv_sec,
+		unsigned int tv_usec, void *user_data)
+{
+}
+
+static void incrementor(int *inc, int *val, int increment, int lower, int upper)
+{
+	if(*inc > 0)
+		*inc = *val + increment >= upper ? -1 : 1;
+	else
+		*inc = *val - increment <= lower ? 1 : -1;
+	*val += *inc * increment;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret, i, j, num_test_planes;
+	int x_inc = 1, x = 0, y_inc = 1, y = 0;
+	uint32_t plane_w = 128, plane_h = 128;
+	struct sp_dev *dev;
+	struct sp_plane **plane = NULL;
+	struct sp_crtc *test_crtc;
+	fd_set fds;
+	drmModeAtomicReqPtr pset;
+	drmEventContext event_context = {
+		.version = DRM_EVENT_CONTEXT_VERSION,
+		.page_flip_handler = page_flip_handler,
+	};
+	int card = 0, crtc = 0;
+
+	signal(SIGINT, sigint_handler);
+
+	parse_arguments(argc, argv, &card, &crtc);
+
+	dev = create_sp_dev(card);
+	if (!dev) {
+		printf("Failed to create sp_dev\n");
+		return -1;
+	}
+
+	if (crtc >= dev->num_crtcs) {
+		printf("Invalid crtc %d (num=%d)\n", crtc, dev->num_crtcs);
+		return -1;
+	}
+
+	ret = initialize_screens(dev);
+	if (ret) {
+		printf("Failed to initialize screens\n");
+		goto out;
+	}
+	test_crtc = &dev->crtcs[crtc];
+
+	plane = calloc(dev->num_planes, sizeof(*plane));
+	if (!plane) {
+		printf("Failed to allocate plane array\n");
+		goto out;
+	}
+
+	/* Create our planes */
+	num_test_planes = test_crtc->num_planes;
+	for (i = 0; i < num_test_planes; i++) {
+		plane[i] = get_sp_plane(dev, test_crtc);
+		if (!plane[i]) {
+			printf("no unused planes available\n");
+			goto out;
+		}
+
+		plane[i]->bo = create_sp_bo(dev, plane_w, plane_h, 16, plane[i]->format, 0);
+		if (!plane[i]->bo) {
+			printf("failed to create plane bo\n");
+			goto out;
+		}
+
+		fill_bo(plane[i]->bo, 0xFF, 0xFF, 0xFF, 0xFF);
+	}
+
+	pset = drmModeAtomicAlloc();
+	if (!pset) {
+		printf("Failed to allocate the property set\n");
+		goto out;
+	}
+
+	while (!terminate) {
+		FD_ZERO(&fds);
+		FD_SET(dev->fd, &fds);
+
+		incrementor(&x_inc, &x, 5, 0,
+			test_crtc->crtc->mode.hdisplay - plane_w);
+		incrementor(&y_inc, &y, 5, 0, test_crtc->crtc->mode.vdisplay -
+						plane_h * num_test_planes);
+
+		for (j = 0; j < num_test_planes; j++) {
+			ret = set_sp_plane_pset(dev, plane[j], pset, test_crtc,
+					x, y + j * plane_h);
+			if (ret) {
+				printf("failed to move plane %d\n", ret);
+				goto out;
+			}
+		}
+
+		ret = drmModeAtomicCommit(dev->fd, pset,
+					DRM_MODE_PAGE_FLIP_EVENT, NULL);
+		if (ret) {
+			printf("failed to commit properties ret=%d\n", ret);
+			goto out;
+		}
+
+		do {
+			ret = select(dev->fd + 1, &fds, NULL, NULL, NULL);
+		} while (ret == -1 && errno == EINTR);
+
+		if (FD_ISSET(dev->fd, &fds))
+			drmHandleEvent(dev->fd, &event_context);
+	}
+
+	drmModeAtomicFree(pset);
+
+	for (i = 0; i < num_test_planes; i++)
+		put_sp_plane(plane[i]);
+
+out:
+	destroy_sp_dev(dev);
+	free(plane);
+	return ret;
+}
diff --git a/tests/planetest/bo.c b/tests/planetest/bo.c
new file mode 100644
index 0000000..d4b82c6
--- /dev/null
+++ b/tests/planetest/bo.c
@@ -0,0 +1,234 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+#include "bo.h"
+#include "dev.h"
+
+#define MAKE_YUV_601_Y(r, g, b) \
+	((( 66 * (r) + 129 * (g) +  25 * (b) + 128) >> 8) + 16)
+#define MAKE_YUV_601_U(r, g, b) \
+	(((-38 * (r) -  74 * (g) + 112 * (b) + 128) >> 8) + 128)
+#define MAKE_YUV_601_V(r, g, b) \
+	(((112 * (r) -  94 * (g) -  18 * (b) + 128) >> 8) + 128)
+
+static void draw_rect_yuv(struct sp_bo *bo, uint32_t x, uint32_t y, uint32_t width,
+		uint32_t height, uint8_t a, uint8_t r, uint8_t g, uint8_t b)
+{
+	uint32_t i, j, xmax = x + width, ymax = y + height;
+
+	if (xmax > bo->width)
+		xmax = bo->width;
+	if (ymax > bo->height)
+		ymax = bo->height;
+
+	for (i = y; i < ymax; i++) {
+		uint8_t *luma = bo->map_addr + i * bo->pitch;
+
+		for (j = x; j < xmax; j++)
+			luma[j] = MAKE_YUV_601_Y(r, g, b);
+	}
+
+	for (i = y; i < ymax / 2; i++) {
+		uint8_t *chroma = bo->map_addr + (i + height) * bo->pitch;
+
+		for (j = x; j < xmax / 2; j++) {
+			chroma[j*2] = MAKE_YUV_601_U(r, g, b);
+			chroma[j*2 + 1] = MAKE_YUV_601_V(r, g, b);
+		}
+	}
+}
+
+void fill_bo(struct sp_bo *bo, uint8_t a, uint8_t r, uint8_t g, uint8_t b)
+{
+	if (bo->format == DRM_FORMAT_NV12)
+		draw_rect_yuv(bo, 0, 0, bo->width, bo->height, a, r, g, b);
+	else
+		draw_rect(bo, 0, 0, bo->width, bo->height, a, r, g, b);
+}
+
+void draw_rect(struct sp_bo *bo, uint32_t x, uint32_t y, uint32_t width,
+		uint32_t height, uint8_t a, uint8_t r, uint8_t g, uint8_t b)
+{
+	uint32_t i, j, xmax = x + width, ymax = y + height;
+
+	if (xmax > bo->width)
+		xmax = bo->width;
+	if (ymax > bo->height)
+		ymax = bo->height;
+
+	for (i = y; i < ymax; i++) {
+		uint8_t *row = bo->map_addr + i * bo->pitch;
+
+		for (j = x; j < xmax; j++) {
+			uint8_t *pixel = row + j * 4;
+
+			if (bo->format == DRM_FORMAT_ARGB8888 ||
+			    bo->format == DRM_FORMAT_XRGB8888)
+			{
+				pixel[0] = b;
+				pixel[1] = g;
+				pixel[2] = r;
+				pixel[3] = a;
+			} else if (bo->format == DRM_FORMAT_RGBA8888) {
+				pixel[0] = r;
+				pixel[1] = g;
+				pixel[2] = b;
+				pixel[3] = a;
+			}
+		}
+	}
+}
+
+static int add_fb_sp_bo(struct sp_bo *bo, uint32_t format)
+{
+	int ret;
+	uint32_t handles[4], pitches[4], offsets[4];
+
+	handles[0] = bo->handle;
+	pitches[0] = bo->pitch;
+	offsets[0] = 0;
+	if (bo->format == DRM_FORMAT_NV12) {
+		handles[1] = bo->handle;
+		pitches[1] = pitches[0];
+		offsets[1] = pitches[0] * bo->height;
+	}
+
+	ret = drmModeAddFB2(bo->dev->fd, bo->width, bo->height,
+			format, handles, pitches, offsets,
+			&bo->fb_id, bo->flags);
+	if (ret) {
+		printf("failed to create fb ret=%d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int map_sp_bo(struct sp_bo *bo)
+{
+	int ret;
+	struct drm_mode_map_dumb md;
+
+	if (bo->map_addr)
+		return 0;
+
+	md.handle = bo->handle;
+	ret = drmIoctl(bo->dev->fd, DRM_IOCTL_MODE_MAP_DUMB, &md);
+	if (ret) {
+		printf("failed to map sp_bo ret=%d\n", ret);
+		return ret;
+	}
+
+	bo->map_addr = mmap(NULL, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+				bo->dev->fd, md.offset);
+	if (bo->map_addr == MAP_FAILED) {
+		printf("failed to map bo ret=%d\n", -errno);
+		return -errno;
+	}
+	return 0;
+}
+
+static int format_to_bpp(uint32_t format)
+{
+	switch (format) {
+	case DRM_FORMAT_NV12:
+		return 8;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_RGBA8888:
+	default:
+		return 32;
+	}
+}
+
+struct sp_bo *create_sp_bo(struct sp_dev *dev, uint32_t width, uint32_t height,
+		uint32_t depth, uint32_t format, uint32_t flags)
+{
+	int ret;
+	struct drm_mode_create_dumb cd;
+	struct sp_bo *bo;
+
+	bo = calloc(1, sizeof(*bo));
+	if (!bo)
+		return NULL;
+
+	if (format == DRM_FORMAT_NV12)
+		cd.height = height * 3 / 2;
+	else
+		cd.height = height;
+
+	cd.width = width;
+	cd.bpp = format_to_bpp(format);
+	cd.flags = flags;
+
+	ret = drmIoctl(dev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &cd);
+	if (ret) {
+		printf("failed to create sp_bo %d\n", ret);
+		goto err;
+	}
+
+	bo->dev = dev;
+	bo->width = width;
+	bo->height = height;
+	bo->depth = depth;
+	bo->bpp = format_to_bpp(format);
+	bo->format = format;
+	bo->flags = flags;
+
+	bo->handle = cd.handle;
+	bo->pitch = cd.pitch;
+	bo->size = cd.size;
+
+	ret = add_fb_sp_bo(bo, format);
+	if (ret) {
+		printf("failed to add fb ret=%d\n", ret);
+		goto err;
+	}
+
+	ret = map_sp_bo(bo);
+	if (ret) {
+		printf("failed to map bo ret=%d\n", ret);
+		goto err;
+	}
+
+	return bo;
+
+err:
+	free_sp_bo(bo);
+	return NULL;
+}
+
+void free_sp_bo(struct sp_bo *bo)
+{
+	int ret;
+	struct drm_mode_destroy_dumb dd;
+
+	if (!bo)
+		return;
+
+	if (bo->map_addr)
+		munmap(bo->map_addr, bo->size);
+
+	if (bo->fb_id) {
+		ret = drmModeRmFB(bo->dev->fd, bo->fb_id);
+		if (ret)
+			printf("Failed to rmfb ret=%d!\n", ret);
+	}
+
+	if (bo->handle) {
+		dd.handle = bo->handle;
+		ret = drmIoctl(bo->dev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dd);
+		if (ret)
+			printf("Failed to destroy buffer ret=%d\n", ret);
+	}
+
+	free(bo);
+}
diff --git a/tests/planetest/bo.h b/tests/planetest/bo.h
new file mode 100644
index 0000000..7471e12
--- /dev/null
+++ b/tests/planetest/bo.h
@@ -0,0 +1,34 @@
+#ifndef __BO_H_INCLUDED__
+#define __BO_H_INCLUDED__
+
+#include <stdint.h>
+
+struct sp_dev;
+
+struct sp_bo {
+	struct sp_dev *dev;
+
+	uint32_t width;
+	uint32_t height;
+	uint32_t depth;
+	uint32_t bpp;
+	uint32_t format;
+	uint32_t flags;
+
+	uint32_t fb_id;
+	uint32_t handle;
+	void *map_addr;
+	uint32_t pitch;
+	uint32_t size;
+};
+
+struct sp_bo *create_sp_bo(struct sp_dev *dev, uint32_t width, uint32_t height,
+		uint32_t depth, uint32_t format, uint32_t flags);
+
+void fill_bo(struct sp_bo *bo, uint8_t a, uint8_t r, uint8_t g, uint8_t b);
+void draw_rect(struct sp_bo *bo, uint32_t x, uint32_t y, uint32_t width,
+		uint32_t height, uint8_t a, uint8_t r, uint8_t g, uint8_t b);
+
+void free_sp_bo(struct sp_bo *bo);
+
+#endif /* __BO_H_INCLUDED__ */
diff --git a/tests/planetest/dev.c b/tests/planetest/dev.c
new file mode 100644
index 0000000..bd0968c
--- /dev/null
+++ b/tests/planetest/dev.c
@@ -0,0 +1,367 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <drm.h>
+#include <drm_fourcc.h>
+#include <errno.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "bo.h"
+#include "dev.h"
+#include "modeset.h"
+
+static void show_usage(char *name)
+{
+	printf("Usage: %s [OPTION]\n", name);
+	printf("   -c, --card      Index of dri card (ie: /dev/dri/cardN)\n");
+	printf("   -r, --crtc      Index of crtc to use for test\n");
+	printf("\n\n");
+}
+
+void parse_arguments(int argc, char *argv[], int *card, int *crtc)
+{
+	static struct option options[] = {
+		{ "card", required_argument, NULL, 'c' },
+		{ "crtc", required_argument, NULL, 'r' },
+		{ "help", no_argument, NULL, 'h' },
+	};
+	int option_index = 0;
+	int c;
+
+	*card = -1;
+	*crtc = -1;
+	do {
+		c = getopt_long(argc, argv, "c:r:h", options, &option_index);
+		switch (c) {
+		case 0:
+		case 'h':
+			show_usage(argv[0]);
+			exit(0);
+		case -1:
+			break;
+		case 'c':
+			if (optarg[0] < '0' || optarg[0] > '9') {
+				printf("Invalid card value '%s'!\n", optarg);
+				show_usage(argv[0]);
+				exit(-1);
+			}
+			*card = optarg[0] - '0';
+			break;
+		case 'r':
+			if (optarg[0] < '0' || optarg[0] > '9') {
+				printf("Invalid crtc value '%s'!\n", optarg);
+				show_usage(argv[0]);
+				exit(-1);
+			}
+			*crtc = optarg[0] - '0';
+			break;
+		}
+	} while (c != -1);
+
+	if (*card < 0 || *crtc < 0) {
+		show_usage(argv[0]);
+		exit(-1);
+	}
+}
+
+static uint32_t get_prop_id(struct sp_dev *dev,
+			drmModeObjectPropertiesPtr props, const char *name)
+{
+	drmModePropertyPtr p;
+	uint32_t i, prop_id = 0; /* Property ID should always be > 0 */
+
+	for (i = 0; !prop_id && i < props->count_props; i++) {
+		p = drmModeGetProperty(dev->fd, props->props[i]);
+		if (!strcmp(p->name, name))
+			prop_id = p->prop_id;
+		drmModeFreeProperty(p);
+	}
+	if (!prop_id)
+		printf("Could not find %s property\n", name);
+	return prop_id;
+}
+
+static int get_supported_format(struct sp_plane *plane, uint32_t *format)
+{
+	uint32_t i;
+
+	for (i = 0; i < plane->plane->count_formats; i++) {
+		if (plane->plane->formats[i] == DRM_FORMAT_XRGB8888 ||
+		    plane->plane->formats[i] == DRM_FORMAT_ARGB8888 ||
+		    plane->plane->formats[i] == DRM_FORMAT_RGBA8888 ||
+		    plane->plane->formats[i] == DRM_FORMAT_NV12) {
+			*format = plane->plane->formats[i];
+			return 0;
+		}
+	}
+	printf("No suitable formats found!\n");
+	return -ENOENT;
+}
+
+struct sp_dev *create_sp_dev(int card)
+{
+	struct sp_dev *dev;
+	int ret, fd, i, j;
+	drmModeRes *r = NULL;
+	drmModePlaneRes *pr = NULL;
+	char card_path[256];
+
+	snprintf(card_path, sizeof(card_path), "/dev/dri/card%d", card);
+
+	fd = open(card_path, O_RDWR);
+	if (fd < 0) {
+		printf("failed to open card0\n");
+		return NULL;
+	}
+
+	dev = calloc(1, sizeof(*dev));
+	if (!dev) {
+		printf("failed to allocate dev\n");
+		return NULL;
+	}
+
+	dev->fd = fd;
+
+	ret = drmSetClientCap(dev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+	if (ret) {
+		printf("failed to set client cap\n");
+		goto err;
+	}
+
+	ret = drmSetClientCap(dev->fd, DRM_CLIENT_CAP_ATOMIC, 1);
+	if (ret) {
+		printf("Failed to set atomic cap %d", ret);
+		goto err;
+	}
+
+	r = drmModeGetResources(dev->fd);
+	if (!r) {
+		printf("failed to get r\n");
+		goto err;
+	}
+
+	dev->num_connectors = r->count_connectors;
+	dev->connectors = calloc(dev->num_connectors,
+				sizeof(struct sp_connector));
+	if (!dev->connectors) {
+		printf("failed to allocate connectors\n");
+		goto err;
+	}
+	for (i = 0; i < dev->num_connectors; i++) {
+		drmModeObjectPropertiesPtr props;
+		dev->connectors[i].conn = drmModeGetConnector(dev->fd,
+					r->connectors[i]);
+		if (!dev->connectors[i].conn) {
+			printf("failed to get connector %d\n", i);
+			goto err;
+		}
+
+		props = drmModeObjectGetProperties(dev->fd, r->connectors[i],
+				DRM_MODE_OBJECT_CONNECTOR);
+		if (!props) {
+			printf("failed to get connector properties\n");
+			goto err;
+		}
+
+		dev->connectors[i].crtc_id_pid = get_prop_id(dev, props,
+								"CRTC_ID");
+		drmModeFreeObjectProperties(props);
+		if (!dev->connectors[i].crtc_id_pid)
+			goto err;
+	}
+
+	dev->num_encoders = r->count_encoders;
+	dev->encoders = calloc(dev->num_encoders, sizeof(*dev->encoders));
+	if (!dev->encoders) {
+		printf("failed to allocate encoders\n");
+		goto err;
+	}
+	for (i = 0; i < dev->num_encoders; i++) {
+		dev->encoders[i] = drmModeGetEncoder(dev->fd, r->encoders[i]);
+		if (!dev->encoders[i]) {
+			printf("failed to get encoder %d\n", i);
+			goto err;
+		}
+	}
+
+	dev->num_crtcs = r->count_crtcs;
+	dev->crtcs = calloc(dev->num_crtcs, sizeof(struct sp_crtc));
+	if (!dev->crtcs) {
+		printf("failed to allocate crtcs\n");
+		goto err;
+	}
+	for (i = 0; i < dev->num_crtcs; i++) {
+		drmModeObjectPropertiesPtr props;
+
+		dev->crtcs[i].crtc = drmModeGetCrtc(dev->fd, r->crtcs[i]);
+		if (!dev->crtcs[i].crtc) {
+			printf("failed to get crtc %d\n", i);
+			goto err;
+		}
+		dev->crtcs[i].pipe = i;
+		dev->crtcs[i].num_planes = 0;
+
+		props = drmModeObjectGetProperties(dev->fd, r->crtcs[i],
+				DRM_MODE_OBJECT_CRTC);
+		if (!props) {
+			printf("failed to get crtc properties\n");
+			goto err;
+		}
+
+		dev->crtcs[i].mode_pid = get_prop_id(dev, props, "MODE_ID");
+		dev->crtcs[i].active_pid = get_prop_id(dev, props, "ACTIVE");
+		drmModeFreeObjectProperties(props);
+		if (!dev->crtcs[i].mode_pid || !dev->crtcs[i].active_pid)
+			goto err;
+	}
+
+	pr = drmModeGetPlaneResources(dev->fd);
+	if (!pr) {
+		printf("failed to get plane resources\n");
+		goto err;
+	}
+	dev->num_planes = pr->count_planes;
+	dev->planes = calloc(dev->num_planes, sizeof(struct sp_plane));
+	for(i = 0; i < dev->num_planes; i++) {
+		drmModeObjectPropertiesPtr props;
+		struct sp_plane *plane = &dev->planes[i];
+
+		plane->dev = dev;
+		plane->plane = drmModeGetPlane(dev->fd, pr->planes[i]);
+		if (!plane->plane) {
+			printf("failed to get plane %d\n", i);
+			goto err;
+		}
+		plane->bo = NULL;
+		plane->in_use = 0;
+
+		ret = get_supported_format(plane, &plane->format);
+		if (ret) {
+			printf("failed to get supported format: %d\n", ret);
+			goto err;
+		}
+
+		for (j = 0; j < dev->num_crtcs; j++) {
+			if (plane->plane->possible_crtcs & (1 << j))
+				dev->crtcs[j].num_planes++;
+		}
+
+		props = drmModeObjectGetProperties(dev->fd, pr->planes[i],
+				DRM_MODE_OBJECT_PLANE);
+		if (!props) {
+			printf("failed to get plane properties\n");
+			goto err;
+		}
+		plane->crtc_pid = get_prop_id(dev, props, "CRTC_ID");
+		if (!plane->crtc_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->fb_pid = get_prop_id(dev, props, "FB_ID");
+		if (!plane->fb_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->crtc_x_pid = get_prop_id(dev, props, "CRTC_X");
+		if (!plane->crtc_x_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->crtc_y_pid = get_prop_id(dev, props, "CRTC_Y");
+		if (!plane->crtc_y_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->crtc_w_pid = get_prop_id(dev, props, "CRTC_W");
+		if (!plane->crtc_w_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->crtc_h_pid = get_prop_id(dev, props, "CRTC_H");
+		if (!plane->crtc_h_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->src_x_pid = get_prop_id(dev, props, "SRC_X");
+		if (!plane->src_x_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->src_y_pid = get_prop_id(dev, props, "SRC_Y");
+		if (!plane->src_y_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->src_w_pid = get_prop_id(dev, props, "SRC_W");
+		if (!plane->src_w_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		plane->src_h_pid = get_prop_id(dev, props, "SRC_H");
+		if (!plane->src_h_pid) {
+			drmModeFreeObjectProperties(props);
+			goto err;
+		}
+		drmModeFreeObjectProperties(props);
+	}
+
+	if (pr)
+		drmModeFreePlaneResources(pr);
+	if (r)
+		drmModeFreeResources(r);
+
+	return dev;
+err:
+	if (pr)
+		drmModeFreePlaneResources(pr);
+	if (r)
+		drmModeFreeResources(r);
+	destroy_sp_dev(dev);
+	return NULL;
+}
+
+void destroy_sp_dev(struct sp_dev *dev)
+{
+	int i;
+
+	if (dev->planes) {
+		for (i = 0; i< dev->num_planes; i++) {
+			if (dev->planes[i].in_use)
+				put_sp_plane(&dev->planes[i]);
+			if (dev->planes[i].plane)
+				drmModeFreePlane(dev->planes[i].plane);
+			if (dev->planes[i].bo)
+				free_sp_bo(dev->planes[i].bo);
+		}
+		free(dev->planes);
+	}
+	if (dev->crtcs) {
+		for (i = 0; i< dev->num_crtcs; i++) {
+			if (dev->crtcs[i].crtc)
+				drmModeFreeCrtc(dev->crtcs[i].crtc);
+		}
+		free(dev->crtcs);
+	}
+	if (dev->encoders) {
+		for (i = 0; i< dev->num_encoders; i++) {
+			if (dev->encoders[i])
+				drmModeFreeEncoder(dev->encoders[i]);
+		}
+		free(dev->encoders);
+	}
+	if (dev->connectors) {
+		for (i = 0; i< dev->num_connectors; i++) {
+			if (dev->connectors[i].conn)
+				drmModeFreeConnector(dev->connectors[i].conn);
+		}
+		free(dev->connectors);
+	}
+
+	close(dev->fd);
+	free(dev);
+}
diff --git a/tests/planetest/dev.h b/tests/planetest/dev.h
new file mode 100644
index 0000000..04dec79
--- /dev/null
+++ b/tests/planetest/dev.h
@@ -0,0 +1,65 @@
+#ifndef __DEV_H_INCLUDED__
+#define __DEV_H_INCLUDED__
+
+#include <stdint.h>
+#include <xf86drmMode.h>
+
+struct sp_bo;
+struct sp_dev;
+
+struct sp_plane {
+	struct sp_dev *dev;
+	drmModePlanePtr plane;
+	struct sp_bo *bo;
+	int in_use;
+	uint32_t format;
+
+	/* Property ID's */
+	uint32_t crtc_pid;
+	uint32_t fb_pid;
+	uint32_t zpos_pid;
+	uint32_t crtc_x_pid;
+	uint32_t crtc_y_pid;
+	uint32_t crtc_w_pid;
+	uint32_t crtc_h_pid;
+	uint32_t src_x_pid;
+	uint32_t src_y_pid;
+	uint32_t src_w_pid;
+	uint32_t src_h_pid;
+};
+
+struct sp_connector {
+	drmModeConnectorPtr conn;
+	uint32_t crtc_id_pid;
+};
+
+struct sp_crtc {
+	drmModeCrtcPtr crtc;
+	int pipe;
+	int num_planes;
+	uint32_t mode_pid;
+	uint32_t active_pid;
+};
+
+struct sp_dev {
+	int fd;
+
+	int num_connectors;
+	struct sp_connector *connectors;
+
+	int num_encoders;
+	drmModeEncoderPtr *encoders;
+
+	int num_crtcs;
+	struct sp_crtc *crtcs;
+
+	int num_planes;
+	struct sp_plane *planes;
+};
+
+void parse_arguments(int argc, char *argv[], int *card, int *crtc);
+
+struct sp_dev *create_sp_dev(int card);
+void destroy_sp_dev(struct sp_dev *dev);
+
+#endif /* __DEV_H_INCLUDED__ */
diff --git a/tests/planetest/modeset.c b/tests/planetest/modeset.c
new file mode 100644
index 0000000..b8f6690
--- /dev/null
+++ b/tests/planetest/modeset.c
@@ -0,0 +1,232 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+#include "modeset.h"
+#include "bo.h"
+#include "dev.h"
+
+static int set_crtc_mode(struct sp_dev *dev, struct sp_crtc *crtc,
+			struct sp_connector *conn, drmModeModeInfoPtr mode)
+{
+	int ret;
+	struct drm_mode_create_blob create_blob;
+	drmModeAtomicReqPtr pset;
+
+	memset(&create_blob, 0, sizeof(create_blob));
+	create_blob.length = sizeof(struct drm_mode_modeinfo);
+	create_blob.data = (__u64)(uintptr_t)mode;
+
+	ret = drmIoctl(dev->fd, DRM_IOCTL_MODE_CREATEPROPBLOB, &create_blob);
+	if (ret) {
+		printf("Failed to create mode property blob %d", ret);
+		return ret;
+	}
+
+	pset = drmModeAtomicAlloc();
+	if (!pset) {
+		printf("Failed to allocate property set");
+		return -1;
+	}
+
+	ret = drmModeAtomicAddProperty(pset, crtc->crtc->crtc_id,
+				    crtc->mode_pid, create_blob.blob_id) ||
+	      drmModeAtomicAddProperty(pset, crtc->crtc->crtc_id,
+				    crtc->active_pid, 1) ||
+	      drmModeAtomicAddProperty(pset, conn->conn->connector_id,
+				conn->crtc_id_pid, crtc->crtc->crtc_id);
+	if (ret) {
+		printf("Failed to add blob %d to pset", create_blob.blob_id);
+		drmModeAtomicFree(pset);
+		return ret;
+	}
+
+	ret = drmModeAtomicCommit(dev->fd, pset, DRM_MODE_ATOMIC_ALLOW_MODESET,
+					NULL);
+
+	drmModeAtomicFree(pset);
+
+	if (ret) {
+		printf("Failed to commit pset ret=%d\n", ret);
+		return ret;
+	}
+
+	memcpy(&crtc->crtc->mode, mode, sizeof(struct drm_mode_modeinfo));
+	crtc->crtc->mode_valid = 1;
+	return 0;
+}
+
+int initialize_screens(struct sp_dev *dev)
+{
+	int ret, i, j;
+	unsigned crtc_mask = 0;
+
+	for (i = 0; i < dev->num_connectors; i++) {
+		struct sp_connector *c = &dev->connectors[i];
+		drmModeModeInfoPtr m = NULL;
+		drmModeEncoderPtr e = NULL;
+		struct sp_crtc *cr = NULL;
+
+		if (c->conn->connection != DRM_MODE_CONNECTED)
+			continue;
+
+		if (!c->conn->count_modes) {
+			printf("connector has no modes, skipping\n");
+			continue;
+		}
+
+		/* Take the first unless there's a preferred mode */
+		m = &c->conn->modes[0];
+		for (j = 0; j < c->conn->count_modes; j++) {
+			drmModeModeInfoPtr tmp_m = &c->conn->modes[j];
+
+			if (!(tmp_m->type & DRM_MODE_TYPE_PREFERRED))
+				continue;
+
+			m = tmp_m;
+			break;
+		}
+
+		if (!c->conn->count_encoders) {
+			printf("no possible encoders for connector\n");
+			continue;
+		}
+
+		for (j = 0; j < dev->num_encoders; j++) {
+			e = dev->encoders[j];
+			if (e->encoder_id == c->conn->encoders[0])
+				break;
+		}
+		if (j == dev->num_encoders) {
+			printf("could not find encoder for the connector\n");
+			continue;
+		}
+
+		for (j = 0; j < dev->num_crtcs; j++) {
+			if ((1 << j) & crtc_mask)
+				continue;
+
+			cr = &dev->crtcs[j];
+
+			if ((1 << j) & e->possible_crtcs)
+				break;
+		}
+		if (j == dev->num_crtcs) {
+			printf("could not find crtc for the encoder\n");
+			continue;
+		}
+
+		ret = set_crtc_mode(dev, cr, c, m);
+		if (ret) {
+			printf("failed to set mode!\n");
+			continue;
+		}
+		crtc_mask |= 1 << j;
+	}
+	return 0;
+}
+
+struct sp_plane *get_sp_plane(struct sp_dev *dev, struct sp_crtc *crtc)
+{
+	int i;
+
+	for(i = 0; i < dev->num_planes; i++) {
+		struct sp_plane *p = &dev->planes[i];
+
+		if (p->in_use)
+			continue;
+
+		if (!(p->plane->possible_crtcs & (1 << crtc->pipe)))
+			continue;
+
+		p->in_use = 1;
+		return p;
+	}
+	return NULL;
+}
+
+void put_sp_plane(struct sp_plane *plane)
+{
+	drmModePlanePtr p;
+
+	/* Get the latest plane information (most notably the crtc_id) */
+	p = drmModeGetPlane(plane->dev->fd, plane->plane->plane_id);
+	if (p)
+		plane->plane = p;
+
+	if (plane->bo) {
+		free_sp_bo(plane->bo);
+		plane->bo = NULL;
+	}
+	plane->in_use = 0;
+}
+
+int set_sp_plane(struct sp_dev *dev, struct sp_plane *plane,
+		struct sp_crtc *crtc, int x, int y)
+{
+	int ret;
+	uint32_t w, h;
+
+	w = plane->bo->width;
+	h = plane->bo->height;
+
+	if ((w + x) > crtc->crtc->mode.hdisplay)
+		w = crtc->crtc->mode.hdisplay - x;
+	if ((h + y) > crtc->crtc->mode.vdisplay)
+		h = crtc->crtc->mode.vdisplay - y;
+
+	ret = drmModeSetPlane(dev->fd, plane->plane->plane_id,
+			crtc->crtc->crtc_id, plane->bo->fb_id, 0, x, y, w, h,
+			0, 0, w << 16, h << 16);
+	if (ret) {
+		printf("failed to set plane to crtc ret=%d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+int set_sp_plane_pset(struct sp_dev *dev, struct sp_plane *plane,
+		drmModeAtomicReqPtr pset, struct sp_crtc *crtc, int x, int y)
+{
+	int ret;
+	uint32_t w, h;
+
+	w = plane->bo->width;
+	h = plane->bo->height;
+
+	if ((w + x) > crtc->crtc->mode.hdisplay)
+		w = crtc->crtc->mode.hdisplay - x;
+	if ((h + y) > crtc->crtc->mode.vdisplay)
+		h = crtc->crtc->mode.vdisplay - y;
+
+	ret = drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->crtc_pid, crtc->crtc->crtc_id)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->fb_pid, plane->bo->fb_id)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->crtc_x_pid, x)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->crtc_y_pid, y)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->crtc_w_pid, w)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->crtc_h_pid, h)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->src_x_pid, 0)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->src_y_pid, 0)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->src_w_pid, w << 16)
+		|| drmModeAtomicAddProperty(pset, plane->plane->plane_id,
+			plane->src_h_pid, h << 16);
+	if (ret) {
+		printf("failed to add properties to the set\n");
+		return -1;
+	}
+
+	return ret;
+}
diff --git a/tests/planetest/modeset.h b/tests/planetest/modeset.h
new file mode 100644
index 0000000..5499959
--- /dev/null
+++ b/tests/planetest/modeset.h
@@ -0,0 +1,19 @@
+#ifndef __MODESET_H_INCLUDED__
+#define __MODESET_H_INCLUDED__
+
+struct sp_dev;
+struct sp_crtc;
+
+int initialize_screens(struct sp_dev *dev);
+
+
+struct sp_plane *get_sp_plane(struct sp_dev *dev, struct sp_crtc *crtc);
+void put_sp_plane(struct sp_plane *plane);
+
+int set_sp_plane(struct sp_dev *dev, struct sp_plane *plane,
+		struct sp_crtc *crtc, int x, int y);
+
+int set_sp_plane_pset(struct sp_dev *dev, struct sp_plane *plane,
+		drmModeAtomicReqPtr pset, struct sp_crtc *crtc, int x, int y);
+
+#endif /* __MODESET_H_INCLUDED__ */
diff --git a/tests/planetest/planetest.c b/tests/planetest/planetest.c
new file mode 100644
index 0000000..5e187c9
--- /dev/null
+++ b/tests/planetest/planetest.c
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+
+#include <xf86drm.h>
+
+#include "dev.h"
+#include "bo.h"
+#include "modeset.h"
+
+static int terminate = 0;
+
+static void sigint_handler(int arg)
+{
+	terminate = 1;
+}
+
+static void incrementor(int *inc, int *val, int increment, int lower, int upper)
+{
+	if(*inc > 0)
+		*inc = *val + increment >= upper ? -1 : 1;
+	else
+		*inc = *val - increment <= lower ? 1 : -1;
+	*val += *inc * increment;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret, i, j, num_test_planes;
+	int x_inc = 1, x = 0, y_inc = 1, y = 0;
+	uint32_t plane_w = 128, plane_h = 128;
+	struct sp_dev *dev;
+	struct sp_plane **plane = NULL;
+	struct sp_crtc *test_crtc;
+	int card = 0, crtc = 0;
+
+	signal(SIGINT, sigint_handler);
+
+	parse_arguments(argc, argv, &card, &crtc);
+
+	dev = create_sp_dev(card);
+	if (!dev) {
+		printf("Failed to create sp_dev\n");
+		return -1;
+	}
+
+	if (crtc >= dev->num_crtcs) {
+		printf("Invalid crtc %d (num=%d)\n", crtc, dev->num_crtcs);
+		return -1;
+	}
+
+	ret = initialize_screens(dev);
+	if (ret) {
+		printf("Failed to initialize screens\n");
+		goto out;
+	}
+	test_crtc = &dev->crtcs[crtc];
+
+	plane = calloc(dev->num_planes, sizeof(*plane));
+	if (!plane) {
+		printf("Failed to allocate plane array\n");
+		goto out;
+	}
+
+	/* Create our planes */
+	num_test_planes = test_crtc->num_planes;
+	for (i = 0; i < num_test_planes; i++) {
+		plane[i] = get_sp_plane(dev, test_crtc);
+		if (!plane[i]) {
+			printf("no unused planes available\n");
+			goto out;
+		}
+
+		plane[i]->bo = create_sp_bo(dev, plane_w, plane_h, 16,
+				plane[i]->format, 0);
+		if (!plane[i]->bo) {
+			printf("failed to create plane bo\n");
+			goto out;
+		}
+
+		fill_bo(plane[i]->bo, 0xFF, 0xFF, 0xFF, 0xFF);
+	}
+
+	while (!terminate) {
+		incrementor(&x_inc, &x, 5, 0,
+			test_crtc->crtc->mode.hdisplay - plane_w);
+		incrementor(&y_inc, &y, 5, 0, test_crtc->crtc->mode.vdisplay -
+						plane_h * num_test_planes);
+
+		for (j = 0; j < num_test_planes; j++) {
+			ret = set_sp_plane(dev, plane[j], test_crtc,
+					x, y + j * plane_h);
+			if (ret) {
+				printf("failed to set plane %d %d\n", j, ret);
+				goto out;
+			}
+		}
+		usleep(15 * 1000);
+	}
+
+	for (i = 0; i < num_test_planes; i++)
+		put_sp_plane(plane[i]);
+
+out:
+	destroy_sp_dev(dev);
+	free(plane);
+	return ret;
+}
