// SPDX-License-Identifier: GPL-2.0+
/*
 * NVIDIA Tegra Video decoder driver
 *
 * Copyright (C) 2016-2019 GRATE-DRIVER project
 */

#include <linux/iommu.h>
#include <linux/iova.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
#include <asm/dma-iommu.h>
#endif

#include "vde.h"

int tegra_vde_iommu_map(struct tegra_vde *vde,
			struct sg_table *sgt,
			struct iova **iovap,
			size_t size)
{
	struct iova *iova;
	unsigned long shift;
	unsigned long end;
	dma_addr_t addr;

	end = vde->domain->geometry.aperture_end;
	size = iova_align(&vde->iova, size);
	shift = iova_shift(&vde->iova);

	iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true);
	if (!iova)
		return -ENOMEM;

	addr = iova_dma_addr(&vde->iova, iova);

	size = iommu_map_sg(vde->domain, addr, sgt->sgl, sgt->nents,
			    IOMMU_READ | IOMMU_WRITE);
	if (!size) {
		__free_iova(&vde->iova, iova);
		return -ENXIO;
	}

	*iovap = iova;

	return 0;
}

void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova)
{
	unsigned long shift = iova_shift(&vde->iova);
	unsigned long size = iova_size(iova) << shift;
	dma_addr_t addr = iova_dma_addr(&vde->iova, iova);

	iommu_unmap(vde->domain, addr, size);
	__free_iova(&vde->iova, iova);
}

int tegra_vde_iommu_init(struct tegra_vde *vde)
{
	struct device *dev = vde->miscdev.parent;
	struct iova *iova;
	unsigned long order;
	unsigned long shift;
	int err;

	vde->group = iommu_group_get(dev);
	if (!vde->group)
		return 0;

#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
	if (dev->archdata.mapping) {
		struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);

		arm_iommu_detach_device(dev);
		arm_iommu_release_mapping(mapping);
	}
#endif
	vde->domain = iommu_domain_alloc(&platform_bus_type);
	if (!vde->domain) {
		err = -ENOMEM;
		goto put_group;
	}

	err = iova_cache_get();
	if (err)
		goto free_domain;

	order = __ffs(vde->domain->pgsize_bitmap);
	init_iova_domain(&vde->iova, 1UL << order, 0);

	err = iommu_attach_group(vde->domain, vde->group);
	if (err)
		goto put_iova;

	/*
	 * We're using some static addresses that are not accessible by VDE
	 * to trap invalid memory accesses.
	 */
	shift = iova_shift(&vde->iova);
	iova = reserve_iova(&vde->iova, 0x60000000 >> shift,
			    0x70000000 >> shift);
	if (!iova) {
		err = -ENOMEM;
		goto detach_group;
	}

	vde->iova_resv_static_addresses = iova;

	/*
	 * BSEV's end-address wraps around due to integer overflow during
	 * of hardware context preparation if IOVA is allocated at the end
	 * of address space and VDE can't handle that. Hence simply reserve
	 * the last page to avoid the problem.
	 */
	iova = reserve_iova(&vde->iova, 0xffffffff >> shift,
			    (0xffffffff >> shift) + 1);
	if (!iova) {
		err = -ENOMEM;
		goto unreserve_iova;
	}

	vde->iova_resv_last_page = iova;

	return 0;

unreserve_iova:
	__free_iova(&vde->iova, vde->iova_resv_static_addresses);
detach_group:
	iommu_detach_group(vde->domain, vde->group);
put_iova:
	put_iova_domain(&vde->iova);
	iova_cache_put();
free_domain:
	iommu_domain_free(vde->domain);
put_group:
	iommu_group_put(vde->group);

	return err;
}

void tegra_vde_iommu_deinit(struct tegra_vde *vde)
{
	if (vde->domain) {
		__free_iova(&vde->iova, vde->iova_resv_last_page);
		__free_iova(&vde->iova, vde->iova_resv_static_addresses);
		iommu_detach_group(vde->domain, vde->group);
		put_iova_domain(&vde->iova);
		iova_cache_put();
		iommu_domain_free(vde->domain);
		iommu_group_put(vde->group);

		vde->domain = NULL;
	}
}
