/*
 * Copyright © 2020 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 "igt.h"

IGT_TEST_DESCRIPTION("Test flipping between scaled/nonscaled framebuffers");

typedef struct {
	int drm_fd;
	igt_display_t display;
	igt_output_t *output;
	enum pipe pipe;
	uint32_t gen;
	struct igt_fb small_fb;
	struct igt_fb big_fb;
	igt_pipe_crc_t *pipe_crc;
} data_t;

const struct {
	const char * const name;
	const char * const describe;
	const uint64_t firstmodifier;
	const uint32_t firstformat;
	const uint64_t secondmodifier;
	const uint32_t secondformat;
} flip_scenario_test[] = {
	{
		"flip-32bpp-ytile-to-64bpp-ytile",
		"Flip from 32bpp non scaled fb to 64bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB8888,
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB16161616F
	},
	{
		"flip-64bpp-ytile-to-32bpp-ytile",
		"Flip from 64bpp non scaled fb to 32bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB16161616F,
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB8888
	},
	{
		"flip-64bpp-ytile-to-16bpp-ytile",
		"Flip from 64bpp non scaled fb to 16bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB16161616F,
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_RGB565
	},
	{
		"flip-32bpp-ytileccs-to-64bpp-ytile",
		"Flip from 32bpp non scaled fb to 64bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED_CCS, DRM_FORMAT_XRGB8888,
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB16161616F
	},
	{
		"flip-32bpp-ytile-to-32bpp-ytilegen12rcccs",
		"Flip from 32bpp non scaled fb to 32bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB8888,
		LOCAL_I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS, DRM_FORMAT_XRGB8888
	},
	{
		"flip-32bpp-ytile-to-32bpp-ytileccs",
		"Flip from 32bpp non scaled fb to 32bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB8888,
		LOCAL_I915_FORMAT_MOD_Y_TILED_CCS, DRM_FORMAT_XRGB8888
	},
	{
		"flip-64bpp-ytile-to-32bpp-ytilercccs",
		"Flip from 64bpp non scaled fb to 32bpp downscaled fb to stress CD clock programming",
		LOCAL_I915_FORMAT_MOD_Y_TILED, DRM_FORMAT_XRGB16161616F,
		LOCAL_I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS, DRM_FORMAT_XRGB8888
	},
};

static void setup_fb(data_t *data, struct igt_fb *newfb, uint32_t width,
		     uint32_t height, uint64_t format, uint64_t modifier)
{
	struct drm_mode_fb_cmd2 f = {0};
	cairo_t *cr;

	igt_require(igt_display_has_format_mod(&data->display, format,
					       modifier));

	igt_create_bo_for_fb(data->drm_fd, width, height, format, modifier,
			     newfb);
	igt_assert(newfb->gem_handle > 0);

	f.width = newfb->width;
	f.height = newfb->height;
	f.pixel_format = newfb->drm_format;
	f.flags = LOCAL_DRM_MODE_FB_MODIFIERS;

	for (int n = 0; n < newfb->num_planes; n++) {
		f.handles[n] = newfb->gem_handle;
		f.modifier[n] = newfb->modifier;
		f.pitches[n] = newfb->strides[n];
		f.offsets[n] = newfb->offsets[n];
	}

	cr = igt_get_cairo_ctx(data->drm_fd, newfb);
	igt_paint_color(cr, 0, 0, newfb->width, newfb->height, 0, 1, 0);
	igt_put_cairo_ctx(cr);

	igt_assert(drmIoctl(data->drm_fd, LOCAL_DRM_IOCTL_MODE_ADDFB2, &f) == 0);
	newfb->fb_id = f.fb_id;
}

static void free_fbs(data_t *data)
{
	igt_remove_fb(data->drm_fd, &data->small_fb);
	igt_remove_fb(data->drm_fd, &data->big_fb);
}

static void test_flip_to_scaled(data_t *data, uint32_t index, enum pipe pipe,
				igt_output_t *output)
{
	igt_plane_t *primary;
	igt_crc_t small_crc, big_crc;

	drmModeModeInfo *mode;
	struct drm_event_vblank ev;
	int ret;

	igt_display_reset(&data->display);

	igt_debug("running on output %s pipe %s\n", output->name,
		  kmstest_pipe_name(pipe));
	if (data->big_fb.fb_id == 0) {
		mode = igt_output_get_mode(output);

		// big fb will be 4k unless running on older than ICL
		if (data->gen < 11) {
			setup_fb(data, &data->small_fb,
				 min(mode->hdisplay, 640),
				 min(mode->vdisplay, 480),
				 flip_scenario_test[index].firstformat,
				 flip_scenario_test[index].firstmodifier);

			setup_fb(data, &data->big_fb,
				 1280, 960,
				 flip_scenario_test[index].secondformat,
				 flip_scenario_test[index].secondmodifier);
			igt_debug("small fb %dx%d\n", data->small_fb.width,
			          data->small_fb.height);
			igt_debug("big fb %dx%d\n", data->big_fb.width,
			          data->big_fb.height);
		} else {
			setup_fb(data, &data->small_fb,
				 min(mode->hdisplay, 1920),
				 min(mode->vdisplay, 1080),
				 flip_scenario_test[index].firstformat,
				 flip_scenario_test[index].firstmodifier);

			setup_fb(data, &data->big_fb,
				 3840, 2160,
				 flip_scenario_test[index].secondformat,
				 flip_scenario_test[index].secondmodifier);
			igt_debug("small fb %dx%d\n", data->small_fb.width,
			          data->small_fb.height);
			igt_debug("big fb %dx%d\n", data->big_fb.width,
			          data->big_fb.height);
		}
	}

	igt_output_set_pipe(output, pipe);

	primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
	igt_display_commit_atomic(&data->display, DRM_MODE_ATOMIC_ALLOW_MODESET,
				  NULL);
	data->pipe_crc = igt_pipe_crc_new(data->drm_fd, pipe,
					  INTEL_PIPE_CRC_SOURCE_AUTO);
	igt_pipe_crc_start(data->pipe_crc);

	igt_plane_set_position(primary, 0, 0);
	igt_plane_set_fb(primary, &data->small_fb);

	igt_display_commit_atomic(&data->display,
				  DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
	igt_pipe_crc_get_current(data->drm_fd, data->pipe_crc, &small_crc);

	igt_plane_set_fb(primary, &data->big_fb);
	igt_plane_set_size(primary, data->small_fb.width,
			   data->small_fb.height);
	ret = igt_display_try_commit_atomic(&data->display,
					    DRM_MODE_ATOMIC_ALLOW_MODESET  |
					    DRM_MODE_PAGE_FLIP_EVENT, NULL);

	igt_require_f(ret != -ERANGE,
		      "Platform scaling limits exceeded, skipping.");
	igt_assert_eq(ret, 0);

	igt_assert(read(data->drm_fd, &ev, sizeof(ev)) == sizeof(ev));

	igt_pipe_crc_get_current(data->drm_fd, data->pipe_crc, &big_crc);
	igt_assert(igt_check_crc_equal(&small_crc, &big_crc));

	igt_pipe_crc_stop(data->pipe_crc);
	igt_pipe_crc_free(data->pipe_crc);
	data->pipe_crc = NULL;
}

igt_main
{
	enum pipe pipe;
	data_t data = {};
	igt_output_t *output;

	igt_fixture {
		data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
		data.gen = intel_gen(intel_get_drm_devid(data.drm_fd));
		igt_require(data.gen >= 9);
		igt_display_require(&data.display, data.drm_fd);
		igt_require(data.display.is_atomic);
		igt_require_pipe_crc(data.drm_fd);
		kmstest_set_vt_graphics_mode();
	}

	for (int index = 0; index < ARRAY_SIZE(flip_scenario_test); index++) {
		igt_describe(flip_scenario_test[index].describe);
		igt_subtest(flip_scenario_test[index].name) {
			for_each_pipe_with_single_output(&data.display, pipe,
							 output)
				test_flip_to_scaled(&data, index, pipe, output);

			free_fbs(&data);
		}
	}
	igt_fixture {
		free_fbs(&data);
		if (data.pipe_crc) {
			igt_pipe_crc_stop(data.pipe_crc);
			igt_pipe_crc_free(data.pipe_crc);
			data.pipe_crc = NULL;
		}
		kmstest_set_vt_text_mode();
		igt_display_fini(&data.display);
	}
}