diff --git a/tests/modetest/Makefile.am b/tests/modetest/Makefile.am
index 6e7ff14..fd6ebb2 100644
--- a/tests/modetest/Makefile.am
+++ b/tests/modetest/Makefile.am
@@ -14,7 +14,9 @@ noinst_PROGRAMS = \
 endif
 
 modetest_SOURCES = \
-	buffers.c modetest.c buffers.h
+	buffers.c buffers.h \
+	cursor.c cursor.h \
+	modetest.c
 
 modetest_LDADD = \
 	$(top_builddir)/libdrm.la \
diff --git a/tests/modetest/cursor.c b/tests/modetest/cursor.c
new file mode 100644
index 0000000..7077f20
--- /dev/null
+++ b/tests/modetest/cursor.c
@@ -0,0 +1,207 @@
+/*
+ * DRM based mode setting test program
+ * Copyright (C) 2013 Red Hat
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/time.h>
+
+#include "xf86drm.h"
+#include "xf86drmMode.h"
+
+#include "buffers.h"
+#include "cursor.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+struct cursor {
+	int fd;
+	uint32_t bo_handle;
+	uint32_t crtc_id;
+	uint32_t crtc_w, crtc_h;
+	uint32_t w, h;
+
+	/* current state */
+	uint32_t enabled, x, y;
+	int32_t dx, dy;
+};
+
+#define MAX_CURSORS 8
+static struct cursor cursors[MAX_CURSORS];
+static int ncursors;
+
+/*
+ * Timer driven program loops through these steps to move/enable/disable
+ * the cursor
+ */
+
+struct cursor_step {
+	void (*run)(struct cursor *cursor, struct cursor_step *step);
+	uint32_t msec;
+	uint32_t repeat;
+	int arg;
+};
+
+static uint32_t indx, count;
+
+static void set_cursor(struct cursor *cursor, struct cursor_step *step)
+{
+	int enabled = (step->arg ^ count) & 0x1;
+	uint32_t handle = 0;
+
+	if (enabled)
+		handle = cursor->bo_handle;
+
+	cursor->enabled = enabled;
+
+	drmModeSetCursor(cursor->fd, cursor->crtc_id, handle, cursor->w, cursor->h);
+}
+
+static void move_cursor(struct cursor *cursor, struct cursor_step *step)
+{
+	int x = cursor->x;
+	int y = cursor->y;
+
+	if (!cursor->enabled)
+		drmModeSetCursor(cursor->fd, cursor->crtc_id,
+				cursor->bo_handle, cursor->w, cursor->h);
+
+	/* calculate new cursor position: */
+	x += cursor->dx * step->arg;
+	y += cursor->dy * step->arg;
+
+	if (x < 0) {
+		x = 0;
+		cursor->dx = 1;
+	} else if (x > (int)cursor->crtc_w) {
+		x = cursor->crtc_w - 1;
+		cursor->dx = -1;
+	}
+
+	if (y < 0) {
+		y = 0;
+		cursor->dy = 1;
+	} else if (y > (int)cursor->crtc_h) {
+		y = cursor->crtc_h - 1;
+		cursor->dy = -1;
+	}
+
+	cursor->x = x;
+	cursor->y = y;
+
+	drmModeMoveCursor(cursor->fd, cursor->crtc_id, x, y);
+}
+
+static struct cursor_step steps[] = {
+		{  set_cursor, 10,   0,  1 },  /* enable */
+		{ move_cursor,  1, 100,  1 },
+		{ move_cursor,  1,  10, 10 },
+		{  set_cursor,  1, 100,  0 },  /* disable/enable loop */
+		{ move_cursor,  1,  10, 10 },
+		{ move_cursor,  9, 100,  1 },
+		{ move_cursor, 11, 100,  5 },
+		{  set_cursor, 17,  10,  0 },  /* disable/enable loop */
+		{ move_cursor,  9, 100,  1 },
+		{  set_cursor, 13,  10,  0 },  /* disable/enable loop */
+		{ move_cursor,  9, 100,  1 },
+		{  set_cursor, 13,  10,  0 },  /* disable/enable loop */
+		{  set_cursor, 10,   0,  0 },  /* disable */
+};
+
+/*
+ * Cursor API:
+ */
+
+static void run_step(int sig)
+{
+	struct cursor_step *step = &steps[indx % ARRAY_SIZE(steps)];
+	struct itimerval itimer = {
+			.it_value.tv_usec = 1000 * step->msec,
+	};
+	int i;
+
+	for (i = 0; i < ncursors; i++) {
+		struct cursor *cursor = &cursors[i];
+		step->run(cursor, step);
+	}
+
+	/* iterate to next count/step: */
+	if (count < step->repeat) {
+		count++;
+	} else {
+		count = 0;
+		indx++;
+	}
+
+	/* and lastly, setup timer for next step */
+	setitimer(ITIMER_REAL, &itimer, NULL);
+}
+
+int cursor_init(int fd, uint32_t bo_handle, uint32_t crtc_id,
+		uint32_t crtc_w, uint32_t crtc_h, uint32_t w, uint32_t h)
+{
+	struct cursor *cursor = &cursors[ncursors];
+
+	assert(ncursors < MAX_CURSORS);
+
+	cursor->fd = fd;
+	cursor->bo_handle = bo_handle;
+	cursor->crtc_id = crtc_id;
+	cursor->crtc_w = crtc_w;
+	cursor->crtc_h = crtc_h;
+	cursor->w = w;
+	cursor->h = h;
+
+	cursor->enabled = 0;
+	cursor->x = w/2;
+	cursor->y = h/2;
+	cursor->dx = 1;
+	cursor->dy = 1;
+
+	ncursors++;
+
+	return 0;
+}
+
+int cursor_start(void)
+{
+	/* setup signal handler to update cursor: */
+	signal(SIGALRM, run_step);
+	printf("starting cursor\n");
+	run_step(SIGALRM);
+	return 0;
+}
+
+int cursor_stop(void)
+{
+	signal(SIGALRM, NULL);
+	printf("cursor stopped\n");
+	return 0;
+}
diff --git a/tests/modetest/cursor.h b/tests/modetest/cursor.h
new file mode 100644
index 0000000..d849f6c
--- /dev/null
+++ b/tests/modetest/cursor.h
@@ -0,0 +1,33 @@
+/*
+ * DRM based mode setting test program
+ * Copyright (C) 2014 Red Hat
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __CURSOR_H__
+#define __CURSOR_H__
+
+int cursor_init(int fd, uint32_t bo_handle, uint32_t crtc_id,
+		uint32_t crtc_w, uint32_t crtc_h, uint32_t w, uint32_t h);
+int cursor_start(void);
+int cursor_stop(void);
+
+#endif
diff --git a/tests/modetest/modetest.c b/tests/modetest/modetest.c
index bc9c998..7d436b5 100644
--- a/tests/modetest/modetest.c
+++ b/tests/modetest/modetest.c
@@ -58,6 +58,7 @@
 #include "libkms.h"
 
 #include "buffers.h"
+#include "cursor.h"
 
 struct crtc {
 	drmModeCrtc *crtc;
@@ -1104,6 +1105,46 @@ static void set_planes(struct device *dev, struct plane_arg *p, unsigned int cou
 			return;
 }
 
+static void set_cursors(struct device *dev, struct pipe_arg *pipes, unsigned int count)
+{
+	uint32_t handles[4], pitches[4], offsets[4] = {0}; /* we only use [0] */
+	struct kms_bo *bo;
+	unsigned int i;
+	int ret;
+
+	/* maybe make cursor width/height configurable some day */
+	uint32_t cw = 64;
+	uint32_t ch = 64;
+
+	/* create cursor bo.. just using PATTERN_PLAIN as it has
+	 * translucent alpha
+	 */
+	bo = create_test_buffer(dev->kms, DRM_FORMAT_ARGB8888,
+			cw, ch, handles, pitches, offsets, PATTERN_PLAIN);
+	if (bo == NULL)
+		return;
+
+	for (i = 0; i < count; i++) {
+		struct pipe_arg *pipe = &pipes[i];
+		ret = cursor_init(dev->fd, handles[0],
+				pipe->crtc->crtc->crtc_id,
+				pipe->mode->hdisplay, pipe->mode->vdisplay,
+				cw, ch);
+		if (ret) {
+			fprintf(stderr, "failed to init cursor for CRTC[%u]\n",
+					pipe->crtc_id);
+			return;
+		}
+	}
+
+	cursor_start();
+}
+
+static void clear_cursors(struct device *dev)
+{
+	cursor_stop();
+}
+
 static void test_page_flip(struct device *dev, struct pipe_arg *pipes, unsigned int count)
 {
 	uint32_t handles[4], pitches[4], offsets[4] = {0}; /* we only use [0] */
@@ -1332,7 +1373,7 @@ static int parse_property(struct property_arg *p, const char *arg)
 
 static void usage(char *name)
 {
-	fprintf(stderr, "usage: %s [-cDdefMPpsvw]\n", name);
+	fprintf(stderr, "usage: %s [-cDdefMPpsCvw]\n", name);
 
 	fprintf(stderr, "\n Query options:\n\n");
 	fprintf(stderr, "\t-c\tlist connectors\n");
@@ -1343,6 +1384,7 @@ static void usage(char *name)
 	fprintf(stderr, "\n Test options:\n\n");
 	fprintf(stderr, "\t-P <crtc_id>:<w>x<h>[+<x>+<y>][*<scale>][@<format>]\tset a plane\n");
 	fprintf(stderr, "\t-s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>]\tset a mode\n");
+	fprintf(stderr, "\t-C\ttest hw cursor\n");
 	fprintf(stderr, "\t-v\ttest vsynced page flipping\n");
 	fprintf(stderr, "\t-w <obj_id>:<prop_name>:<value>\tset property\n");
 
@@ -1376,7 +1418,13 @@ static int page_flipping_supported(void)
 #endif
 }
 
-static char optstr[] = "cdD:efM:P:ps:vw:";
+static int cursor_supported(void)
+{
+	/*FIXME: generic ioctl needed? */
+	return 1;
+}
+
+static char optstr[] = "cdD:efM:P:ps:Cvw:";
 
 int main(int argc, char **argv)
 {
@@ -1386,6 +1434,7 @@ int main(int argc, char **argv)
 	int encoders = 0, connectors = 0, crtcs = 0, planes = 0, framebuffers = 0;
 	int drop_master = 0;
 	int test_vsync = 0;
+	int test_cursor = 0;
 	const char *modules[] = { "i915", "radeon", "nouveau", "vmwgfx", "omapdrm", "exynos", "tilcdc", "msm" };
 	char *device = NULL;
 	char *module = NULL;
@@ -1456,6 +1505,9 @@ int main(int argc, char **argv)
 
 			count++;				      
 			break;
+		case 'C':
+			test_cursor = 1;
+			break;
 		case 'v':
 			test_vsync = 1;
 			break;
@@ -1515,6 +1567,11 @@ int main(int argc, char **argv)
 		return -1;
 	}
 
+	if (test_cursor && !cursor_supported()) {
+		fprintf(stderr, "hw cursor not supported by drm.\n");
+		return -1;
+	}
+
 	dev.resources = get_resources(&dev);
 	if (!dev.resources) {
 		drmClose(dev.fd);
@@ -1546,16 +1603,22 @@ int main(int argc, char **argv)
 		if (plane_count)
 			set_planes(&dev, plane_args, plane_count);
 
+		if (test_cursor)
+			set_cursors(&dev, pipe_args, count);
+
 		if (test_vsync)
 			test_page_flip(&dev, pipe_args, count);
 
 		if (drop_master)
 			drmDropMaster(dev.fd);
 
+		getchar();
+
+		if (test_cursor)
+			clear_cursors(&dev);
+
 		kms_bo_destroy(&dev.mode.bo);
 		kms_destroy(&dev.kms);
-
-		getchar();
 	}
 
 	free_resources(dev.resources);
