diff --git a/tests/.gitignore b/tests/.gitignore
index 7f20f2b..6fc4782 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -130,6 +130,7 @@ gen7_forcewake_mt
 kms_3d
 kms_addfb_basic
 kms_atomic
+kms_color
 kms_crtc_background_color
 kms_cursor_crc
 kms_draw_crc
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index d594038..f2af648 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -68,6 +68,7 @@ TESTS_progs_M = \
 	gem_write_read_ring_switch \
 	kms_addfb_basic \
 	kms_atomic \
+	kms_color \
 	kms_cursor_crc \
 	kms_draw_crc \
 	kms_fbc_crc \
diff --git a/tests/kms_color.c b/tests/kms_color.c
new file mode 100644
index 0000000..f1492a2
--- /dev/null
+++ b/tests/kms_color.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright © 2015 Intel Corporation
+ *
+  * 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 (including the next
+ * paragraph) 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 <math.h>
+#include "drmtest.h"
+#include "drm.h"
+#include "igt_debugfs.h"
+#include "igt_kms.h"
+#include "igt_core.h"
+#include "intel_io.h"
+#include "intel_chipset.h"
+#include "igt_aux.h"
+#include<unistd.h>
+#include<stdlib.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+
+
+IGT_TEST_DESCRIPTION("Test Color Features at Pipe level");
+
+/* Data structures for gamma/degamma ramps & ctm matrix. */
+struct _drm_r32g32b32 {
+	__u32 r32;
+	__u32 g32;
+	__u32 b32;
+	__u32 reserved;
+};
+
+struct _drm_palette {
+	struct _drm_r32g32b32 lut[0];
+};
+
+struct _drm_ctm {
+	__s64 ctm_coeff[9];
+};
+
+/* Internal */
+typedef struct {
+	float r, g, b;
+} color_t;
+
+typedef struct {
+	int drm_fd;
+	igt_display_t display;
+
+	uint32_t color_depth;
+	uint64_t coeffs_after_ctm;
+	uint64_t coeffs_before_ctm;
+} data_t;
+
+
+static float ctm_identity[] = {
+	1.0, 0.0, 0.0,
+	0.0, 1.0, 0.0,
+	0.0, 0.0, 1.0
+};
+
+static void paint_gradient_rectangles(data_t *data,
+				      drmModeModeInfo *mode,
+				      color_t *colors,
+				      struct igt_fb *fb)
+{
+	cairo_t *cr = igt_get_cairo_ctx(data->drm_fd, fb);
+	int i, l = mode->hdisplay / 3;
+
+	/* Paint 3 gradient rectangles with red/green/blue between 1.0
+	 * and 0.5. We want to avoid 0 so each max LUTs only affect
+	 * their own rectangle.
+	 */
+	for (i = 0 ; i < 3; i++) {
+		igt_paint_color_gradient_range(cr, i * l, 0, l, mode->vdisplay,
+					       colors[i].r != 0 ? 0.5 : 0,
+					       colors[i].g != 0 ? 0.5 : 0,
+					       colors[i].b != 0 ? 0.5 : 0,
+					       colors[i].r,
+					       colors[i].g,
+					       colors[i].b);
+	}
+
+	cairo_destroy(cr);
+}
+
+static void paint_rectangles(data_t *data,
+			     drmModeModeInfo *mode,
+			     color_t *colors,
+			     struct igt_fb *fb)
+{
+	cairo_t *cr = igt_get_cairo_ctx(data->drm_fd, fb);
+	int i, l = mode->hdisplay / 3;
+
+	/* Paint 3 solid rectangles. */
+	for (i = 0 ; i < 3; i++)
+		igt_paint_color(cr, i * l, 0, l, mode->vdisplay,
+				colors[i].r, colors[i].g, colors[i].b);
+
+	cairo_destroy(cr);
+}
+
+static float *generate_table(uint32_t lut_size, float exp)
+{
+	float *coeffs = malloc(sizeof(float) * lut_size);
+	uint32_t i;
+
+	for (i = 0; i < lut_size; i++)
+		coeffs[i] = powf((float) i * 1.0 / (float) lut_size, exp);
+
+	return coeffs;
+}
+
+static float *generate_table_max(uint32_t lut_size)
+{
+	float *coeffs = malloc(sizeof(float) * lut_size);
+	uint32_t i;
+
+	coeffs[0] = 0.0;
+	for (i = 1; i < lut_size; i++)
+		coeffs[i] = 1.0;
+
+	return coeffs;
+}
+
+static struct _drm_palette *coeffs_to_palette(const float *coefficients,
+					      uint32_t lut_size,
+					      uint32_t color_depth,
+					      size_t *size)
+{
+	struct _drm_palette *palette;
+	uint32_t i;
+	uint32_t max_value_mask = ((1 << (color_depth + 1)) - 1) << 16;
+
+	*size = sizeof(struct _drm_r32g32b32) * lut_size;
+	palette = malloc(*size);
+
+	for (i = 0; i < lut_size; i++) {
+		/*
+		 * Hardware might encode colors at a different number
+		 * of bits that what is in our framebuffer (10 or
+		 * 12bits for example). Map the max value to the max
+		 * value of the framebuffer so we can do CRC
+		 * comparisons.
+		 */
+		uint32_t v = coefficients[i] * max_value_mask;
+
+		palette->lut[i].r32 = v;
+		palette->lut[i].g32 = v;
+		palette->lut[i].b32 = v;
+	}
+
+	return palette;
+}
+
+static void set_degamma(igt_pipe_t *pipe,
+			const float *coefficients,
+			uint32_t lut_size,
+			uint32_t color_depth)
+{
+	size_t size;
+	struct _drm_palette *palette = coeffs_to_palette(coefficients, lut_size,
+							 color_depth, &size);
+
+	igt_assert_eq(igt_pipe_set_blob_property(pipe, "PALETTE_BEFORE_CTM",
+						 palette, size), 0);
+
+	free(palette);
+}
+
+static void set_gamma(igt_pipe_t *pipe,
+		      const float *coefficients,
+		      uint32_t lut_size,
+		      uint32_t color_depth)
+{
+	size_t size;
+	struct _drm_palette *palette = coeffs_to_palette(coefficients, lut_size,
+							 color_depth, &size);
+
+	igt_assert_eq(igt_pipe_set_blob_property(pipe, "PALETTE_AFTER_CTM",
+						 palette, size), 0);
+
+	free(palette);
+}
+
+static void set_ctm(igt_pipe_t *pipe, const float *coefficients)
+{
+	struct _drm_ctm ctm;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctm.ctm_coeff); i++) {
+		ctm.ctm_coeff[i] =
+			(int64_t) (coefficients[i] * ((int64_t) 1L << 32));
+	}
+
+	igt_assert_eq(igt_pipe_set_blob_property(pipe, "CTM",
+						 &ctm, sizeof(ctm)), 0);
+}
+
+/*
+ * Draw 3 gradient rectangles in red, green and blue, with a maxed out
+ * degamma LUT and verify we have the same CRC as drawing solid color
+ * rectangles with linear degamma LUT.
+ */
+static void test_pipe_degamma(data_t *data,
+			      igt_plane_t *primary)
+{
+	igt_output_t *output;
+	igt_pipe_crc_t *pipe_crc;
+	float *degamma_linear, *degamma_full;
+	float *gamma_linear;
+	color_t red_green_blue[] = {
+		{ 1.0, 0.0, 0.0 },
+		{ 0.0, 1.0, 0.0 },
+		{ 0.0, 0.0, 1.0 }
+	};
+
+	pipe_crc = igt_pipe_crc_new(primary->pipe->pipe,
+				    INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	degamma_linear = generate_table(data->coeffs_before_ctm, 1.0);
+	degamma_full = generate_table_max(data->coeffs_before_ctm);
+
+	gamma_linear = generate_table(data->coeffs_after_ctm, 1.0);
+
+	for_each_connected_output(&data->display, output) {
+		drmModeModeInfo *mode;
+		struct igt_fb fb_modeset, fb;
+		igt_crc_t crc_fullgamma, crc_fullcolors;
+		int fb_id, fb_modeset_id;
+
+		igt_output_set_pipe(output, primary->pipe->pipe);
+		mode = igt_output_get_mode(output);
+
+		/* Create a framebuffer at the size of the output. */
+		fb_id = igt_create_fb(data->drm_fd,
+				      mode->hdisplay,
+				      mode->hdisplay,
+				      DRM_FORMAT_XRGB8888,
+				      LOCAL_DRM_FORMAT_MOD_NONE,
+				      &fb);
+		igt_assert(fb_id);
+
+		fb_modeset_id = igt_create_fb(data->drm_fd,
+					      mode->hdisplay,
+					      mode->hdisplay,
+					      DRM_FORMAT_XRGB8888,
+					      LOCAL_DRM_FORMAT_MOD_NONE,
+					      &fb_modeset);
+		igt_assert(fb_modeset_id);
+
+		igt_plane_set_fb(primary, &fb_modeset);
+		set_ctm(primary->pipe, ctm_identity);
+		set_gamma(primary->pipe, gamma_linear,
+			  data->coeffs_after_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+
+		/* Draw solid colors with no degamma transformation. */
+		paint_rectangles(data, mode, red_green_blue, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_degamma(primary->pipe, degamma_linear,
+			    data->coeffs_before_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_fullcolors);
+
+		/* Draw a gradient with degamma LUT to remap all
+		 * values to max red/green/blue.
+		 */
+		paint_gradient_rectangles(data, mode, red_green_blue, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_degamma(primary->pipe, degamma_full,
+			    data->coeffs_before_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_fullgamma);
+
+		/* Verify that the CRC of the software computed output
+		 * is equal to the CRC of the degamma LUT
+		 * transformation output.
+		 */
+		igt_assert_crc_equal(&crc_fullgamma, &crc_fullcolors);
+
+		/* Reset output. */
+		igt_output_set_pipe(output, PIPE_ANY);
+		igt_display_commit(&data->display);
+	}
+
+	/* Reset the degamma ramp. */
+	set_degamma(primary->pipe, degamma_linear,
+		    data->coeffs_before_ctm, data->color_depth);
+	igt_display_commit(&data->display);
+
+	igt_pipe_crc_free(pipe_crc);
+
+	free(degamma_linear);
+	free(degamma_full);
+	free(gamma_linear);
+}
+
+/*
+ * Draw 3 gradient rectangles in red, green and blue, with a maxed out
+ * gamma LUT and verify we have the same CRC as drawing solid color
+ * rectangles with linear gamma LUT.
+ */
+static void test_pipe_gamma(data_t *data,
+			    igt_plane_t *primary)
+{
+	igt_output_t *output;
+	igt_pipe_crc_t *pipe_crc;
+	float *degamma_linear;
+	float *gamma_linear, *gamma_full;
+	color_t red_green_blue[] = {
+		{ 1.0, 0.0, 0.0 },
+		{ 0.0, 1.0, 0.0 },
+		{ 0.0, 0.0, 1.0 }
+	};
+
+	pipe_crc = igt_pipe_crc_new(primary->pipe->pipe,
+				    INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	degamma_linear = generate_table(data->coeffs_before_ctm, 1.0);
+
+	gamma_linear = generate_table(data->coeffs_after_ctm, 1.0);
+	gamma_full = generate_table_max(data->coeffs_after_ctm);
+
+	for_each_connected_output(&data->display, output) {
+		drmModeModeInfo *mode;
+		struct igt_fb fb_modeset, fb;
+		igt_crc_t crc_fullgamma, crc_fullcolors;
+		int fb_id, fb_modeset_id;
+
+		igt_output_set_pipe(output, primary->pipe->pipe);
+		mode = igt_output_get_mode(output);
+
+		/* Create a framebuffer at the size of the output. */
+		fb_id = igt_create_fb(data->drm_fd,
+				      mode->hdisplay,
+				      mode->hdisplay,
+				      DRM_FORMAT_XRGB8888,
+				      LOCAL_DRM_FORMAT_MOD_NONE,
+				      &fb);
+		igt_assert(fb_id);
+
+		fb_modeset_id = igt_create_fb(data->drm_fd,
+					      mode->hdisplay,
+					      mode->hdisplay,
+					      DRM_FORMAT_XRGB8888,
+					      LOCAL_DRM_FORMAT_MOD_NONE,
+					      &fb_modeset);
+		igt_assert(fb_modeset_id);
+
+		igt_plane_set_fb(primary, &fb_modeset);
+		set_ctm(primary->pipe, ctm_identity);
+		set_degamma(primary->pipe, degamma_linear,
+			    data->coeffs_before_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+
+		/* Draw solid colors with no gamma transformation. */
+		paint_rectangles(data, mode, red_green_blue, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_gamma(primary->pipe, gamma_linear,
+			  data->coeffs_after_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_fullcolors);
+
+		/* Draw a gradient with gamma LUT to remap all values
+		 * to max red/green/blue.
+		 */
+		paint_gradient_rectangles(data, mode, red_green_blue, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_gamma(primary->pipe, gamma_full,
+			  data->coeffs_after_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_fullgamma);
+
+		/* Verify that the CRC of the software computed output
+		 * is equal to the CRC of the gamma LUT
+		 * transformation output.
+		 */
+		igt_assert_crc_equal(&crc_fullgamma, &crc_fullcolors);
+
+		/* Reset output. */
+		igt_output_set_pipe(output, PIPE_ANY);
+		igt_display_commit(&data->display);
+	}
+
+	/* Reset the gamma ramp. */
+	set_gamma(primary->pipe, gamma_linear,
+		  data->coeffs_before_ctm, data->color_depth);
+	igt_display_commit(&data->display);
+
+	igt_pipe_crc_free(pipe_crc);
+
+	free(degamma_linear);
+	free(gamma_full);
+	free(gamma_linear);
+}
+
+/*
+ * Draw 3 rectangles using before colors, apply the ctm matrix and
+ * verify the CRC is equal to using after colors with an identify ctm
+ * matrix.
+ */
+static void test_pipe_ctm(data_t *data,
+			  igt_plane_t *primary,
+			  color_t *before,
+			  color_t *after,
+			  float *ctm_matrix)
+{
+	igt_output_t *output;
+	igt_pipe_crc_t *pipe_crc;
+	float *degamma_linear, *gamma_linear;
+
+	pipe_crc = igt_pipe_crc_new(primary->pipe->pipe,
+				    INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	degamma_linear = generate_table(data->coeffs_before_ctm, 1.0);
+	gamma_linear = generate_table(data->coeffs_after_ctm, 1.0);
+
+	for_each_connected_output(&data->display, output) {
+		drmModeModeInfo *mode;
+		struct igt_fb fb_modeset, fb;
+		igt_crc_t crc_software, crc_hardware;
+		int fb_id, fb_modeset_id;
+
+		igt_output_set_pipe(output, primary->pipe->pipe);
+		mode = igt_output_get_mode(output);
+
+
+		/* Create a framebuffer at the size of the output. */
+		fb_id = igt_create_fb(data->drm_fd,
+				      mode->hdisplay,
+				      mode->hdisplay,
+				      DRM_FORMAT_XRGB8888,
+				      LOCAL_DRM_FORMAT_MOD_NONE,
+				      &fb);
+		igt_assert(fb_id);
+
+		fb_modeset_id = igt_create_fb(data->drm_fd,
+					      mode->hdisplay,
+					      mode->hdisplay,
+					      DRM_FORMAT_XRGB8888,
+					      LOCAL_DRM_FORMAT_MOD_NONE,
+					      &fb_modeset);
+		igt_assert(fb_modeset_id);
+
+		igt_plane_set_fb(primary, &fb_modeset);
+		set_degamma(primary->pipe, degamma_linear,
+			    data->coeffs_before_ctm, data->color_depth);
+		set_gamma(primary->pipe, gamma_linear,
+			  data->coeffs_after_ctm, data->color_depth);
+		igt_display_commit(&data->display);
+
+		/* No CTM transformation. */
+		paint_rectangles(data, mode, after, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_ctm(primary->pipe, ctm_identity);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_software);
+
+		/* With CTM transformation. */
+		paint_rectangles(data, mode, before, &fb);
+		igt_plane_set_fb(primary, &fb);
+		set_ctm(primary->pipe, ctm_matrix);
+		igt_display_commit(&data->display);
+		igt_wait_for_vblank(data->drm_fd, primary->pipe->pipe);
+		igt_pipe_crc_collect_crc(pipe_crc, &crc_hardware);
+
+		/* Verify that the CRC of the software computed output
+		 * is equal to the CRC of the CTM matrix
+		 * transformation output.
+		 */
+		igt_assert_crc_equal(&crc_software, &crc_hardware);
+
+		/* Reset output. */
+		igt_output_set_pipe(output, PIPE_ANY);
+		igt_display_commit(&data->display);
+	}
+
+	/* Reset the CTM matrix. */
+	set_ctm(primary->pipe, ctm_identity);
+	igt_display_commit(&data->display);
+
+	igt_pipe_crc_free(pipe_crc);
+
+	free(degamma_linear);
+	free(gamma_linear);
+}
+
+static void
+run_tests_for_pipe(data_t *data, enum pipe p)
+{
+	igt_pipe_t *pipe;
+	igt_plane_t *primary;
+	color_t red_green_blue[] = {
+		{ 1.0, 0.0, 0.0 },
+		{ 0.0, 1.0, 0.0 },
+		{ 0.0, 0.0, 1.0 }
+	};
+
+	if (p >= data->display.n_pipes)
+		return;
+
+	pipe = &data->display.pipes[p];
+	if (pipe->n_planes < IGT_PLANE_PRIMARY)
+		return;
+
+	primary = &pipe->planes[IGT_PLANE_PRIMARY];
+
+	igt_subtest_f("csc-red-to-blue-pipe%d", p) {
+		color_t blue_green_blue[] = {
+			{ 0.0, 0.0, 1.0 },
+			{ 0.0, 1.0, 0.0 },
+			{ 0.0, 0.0, 1.0 }
+		};
+		float ctm[] = { 0.0, 0.0, 0.0,
+				0.0, 1.0, 0.0,
+				1.0, 0.0, 1.0 };
+		test_pipe_ctm(data, primary, red_green_blue,
+			      blue_green_blue, ctm);
+	}
+
+	igt_subtest_f("csc-green-to-red-pipe%d", p) {
+		color_t red_red_blue[] = {
+			{ 1.0, 0.0, 0.0 },
+			{ 1.0, 0.0, 0.0 },
+			{ 0.0, 0.0, 1.0 }
+		};
+		float ctm[] = { 1.0, 1.0, 0.0,
+				0.0, 0.0, 0.0,
+				0.0, 0.0, 1.0 };
+		test_pipe_ctm(data, primary, red_green_blue,
+			      red_red_blue, ctm);
+	}
+
+	igt_subtest_f("csc-blue-to-red-pipe%d", p) {
+		color_t red_green_red[] = {
+			{ 1.0, 0.0, 0.0 },
+			{ 0.0, 1.0, 0.0 },
+			{ 1.0, 0.0, 0.0 }
+		};
+		float ctm[] = { 1.0, 0.0, 1.0,
+				0.0, 1.0, 0.0,
+				0.0, 0.0, 0.0 };
+		test_pipe_ctm(data, primary, red_green_blue,
+			      red_green_red, ctm);
+	}
+
+	igt_subtest_f("degamma%d", p)
+		test_pipe_degamma(data, primary);
+
+	igt_subtest_f("gamma%d", p)
+		test_pipe_gamma(data, primary);
+}
+
+igt_main
+{
+	data_t data = {};
+
+	igt_skip_on_simulation();
+	igt_fixture {
+		igt_require_pipe_crc();
+
+		data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
+		kmstest_set_vt_graphics_mode();
+
+		igt_display_init(&data.display, data.drm_fd);
+
+		igt_require(igt_pipe_get_property(&data.display.pipes[0],
+						  "COEFFICIENTS_AFTER_CTM",
+						  NULL,
+						  &data.coeffs_after_ctm,
+						  NULL));
+		igt_require(igt_pipe_get_property(&data.display.pipes[0],
+						  "COEFFICIENTS_BEFORE_CTM",
+						  NULL,
+						  &data.coeffs_before_ctm,
+						  NULL));
+
+		/* We assume an 8bits depth per color framebuffer. */
+		data.color_depth = 8;
+	}
+
+	for (int pipe = 0; pipe < I915_MAX_PIPES; pipe++)
+		run_tests_for_pipe(&data, pipe);
+
+	igt_fixture {
+		igt_display_fini(&data.display);
+	}
+}
