diff --git a/tests/testdisplay.c b/tests/testdisplay.c
index c52bb2f..e179c83 100644
--- a/tests/testdisplay.c
+++ b/tests/testdisplay.c
@@ -52,6 +52,7 @@
 #include <errno.h>
 #include <math.h>
 #include <stdint.h>
+#include <strings.h>
 #include <unistd.h>
 #include <sys/poll.h>
 #include <sys/time.h>
@@ -68,7 +69,7 @@
 drmModeRes *resources;
 int drm_fd, modes;
 int dump_info = 0, test_all_modes =0, test_preferred_mode = 0, force_mode = 0,
-	test_plane, enable_tiling;
+	test_plane, test_3d_modes, enable_tiling;
 int sleep_between_modes = 5;
 uint32_t depth = 24, stride, bpp;
 int qr_code = 0;
@@ -153,8 +154,51 @@ struct connector {
 	drmModeConnector *connector;
 	int crtc;
 	int pipe;
+
+	/* stereo 3d */
+	int s3d_format;
+	char s3d_image[32];
 };
 
+static bool connector_expose_3d(uint32_t connector_id, bool enable)
+{
+	drmModeConnector *connector;
+	drmModePropertyRes *property;
+	bool status = false;
+	int i;
+
+	connector = drmModeGetConnector(drm_fd, connector_id);
+	if (connector->count_props == 0)
+		return false;
+
+	for (i = 0; i < connector->count_props; i++) {
+		property = drmModeGetProperty(drm_fd, connector->props[i]);
+		if (!property)
+			continue;
+
+		if (strcmp(property->name, "expose 3D modes") == 0) {
+			if (drmModeConnectorSetProperty(drm_fd,
+							connector_id,
+							property->prop_id,
+							enable))
+				fprintf(stderr, "failed to set the \"expose 3D "
+					"modes\" property on connector %d: %s\n",
+					connector_id, strerror(errno));
+			else
+				status = true;
+			drmModeFreeProperty(property);
+			goto out;
+		}
+
+		drmModeFreeProperty(property);
+		property = NULL;
+	}
+
+out:
+	drmModeFreeConnector(connector);
+	return status;
+}
+
 static void dump_connectors_fd(int drmfd)
 {
 	int i, j;
@@ -172,11 +216,13 @@ static void dump_connectors_fd(int drmfd)
 	for (i = 0; i < mode_resources->count_connectors; i++) {
 		drmModeConnector *connector;
 
+		connector_expose_3d(mode_resources->connectors[i], TRUE);
+
 		connector = drmModeGetConnector(drmfd, mode_resources->connectors[i]);
 		if (!connector) {
 			fprintf(stderr, "could not get connector %i: %s\n",
 				mode_resources->connectors[i], strerror(errno));
-			continue;
+			goto next;
 		}
 
 		printf("%d\t%d\t%s\t%s\t%dx%d\t\t%d\n",
@@ -188,7 +234,7 @@ static void dump_connectors_fd(int drmfd)
 		       connector->count_modes);
 
 		if (!connector->count_modes)
-			continue;
+			goto next;
 
 		printf("  modes:\n");
 		printf("  name refresh (Hz) hdisp hss hse htot vdisp "
@@ -197,6 +243,9 @@ static void dump_connectors_fd(int drmfd)
 			kmstest_dump_mode(&connector->modes[j]);
 
 		drmModeFreeConnector(connector);
+
+next:
+		connector_expose_3d(mode_resources->connectors[i], FALSE);
 	}
 	printf("\n");
 
@@ -554,6 +603,154 @@ set_mode(struct connector *c)
 	drmModeFreeConnector(c->connector);
 }
 
+static void
+paint_3d_image(cairo_t *cr, int l_width, int l_height, void *priv)
+{
+	struct connector *c = priv;
+	cairo_surface_t *image;
+
+	image = cairo_image_surface_create_from_png(c->s3d_image);
+
+	cairo_set_source_surface(cr, image, 0, 0);
+	cairo_paint(cr);
+
+	cairo_surface_destroy(image);
+}
+
+static void adjust_3d_timings(drmModeModeInfo *mode, unsigned int format)
+{
+	uint16_t vdisplay, vactive_space;
+
+	/* just set the 3D format we are setting (this is not used by the
+	 * kernel, it's just for kmstest_dump_mode()) */
+	mode->flags &= ~DRM_MODE_FLAG_3D_MASK;
+	mode->flags |= format;
+
+	switch (format) {
+	case DRM_MODE_FLAG_3D_TOP_BOTTOM:
+	case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+		return;
+	case DRM_MODE_FLAG_3D_FRAME_PACKING:
+		vactive_space = mode->vtotal - mode->vdisplay;
+		vdisplay = mode->vdisplay;
+
+		mode->vdisplay += vdisplay + vactive_space;
+		mode->vsync_start += vdisplay + vactive_space;
+		mode->vsync_end += vdisplay + vactive_space;
+		mode->vtotal += vdisplay + vactive_space;
+		mode->clock = (mode->vtotal * mode->htotal * mode->vrefresh) /
+			      1000;
+		return;
+	default:
+		assert(0);
+	}
+}
+
+static const char *s3d_format_str(unsigned int format)
+{
+	switch(format) {
+	case DRM_MODE_FLAG_3D_TOP_BOTTOM:
+		return "TB";
+	case DRM_MODE_FLAG_3D_FRAME_PACKING:
+		return "FP";
+	}
+
+	return "Unknown format";
+}
+
+static void do_set_3d_format(struct connector *c, unsigned int format)
+{
+	uint32_t fb_id;
+	struct kmstest_fb fb_info;
+
+	snprintf(c->s3d_image, sizeof(c->s3d_image), "%d%s.png",
+		 c->mode.vdisplay, s3d_format_str(format));
+
+	adjust_3d_timings(&c->mode, format);
+	width = c->mode.hdisplay;
+	height = c->mode.vdisplay;
+
+	fb_id = kmstest_create_fb(drm_fd, width, height, bpp, depth,
+				  enable_tiling, &fb_info,
+				  paint_3d_image, c);
+
+	fb_ptr = gem_mmap(drm_fd, fb_info.gem_handle,
+			  fb_info.size, PROT_READ | PROT_WRITE);
+	assert(fb_ptr);
+
+	gem_close(drm_fd, fb_info.gem_handle);
+
+	kmstest_dump_mode(&c->mode);
+
+	if (drmModeSetCrtc(drm_fd, c->crtc, fb_id, 0, 0,
+			   &c->id, 1, &c->mode)) {
+		fprintf(stderr, "failed to set mode (%dx%d@%dHz): %s\n",
+			width, height, c->mode.vrefresh,
+			strerror(errno));
+	}
+}
+
+static void
+set_3d_modes(struct connector *c)
+{
+	int i;
+
+	if (depth <= 8)
+		bpp = 8;
+	else if (depth > 8 && depth <= 16)
+		bpp = 16;
+	else if (depth > 16 && depth <= 32)
+		bpp = 32;
+
+	connector_find_preferred_mode(c);
+	if (!c->mode_valid)
+		return;
+
+	for (i = 0; i < c->connector->count_modes; i++) {
+		unsigned int s3d_formats, format;
+
+		c->mode = c->connector->modes[i];
+
+		if (!c->mode_valid)
+			continue;
+
+		if (c->mode.flags & DRM_MODE_FLAG_INTERLACE)
+			continue;
+
+		s3d_formats = c->mode.flags & DRM_MODE_FLAG_3D_MASK;
+		if (!s3d_formats)
+			continue;
+
+		do {
+			format = 1 << (ffs(s3d_formats) - 1);
+
+			/* Modify the mode flags to specify which 3D format is
+			 * being set.
+			 *
+			 * XXX: One would need to also clear the upper bits of
+			 * flags in case extra modes/flags are added
+			 */
+			c->mode.flags &= ~DRM_MODE_FLAG_3D_MASK;
+			c->mode.flags |= format;
+
+			do_set_3d_format(c, format);
+
+			if (qr_code){
+				set_single();
+				pause();
+			} else if (sleep_between_modes) {
+				sleep(sleep_between_modes);
+			}
+
+			s3d_formats &= ~(format);
+		} while (s3d_formats);
+
+	}
+
+	drmModeFreeEncoder(c->encoder);
+	drmModeFreeConnector(c->connector);
+}
+
 /*
  * Re-probe outputs and light up as many as possible.
  *
@@ -592,11 +789,26 @@ int update_display(void)
 			set_mode(&connectors[c]);
 		}
 	}
+
+	if (test_all_modes || test_3d_modes) {
+		/* Find connectors that can expose 3D modes */
+		for (c = 0; c < resources->count_connectors; c++) {
+			connectors[c].id = resources->connectors[c];
+
+			if (!connector_expose_3d(connectors[c].id, TRUE))
+				continue;
+
+			set_3d_mods(&connectors[c]);
+
+			connector_expose_3d(connectors[c].id, FALSE);
+		}
+	}
+
 	drmModeFreeResources(resources);
 	return 1;
 }
 
-static char optstr[] = "hiaf:s:d:p:mrt";
+static char optstr[] = "3hiaf:s:d:p:mrt";
 
 static void __attribute__((noreturn)) usage(char *name)
 {
@@ -607,6 +819,7 @@ static void __attribute__((noreturn)) usage(char *name)
 	fprintf(stderr, "\t-d\t<depth>\tbit depth of scanout buffer\n");
 	fprintf(stderr, "\t-p\t<planew,h>,<crtcx,y>,<crtcw,h> test overlay plane\n");
 	fprintf(stderr, "\t-m\ttest the preferred mode\n");
+	fprintf(stderr, "\t-3\ttest all 3D modes\n");
 	fprintf(stderr, "\t-t\tuse a tiled framebuffer\n");
 	fprintf(stderr, "\t-r\tprint a QR code on the screen whose content is \"pass\" for the automatic test\n");
 	fprintf(stderr, "\t-f\t<clock MHz>,<hdisp>,<hsync-start>,<hsync-end>,<htotal>,\n");
@@ -663,6 +876,9 @@ int main(int argc, char **argv)
 	opterr = 0;
 	while ((c = getopt(argc, argv, optstr)) != -1) {
 		switch (c) {
+		case '3':
+			test_3d_modes = 1;
+			break;
 		case 'i':
 			dump_info = 1;
 			break;
@@ -710,7 +926,7 @@ int main(int argc, char **argv)
 		}
 	}
 	if (!test_all_modes && !force_mode && !dump_info &&
-	    !test_preferred_mode)
+	    !test_preferred_mode && !test_3d_modes)
 		test_all_modes = 1;
 
 	drm_fd = drm_open_any();
