/*
 * Copyright 2021 Red Hat Inc.
 *
 * 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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 <stdlib.h>
#include <inttypes.h>

#include <nouveau_drm.h>
#include <nouveau/nouveau.h>
#include <nouveau/nvif/class.h>

#include "igt.h"
#include "igt_list.h"
#include "igt_nouveau.h"

#include "nouveau/nvif/push.h"
#include "nouveau/nvhw/class/cla0b5.h"
#include "nouveau/priv.h"

#define PASCAL_DMA_COPY_A                                                            (0x0000C0B5)
#define PASCAL_DMA_COPY_B                                                            (0x0000C1B5)
#define VOLTA_DMA_COPY_A                                                             (0x0000C3B5)
#define TURING_DMA_COPY_A                                                            (0x0000C5B5)
#define AMPERE_DMA_COPY_A                                                            (0x0000C6B5)

struct igt_nouveau_fb_priv {
	struct igt_nouveau_dev *dev;
	struct nouveau_bo *bo;
};

static struct igt_nouveau_dev *get_nouveau_dev(int drm_fd)
{
	struct igt_nouveau_dev *dev;
	struct nouveau_drm *drm;
	static IGT_LIST_HEAD(devices);

	igt_list_for_each_entry(dev, &devices, node) {
		if (dev->drm->fd == drm_fd)
			return dev;
	}

	igt_assert(dev = malloc(sizeof(*dev)));
	memset(dev, 0, sizeof(*dev));

	IGT_INIT_LIST_HEAD(&dev->node);

	do_or_die(nouveau_drm_new(drm_fd, &dev->drm));
	drm = dev->drm;

	igt_skip_on_f(!drm->nvif, "Only the NVIF interface for nouveau is supported\n");

	do_or_die(nouveau_device_new(&drm->client, NV_DEVICE,
				     &(struct nv_device_v0) { .device = ~0ULL, },
				     sizeof(struct nv_device_v0), &dev->dev));
	do_or_die(nouveau_client_new(dev->dev, &dev->client));

	igt_list_add(&dev->node, &devices);

	return dev;
}

uint32_t igt_nouveau_get_chipset(int fd)
{
	return get_nouveau_dev(fd)->dev->chipset;
}

uint64_t igt_nouveau_get_block_height(uint64_t modifier)
{
	uint8_t gob_height;
	uint8_t log_block_height_in_gobs = (modifier & 0xF);

	switch ((modifier >> 20) & 0x3) {
	case 0:
	case 2:
		gob_height = 8;
		break;
	case 1:
		gob_height = 4;
		break;
	default:
		igt_fail_on_f(true, "Unknown GOB height/page kind generation 3 in modifier %" PRIx64 "\n",
			      modifier);
		break;
	}

	return gob_height * (1 << log_block_height_in_gobs);
}

static void
decode_mod(uint16_t chipset, uint64_t modifier, uint32_t *tile_mode, uint32_t *kind)
{
	*tile_mode = modifier & 0xF;
	*kind = (modifier >> 12) & 0xFF;

	if (chipset >= 0xc0)
		*tile_mode <<= 4;
}

int igt_nouveau_create_bo(int drm_fd, bool sysmem, igt_fb_t *fb)
{
	struct igt_nouveau_dev *dev = get_nouveau_dev(drm_fd);
	struct nouveau_device *nvdev = dev->dev;
	union nouveau_bo_config config = { };
	struct igt_nouveau_fb_priv *priv;
	uint32_t flags = sysmem ? NOUVEAU_BO_GART : NOUVEAU_BO_VRAM;

	if (fb->modifier)
		decode_mod(nvdev->chipset, fb->modifier,
			   &config.nvc0.tile_mode, &config.nvc0.memtype);

	igt_assert(priv = malloc(sizeof(*priv)));
	do_or_die(nouveau_bo_new(nvdev, flags | NOUVEAU_BO_RDWR, nvdev->chipset < 0x140 ? 256 : 64,
				 fb->size, &config, &priv->bo));
	priv->dev = dev;
	fb->driver_priv = priv;

	if (!sysmem)
		igt_nouveau_fb_clear(fb);

	return priv->bo->handle;
}

void *igt_nouveau_mmap_bo(igt_fb_t *fb, int prot)
{
	struct igt_nouveau_fb_priv *priv = fb->driver_priv;
	struct igt_nouveau_dev *dev = priv->dev;
	struct nouveau_client *client = dev->client;

	do_or_die(nouveau_bo_map(priv->bo, prot, client));

	return priv->bo->map;
}

void igt_nouveau_munmap_bo(igt_fb_t *fb)
{
	struct igt_nouveau_fb_priv *priv = fb->driver_priv;

	munmap(priv->bo->map, priv->bo->size);
	priv->bo->map = NULL;
}

void igt_nouveau_delete_bo(igt_fb_t *fb)
{
	struct igt_nouveau_fb_priv *priv = fb->driver_priv;

	nouveau_bo_ref(NULL, &priv->bo);
	free(priv);
}

bool igt_nouveau_is_tiled(uint64_t modifier)
{
	switch (modifier) {
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0):
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1):
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2):
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3):
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4):
	case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 0):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 1):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 2):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 3):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 4):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x7a, 5):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 0):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 1):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 2):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 3):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 4):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x78, 5):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 0):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 1):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 2):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 3):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 4):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 1, 0x70, 5):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 0):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 1):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 2):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 3):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 4):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 0, 0xfe, 5):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 0):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 1):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 2):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 3):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 4):
	case DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 1, 2, 0x06, 5):
		return true;
	default:
		return false;
	}
}

/* TODO: Implement CE on Fermi */
static void init_ce(struct igt_nouveau_dev *dev)
{
	struct nouveau_device *nv_dev = dev->dev;
	struct nouveau_client *client = dev->client;
	struct nouveau_mclass mclass[] = {
		{ AMPERE_DMA_COPY_A,  -1, NULL },
		{ TURING_DMA_COPY_A,  -1, NULL },
		{ VOLTA_DMA_COPY_A,   -1, NULL },
		{ PASCAL_DMA_COPY_B,  -1, NULL },
		{ PASCAL_DMA_COPY_A,  -1, NULL },
		{ MAXWELL_DMA_COPY_A, -1, NULL },
		{ KEPLER_DMA_COPY_A,  -1, NULL },
		{ 0 }
	};
	int oclass_idx;
	uint32_t oclass;

	if (dev->ce)
		return;

	do_or_die(nouveau_object_new(&nv_dev->object, 0, NOUVEAU_FIFO_CHANNEL_CLASS,
				     &(struct nve0_fifo) {
					     .engine = NVE0_FIFO_ENGINE_CE0 | NVE0_FIFO_ENGINE_CE1,
				     }, sizeof(struct nve0_fifo), &dev->ce_channel));

	oclass_idx = nouveau_object_mclass(dev->ce_channel, mclass);
	igt_assert_f(oclass_idx >= 0, "No supported dma-copy classes found\n");
	oclass = mclass[oclass_idx].oclass;
	igt_debug("Found dma-copy class %04x\n", oclass);

	do_or_die(nouveau_pushbuf_new(client, dev->ce_channel, 4, 32 * 1024, 1, &dev->pushbuf));
	do_or_die(nouveau_object_new(dev->ce_channel, oclass, oclass, NULL, 0, &dev->ce));
}

void igt_nouveau_fb_clear(struct igt_fb *fb)
{
	struct igt_nouveau_fb_priv *priv = fb->driver_priv;
	struct igt_nouveau_dev *dev = priv->dev;

	init_ce(dev);

	igt_set_timeout(30, "Timed out while clearing bo with dma-copy");

	for (unsigned int plane = 0; plane < fb->num_planes; plane++)
		igt_nouveau_ce_zfilla0b5(dev, fb, priv->bo, plane);

	do_or_die(nouveau_bo_wait(priv->bo, NOUVEAU_BO_RD, dev->client));

	igt_reset_timeout();
}

void igt_nouveau_fb_blit(struct igt_fb *dst, struct igt_fb *src)
{
	struct igt_nouveau_fb_priv *dst_priv = dst->driver_priv, *src_priv = src->driver_priv;
	struct igt_nouveau_dev *dev = dst_priv->dev;
	struct nouveau_bo *dst_nvbo = dst_priv->bo, *src_nvbo = src_priv->bo;

	init_ce(dev);

	igt_set_timeout(30, "Timed out while blitting bo with dma-copy");

	for (unsigned int plane = 0; plane < src->num_planes; plane++)
		igt_nouveau_ce_copya0b5(dev, dst, dst_nvbo, src, src_nvbo, plane);

	do_or_die(nouveau_bo_wait(dst_priv->bo, NOUVEAU_BO_RD, dev->client));

	igt_reset_timeout();
}
