/*
 * Copyright © 2016 Broadcom
 *
 * 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 "drmtest.h"
#include "igt_aux.h"
#include "igt_fb.h"
#include "igt_vc4.h"
#include "ioctl_wrappers.h"
#include "vc4_packet.h"

#if NEW_CONTEXT_PARAM_NO_ERROR_CAPTURE_API
#define LOCAL_CONTEXT_PARAM_NO_ERROR_CAPTURE 0x4
#endif

/**
 * SECTION:igt_vc4
 * @short_description: VC4 support library
 * @title: VC4
 * @include: igt.h
 *
 * This library provides various auxiliary helper functions for writing VC4
 * tests.
 */

bool igt_vc4_is_tiled(uint64_t modifier)
{
	if (modifier >> 56ULL != DRM_FORMAT_MOD_VENDOR_BROADCOM)
		return false;

	switch (fourcc_mod_broadcom_mod(modifier)) {
	case DRM_FORMAT_MOD_BROADCOM_SAND32:
	case DRM_FORMAT_MOD_BROADCOM_SAND64:
	case DRM_FORMAT_MOD_BROADCOM_SAND128:
	case DRM_FORMAT_MOD_BROADCOM_SAND256:
	case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
		return true;
	default:
		return false;
	}
}

bool igt_vc4_is_v3d(int fd)
{
	uint64_t value;

	/*
	 * vc5 doesn't have syncobj capabilities, only vc4.
	 */
	if (drmGetCap(fd, DRM_CAP_SYNCOBJ, &value))
		return false;
	return value;
}

/**
 * igt_vc4_get_cleared_bo:
 * @fd: device file descriptor
 * @size: size of the BO in bytes
 * @clearval: u32 value that the buffer should be completely cleared with
 *
 * This helper returns a new BO with the given size, which has just been
 * cleared using the render engine.
 */
uint32_t igt_vc4_get_cleared_bo(int fd, size_t size, uint32_t clearval)
{
	/* A single row will be a page. */
	uint32_t width = 1024;
	uint32_t height = size / (width * 4);
	uint32_t handle = igt_vc4_create_bo(fd, size);
	struct drm_vc4_submit_cl submit = {
		.color_write = {
			.hindex = 0,
			.bits = VC4_SET_FIELD(VC4_RENDER_CONFIG_FORMAT_RGBA8888,
					      VC4_RENDER_CONFIG_FORMAT),
		},

		.color_read = { .hindex = ~0 },
		.zs_read = { .hindex = ~0 },
		.zs_write = { .hindex = ~0 },
		.msaa_color_write = { .hindex = ~0 },
		.msaa_zs_write = { .hindex = ~0 },

		.bo_handles = to_user_pointer(&handle),
		.bo_handle_count = 1,
		.width = width,
		.height = height,
		.max_x_tile = ALIGN(width, 64) / 64 - 1,
		.max_y_tile = ALIGN(height, 64) / 64 - 1,
		.clear_color = { clearval, clearval },
		.flags = VC4_SUBMIT_CL_USE_CLEAR_COLOR,
	};

	igt_assert_eq_u32(width * height * 4, size);

	do_ioctl(fd, DRM_IOCTL_VC4_SUBMIT_CL, &submit);

	return handle;
}

int
igt_vc4_create_bo(int fd, size_t size)
{
	struct drm_vc4_create_bo create = {
		.size = size,
	};

	do_ioctl(fd, DRM_IOCTL_VC4_CREATE_BO, &create);

	return create.handle;
}

void *
igt_vc4_mmap_bo(int fd, uint32_t handle, uint32_t size, unsigned prot)
{
	struct drm_vc4_mmap_bo mmap_bo = {
		.handle = handle,
	};
	void *ptr;

	do_ioctl(fd, DRM_IOCTL_VC4_MMAP_BO, &mmap_bo);

	igt_assert_eq(mmap_bo.offset % sysconf(_SC_PAGE_SIZE), 0);

	ptr = mmap(0, size, prot, MAP_SHARED, fd, mmap_bo.offset);
	if (ptr == MAP_FAILED)
		return NULL;
	else
		return ptr;
}

void igt_vc4_set_tiling(int fd, uint32_t handle, uint64_t modifier)
{
	struct drm_vc4_set_tiling set = {
		.handle = handle,
		.modifier = modifier,
	};

	do_ioctl(fd, DRM_IOCTL_VC4_SET_TILING, &set);
}

uint64_t igt_vc4_get_tiling(int fd, uint32_t handle)
{
	struct drm_vc4_get_tiling get = {
		.handle = handle,
	};

	do_ioctl(fd, DRM_IOCTL_VC4_GET_TILING, &get);

	return get.modifier;
}

/**
 * igt_vc4_get_param:
 * @fd: device file descriptor
 * @param: vc4 parameter
 *
 * This wraps the GET_PARAM ioctl.
 *
 * Returns the current value of the parameter. If the parameter is
 * invalid, returns 0.
 */
uint64_t igt_vc4_get_param(int fd, uint32_t param)
{
	struct drm_vc4_get_param arg = {
		.param = param,
	};
	int ret;

	ret = igt_ioctl(fd, DRM_IOCTL_VC4_GET_PARAM, &arg);
	if (ret)
		return 0;

	return arg.value;
}

bool igt_vc4_purgeable_bo(int fd, int handle, bool purgeable)
{
	struct drm_vc4_gem_madvise arg = {
		.handle = handle,
		.madv = purgeable ? VC4_MADV_DONTNEED : VC4_MADV_WILLNEED,
	};

	do_ioctl(fd, DRM_IOCTL_VC4_GEM_MADVISE, &arg);

	return arg.retained;
}

uint32_t igt_vc4_perfmon_create(int fd, uint32_t ncounters, uint8_t *events)
{
	struct drm_vc4_perfmon_create create = {
		.ncounters = ncounters,
	};

	memcpy(create.events, events, ncounters * sizeof(*events));

	do_ioctl(fd, DRM_IOCTL_VC4_PERFMON_CREATE, &create);
	igt_assert_neq(create.id, 0);

	return create.id;
}

void igt_vc4_perfmon_get_values(int fd, uint32_t id)
{
	uint64_t *values = calloc(DRM_VC4_MAX_PERF_COUNTERS, sizeof(*values));
	struct drm_vc4_perfmon_get_values get = {
		.id = id,
		.values_ptr = to_user_pointer(values),
	};

	do_ioctl(fd, DRM_IOCTL_VC4_PERFMON_GET_VALUES, &get);
	free(values);
}

void igt_vc4_perfmon_destroy(int fd, uint32_t id)
{
	struct drm_vc4_perfmon_destroy destroy = {
		.id = id,
	};

	do_ioctl(fd, DRM_IOCTL_VC4_PERFMON_DESTROY, &destroy);
}

/* Calculate the t-tile width so that size = width * height * bpp / 8. */
#define VC4_T_TILE_W(size, height, bpp) ((size) / (height) / ((bpp) / 8))

static size_t igt_vc4_t_tiled_offset(size_t stride, size_t height, size_t bpp,
				     size_t x, size_t y)
{
	const size_t t1k_map_even[] = { 0, 3, 1, 2 };
	const size_t t1k_map_odd[] = { 2, 1, 3, 0 };
	const size_t t4k_t_h = 32;
	const size_t t1k_t_h = 16;
	const size_t t64_t_h = 4;
	size_t offset = 0;
	size_t t4k_t_w, t4k_w, t4k_x, t4k_y;
	size_t t1k_t_w, t1k_x, t1k_y;
	size_t t64_t_w, t64_x, t64_y;
	size_t pix_x, pix_y;
	unsigned int index;

	/* T-tiling is only supported for 16 and 32 bpp. */
	igt_assert(bpp == 16 || bpp == 32);

	/* T-tiling stride must be aligned to the 4K tiles strides. */
	igt_assert((stride % (4096 / t4k_t_h)) == 0);

	/* Calculate the tile width for the bpp. */
	t4k_t_w = VC4_T_TILE_W(4096, t4k_t_h, bpp);
	t1k_t_w = VC4_T_TILE_W(1024, t1k_t_h, bpp);
	t64_t_w = VC4_T_TILE_W(64, t64_t_h, bpp);

	/* Aligned total width in number of 4K tiles. */
	t4k_w = (stride / (bpp / 8)) / t4k_t_w;

	/* X and y coordinates in number of 4K tiles. */
	t4k_x = x / t4k_t_w;
	t4k_y = y / t4k_t_h;

	/* Increase offset to the beginning of the 4K tile row. */
	offset += t4k_y * t4k_w * 4096;

	/* X and Y coordinates in number of 1K tiles within the 4K tile. */
	t1k_x = (x % t4k_t_w) / t1k_t_w;
	t1k_y = (y % t4k_t_h) / t1k_t_h;

	/* Index for 1K tile map lookup. */
	index = 2 * t1k_y + t1k_x;

	/* Odd rows start from the right, even rows from the left. */
	if (t4k_y % 2) {
		/* Increase offset to the 4K tile (starting from the right). */
		offset += (t4k_w - t4k_x - 1) * 4096;

		/* Incrase offset to the beginning of the (odd) 1K tile. */
		offset += t1k_map_odd[index] * 1024;
	} else {
		/* Increase offset to the 4K tile (starting from the left). */
		offset += t4k_x * 4096;

		/* Incrase offset to the beginning of the (even) 1K tile. */
		offset += t1k_map_even[index] * 1024;
	}

	/* X and Y coordinates in number of 64 byte tiles within the 1K tile. */
	t64_x = (x % t1k_t_w) / t64_t_w;
	t64_y = (y % t1k_t_h) / t64_t_h;

	/* Increase offset to the beginning of the 64-byte tile. */
	offset += (t64_y * (t1k_t_w / t64_t_w) + t64_x) * 64;

	/* X and Y coordinates in number of pixels within the 64-byte tile. */
	pix_x = x % t64_t_w;
	pix_y = y % t64_t_h;

	/* Increase offset to the correct pixel. */
	offset += (pix_y * t64_t_w + pix_x) * bpp / 8;

	return offset;
}

static void vc4_fb_convert_plane_to_t_tiled(struct igt_fb *dst, void *dst_buf,
					    struct igt_fb *src, void *src_buf,
					    unsigned int plane)
{
	size_t bpp = src->plane_bpp[plane];
	unsigned int i, j;

	for (i = 0; i < src->height; i++) {
		for (j = 0; j < src->width; j++) {
			size_t src_offset = src->offsets[plane];
			size_t dst_offset = dst->offsets[plane];

			src_offset += src->strides[plane] * i + j * bpp / 8;
			dst_offset += igt_vc4_t_tiled_offset(dst->strides[plane],
							     dst->height,
							     bpp, j, i);

			switch (bpp) {
			case 16:
				*(uint16_t *)(dst_buf + dst_offset) =
					*(uint16_t *)(src_buf + src_offset);
				break;
			case 32:
				*(uint32_t *)(dst_buf + dst_offset) =
					*(uint32_t *)(src_buf + src_offset);
				break;
			}
		}
	}
}

static void vc4_fb_convert_plane_from_t_tiled(struct igt_fb *dst, void *dst_buf,
					      struct igt_fb *src, void *src_buf,
					      unsigned int plane)
{
	size_t bpp = src->plane_bpp[plane];
	unsigned int i, j;

	for (i = 0; i < src->height; i++) {
		for (j = 0; j < src->width; j++) {
			size_t src_offset = src->offsets[plane];
			size_t dst_offset = dst->offsets[plane];

			src_offset += igt_vc4_t_tiled_offset(src->strides[plane],
							     src->height,
							     bpp, j, i);
			src_offset += dst->strides[plane] * i + j * bpp / 8;

			switch (bpp) {
			case 16:
				*(uint16_t *)(dst_buf + dst_offset) =
					*(uint16_t *)(src_buf + src_offset);
				break;
			case 32:
				*(uint32_t *)(dst_buf + dst_offset) =
					*(uint32_t *)(src_buf + src_offset);
				break;
			}
		}
	}
}

static size_t vc4_sand_tiled_offset(size_t column_width, size_t column_size, size_t x,
				    size_t y, size_t bpp)
{
	size_t offset = 0;
	size_t cols_x;
	size_t pix_x;

	/* Offset to the beginning of the relevant column. */
	cols_x = x / column_width;
	offset += cols_x * column_size;

	/* Offset to the relevant pixel. */
	pix_x = x % column_width;
	offset += (column_width * y + pix_x) * bpp / 8;

	return offset;
}

static void vc4_fb_convert_plane_to_sand_tiled(struct igt_fb *dst, void *dst_buf,
					       struct igt_fb *src, void *src_buf,
					       unsigned int plane)
{
	uint64_t modifier_base = fourcc_mod_broadcom_mod(dst->modifier);
	uint32_t column_height = fourcc_mod_broadcom_param(dst->modifier);
	uint32_t column_width_bytes, column_width, column_size;
	size_t bpp = dst->plane_bpp[plane];
	unsigned int i, j;

	switch (modifier_base) {
	case DRM_FORMAT_MOD_BROADCOM_SAND32:
		column_width_bytes = 32;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND64:
		column_width_bytes = 64;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND128:
		column_width_bytes = 128;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND256:
		column_width_bytes = 256;
		break;
	default:
		igt_assert(false);
	}

	column_width = column_width_bytes * dst->plane_width[plane] / dst->width;
	column_size = column_width_bytes * column_height;

	for (i = 0; i < dst->plane_height[plane]; i++) {
		for (j = 0; j < src->plane_width[plane]; j++) {
			size_t src_offset = src->offsets[plane];
			size_t dst_offset = dst->offsets[plane];

			src_offset += src->strides[plane] * i + j * bpp / 8;
			dst_offset += vc4_sand_tiled_offset(column_width,
							    column_size, j, i,
							    bpp);

			switch (bpp) {
			case 8:
				*(uint8_t *)(dst_buf + dst_offset) =
					*(uint8_t *)(src_buf + src_offset);
				break;
			case 16:
				*(uint16_t *)(dst_buf + dst_offset) =
					*(uint16_t *)(src_buf + src_offset);
				break;
			default:
				igt_assert(false);
			}
		}
	}
}

static void vc4_fb_convert_plane_from_sand_tiled(struct igt_fb *dst, void *dst_buf,
						 struct igt_fb *src, void *src_buf,
						 unsigned int plane)
{
	uint64_t modifier_base = fourcc_mod_broadcom_mod(src->modifier);
	uint32_t column_height = fourcc_mod_broadcom_param(src->modifier);
	uint32_t column_width_bytes, column_width, column_size;
	size_t bpp = src->plane_bpp[plane];
	unsigned int i, j;

	switch (modifier_base) {
	case DRM_FORMAT_MOD_BROADCOM_SAND32:
		column_width_bytes = 32;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND64:
		column_width_bytes = 64;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND128:
		column_width_bytes = 128;
		break;
	case DRM_FORMAT_MOD_BROADCOM_SAND256:
		column_width_bytes = 256;
		break;
	default:
		igt_assert(false);
	}

	column_width = column_width_bytes * src->plane_width[plane] / src->width;
	column_size = column_width_bytes * column_height;

	for (i = 0; i < dst->plane_height[plane]; i++) {
		for (j = 0; j < src->plane_width[plane]; j++) {
			size_t src_offset = src->offsets[plane];
			size_t dst_offset = dst->offsets[plane];

			src_offset += vc4_sand_tiled_offset(column_width,
							    column_size, j, i,
							    bpp);
			dst_offset += dst->strides[plane] * i + j * bpp / 8;

			switch (bpp) {
			case 8:
				*(uint8_t *)(dst_buf + dst_offset) =
					*(uint8_t *)(src_buf + src_offset);
				break;
			case 16:
				*(uint16_t *)(dst_buf + dst_offset) =
					*(uint16_t *)(src_buf + src_offset);
				break;
			default:
				igt_assert(false);
			}
		}
	}
}

void vc4_fb_convert_plane_to_tiled(struct igt_fb *dst, void *dst_buf,
				     struct igt_fb *src, void *src_buf)
{
	unsigned int plane;

	igt_assert(src->modifier == DRM_FORMAT_MOD_LINEAR);
	igt_assert(igt_vc4_is_tiled(dst->modifier));

	for (plane = 0; plane < src->num_planes; plane++) {
		if (dst->modifier == DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED)
			vc4_fb_convert_plane_to_t_tiled(dst, dst_buf, src, src_buf, plane);
		else
			vc4_fb_convert_plane_to_sand_tiled(dst, dst_buf, src, src_buf, plane);
	}
}

void vc4_fb_convert_plane_from_tiled(struct igt_fb *dst, void *dst_buf,
				       struct igt_fb *src, void *src_buf)
{
	unsigned int plane;

	igt_assert(igt_vc4_is_tiled(src->modifier));
	igt_assert(dst->modifier == DRM_FORMAT_MOD_LINEAR);

	for (plane = 0; plane < src->num_planes; plane++) {
		if (src->modifier == DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED)
			vc4_fb_convert_plane_from_t_tiled(dst, dst_buf, src, src_buf, plane);
		else
			vc4_fb_convert_plane_from_sand_tiled(dst, dst_buf, src, src_buf, plane);
	}
}
