/*
 * @File
 * @Codingstyle LinuxKernel
 * @Copyright   Copyright (c) Imagination Technologies Ltd. All Rights Reserved
 * @License     Dual MIT/GPLv2
 *
 * The contents of this file are subject to the MIT license as set out below.
 *
 * 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.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 ("GPL") in which case the provisions
 * of GPL are applicable instead of those above.
 *
 * If you wish to allow use of your version of this file only under the terms of
 * GPL, and not to allow others to use your version of this file under the terms
 * of the MIT license, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by GPL as set
 * out in the file called "GPL-COPYING" included in this distribution. If you do
 * not delete the provisions above, a recipient may use your version of this file
 * under the terms of either the MIT license or GPL.
 *
 * This License is also included in this distribution in the file called
 * "MIT-COPYING".
 *
 * EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) 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; AND (B) 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 <linux/version.h>

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0))
#include <drm/drm_drv.h>
#include <drm/drm_prime.h>
#include <linux/platform_device.h>
#endif

#include <linux/dma-buf.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/capability.h>

#include <drm/drm_mm.h>

#if defined(SUPPORT_PLATO_DISPLAY)
#include "plato_drv.h"
#else
#include "tc_drv.h"
#endif

#include "drm_pdp_gem.h"
#include "pdp_drm.h"
#include "kernel_compatibility.h"

#if defined(SUPPORT_PLATO_DISPLAY)
#define pdp_gem_platform_data plato_pdp_platform_data
#else
#define pdp_gem_platform_data tc_pdp_platform_data
#endif

struct pdp_gem_private {
	struct mutex			vram_lock;
	struct				drm_mm vram;
};

static struct pdp_gem_object *
pdp_gem_private_object_create(struct drm_device *dev,
			      size_t size,
			      struct dma_resv *resv)
{
	struct pdp_gem_object *pdp_obj;

	WARN_ON(PAGE_ALIGN(size) != size);

	pdp_obj = kzalloc(sizeof(*pdp_obj), GFP_KERNEL);
	if (!pdp_obj)
		return ERR_PTR(-ENOMEM);

#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0))
	if (!resv)
		dma_resv_init(&pdp_obj->_resv);
#else
	pdp_obj->base.resv = resv;
#endif
	drm_gem_private_object_init(dev, &pdp_obj->base, size);

	return pdp_obj;
}

struct drm_gem_object *pdp_gem_object_create(struct drm_device *dev,
					     struct pdp_gem_private *gem_priv,
					     size_t size,
					     u32 flags)
{
	struct pdp_gem_platform_data *pdata =
		to_platform_device(dev->dev)->dev.platform_data;
	struct pdp_gem_object *pdp_obj;
	struct drm_mm_node *node;
	int err = 0;

	pdp_obj = pdp_gem_private_object_create(dev, size, NULL);
	if (!pdp_obj) {
		err = -ENOMEM;
		goto err_exit;
	}

	node = kzalloc(sizeof(*node), GFP_KERNEL);
	if (!node) {
		err = -ENOMEM;
		goto err_unref;
	}

	mutex_lock(&gem_priv->vram_lock);
	err = drm_mm_insert_node(&gem_priv->vram, node, size);
	mutex_unlock(&gem_priv->vram_lock);
	if (err)
		goto err_free_node;

	pdp_obj->vram = node;
	pdp_obj->dev_addr = pdp_obj->vram->start;
	pdp_obj->cpu_addr = pdata->memory_base + pdp_obj->dev_addr;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0))
	pdp_obj->resv = &pdp_obj->_resv;
#else
	pdp_obj->resv = pdp_obj->base.resv;
#endif
	pdp_obj->dma_map_export_host_addr = pdata->dma_map_export_host_addr;

	return &pdp_obj->base;

err_free_node:
	kfree(node);
err_unref:
	pdp_gem_object_free_priv(gem_priv, &pdp_obj->base);
err_exit:
	return ERR_PTR(err);
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0))
vm_fault_t pdp_gem_object_vm_fault(struct vm_fault *vmf)
#else
int pdp_gem_object_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
#endif
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0))
	struct vm_area_struct *vma = vmf->vma;
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
	unsigned long addr = vmf->address;
#else
	unsigned long addr = (unsigned long)vmf->virtual_address;
#endif
	struct drm_gem_object *obj = vma->vm_private_data;
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);
	unsigned long off;
	unsigned long pfn;

	off = addr - vma->vm_start;
	pfn = (pdp_obj->cpu_addr + off) >> PAGE_SHIFT;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0))
	return vmf_insert_pfn(vma, addr, pfn);
#else
	{
		int err;

		err = vm_insert_pfn(vma, addr, pfn);
		switch (err) {
		case 0:
		case -EBUSY:
			return VM_FAULT_NOPAGE;
		case -ENOMEM:
			return VM_FAULT_OOM;
		default:
			return VM_FAULT_SIGBUS;
		}
	}
#endif
}

void pdp_gem_object_free_priv(struct pdp_gem_private *gem_priv,
			      struct drm_gem_object *obj)
{
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);

	drm_gem_free_mmap_offset(obj);

#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0))
	if (&pdp_obj->_resv == pdp_obj->resv)
		dma_resv_fini(&pdp_obj->_resv);
#endif
	if (pdp_obj->vram) {
		mutex_lock(&gem_priv->vram_lock);
		drm_mm_remove_node(pdp_obj->vram);
		mutex_unlock(&gem_priv->vram_lock);

		kfree(pdp_obj->vram);
	} else if (obj->import_attach) {
		drm_prime_gem_destroy(obj, pdp_obj->sgt);
	}

	drm_gem_object_release(&pdp_obj->base);
	kfree(pdp_obj);
}

static int pdp_gem_prime_attach(struct dma_buf *dma_buf,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0))
				struct device *dev,
#endif
				struct dma_buf_attachment *attach)
{
	struct drm_gem_object *obj = dma_buf->priv;

	/* Restrict access to Rogue */
	if (WARN_ON(!obj->dev->dev->parent) ||
	    obj->dev->dev->parent != attach->dev->parent)
		return -EPERM;

	return 0;
}

static struct sg_table *
pdp_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
			  enum dma_data_direction dir)
{
	struct drm_gem_object *obj = attach->dmabuf->priv;
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);
	struct sg_table *sgt;

	sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
	if (!sgt)
		return NULL;

	if (sg_alloc_table(sgt, 1, GFP_KERNEL))
		goto err_free_sgt;

	if (pdp_obj->dma_map_export_host_addr)
		sg_dma_address(sgt->sgl) = pdp_obj->cpu_addr;
	else
		sg_dma_address(sgt->sgl) = pdp_obj->dev_addr;

	sg_dma_len(sgt->sgl) = obj->size;

	return sgt;

err_free_sgt:
	kfree(sgt);
	return NULL;
}

static void pdp_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach,
					struct sg_table *sgt,
					enum dma_data_direction dir)
{
	sg_free_table(sgt);
	kfree(sgt);
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0))
static void *pdp_gem_prime_kmap_atomic(struct dma_buf *dma_buf,
				       unsigned long page_num)
{
	return NULL;
}
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0))
static void *pdp_gem_prime_kmap(struct dma_buf *dma_buf,
				unsigned long page_num)
{
	return NULL;
}
#endif

static int pdp_gem_prime_mmap(struct dma_buf *dma_buf,
			      struct vm_area_struct *vma)
{
	struct drm_gem_object *obj = dma_buf->priv;
	int err;

	mutex_lock(&obj->dev->struct_mutex);
	err = drm_gem_mmap_obj(obj, obj->size, vma);
	mutex_unlock(&obj->dev->struct_mutex);

	return err;
}

#if defined(CONFIG_X86)
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0))
static void *pdp_gem_prime_vmap(struct dma_buf *dma_buf)
#else
static int pdp_gem_prime_vmap(struct dma_buf *dma_buf, struct dma_buf_map *map)
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) */
{
	struct drm_gem_object *obj = dma_buf->priv;
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);
	void __iomem *vaddr;
	__maybe_unused int ret = 0;

	mutex_lock(&obj->dev->struct_mutex);

	/*
	 * On x86 platforms, the pointer returned by ioremap can be dereferenced
	 * directly. As such, explicitly cast away the __ioremap qualifier.
	 */
	vaddr = ioremap(pdp_obj->cpu_addr, obj->size);
	if (vaddr == NULL) {
		DRM_DEBUG_DRIVER("ioremap failed");
		ret = -ENOMEM;
	}

	mutex_unlock(&obj->dev->struct_mutex);

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0))
	if (ret == 0)
		dma_buf_map_set_vaddr_iomem(map, vaddr);
	return ret;
#else
	return (void __force *) vaddr;
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) */
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0))
static void pdp_gem_prime_vunmap(struct dma_buf *dma_buf, void *vaddr)
#else
static void pdp_gem_prime_vunmap(struct dma_buf *dma_buf, struct dma_buf_map *map)
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) */
{
	struct drm_gem_object *obj = dma_buf->priv;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0))
	void __iomem *vaddr = map->vaddr_iomem;
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) */

	mutex_lock(&obj->dev->struct_mutex);
	iounmap((void __iomem *)vaddr);

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0))
	dma_buf_map_clear(map);
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) */

	mutex_unlock(&obj->dev->struct_mutex);
}
#endif

static const struct dma_buf_ops pdp_gem_prime_dmabuf_ops = {
	.attach		= pdp_gem_prime_attach,
	.map_dma_buf	= pdp_gem_prime_map_dma_buf,
	.unmap_dma_buf	= pdp_gem_prime_unmap_dma_buf,
	.release	= drm_gem_dmabuf_release,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0))
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0))
	.map_atomic	= pdp_gem_prime_kmap_atomic,
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0))
	.map		= pdp_gem_prime_kmap,
#endif
#else
	.kmap_atomic	= pdp_gem_prime_kmap_atomic,
	.kmap		= pdp_gem_prime_kmap,
#endif
	.mmap		= pdp_gem_prime_mmap,
#if defined(CONFIG_X86)
	.vmap		= pdp_gem_prime_vmap,
	.vunmap		= pdp_gem_prime_vunmap
#endif
};


static int
pdp_gem_lookup_our_object(struct drm_file *file, u32 handle,
			  struct drm_gem_object **objp)

{
	struct drm_gem_object *obj;

	obj = drm_gem_object_lookup(file, handle);
	if (!obj)
		return -ENOENT;

	if (obj->import_attach) {
		/*
		 * The dmabuf associated with the object is not one of ours.
		 * Our own buffers are handled differently on import.
		 */
		drm_gem_object_put(obj);
		return -EINVAL;
	}

	*objp = obj;
	return 0;
}

struct dma_buf *pdp_gem_prime_export(
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
				     struct drm_device *dev,
#endif
				     struct drm_gem_object *obj,
				     int flags)
{
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
	DEFINE_DMA_BUF_EXPORT_INFO(export_info);

	export_info.ops = &pdp_gem_prime_dmabuf_ops;
	export_info.size = obj->size;
	export_info.flags = flags;
	export_info.resv = pdp_obj->resv;
	export_info.priv = obj;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
	return drm_gem_dmabuf_export(obj->dev, &export_info);
#else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0))
	return drm_gem_dmabuf_export(dev, &export_info);
#else
	return dma_buf_export(&export_info);
#endif
#endif
#else
	return dma_buf_export(obj, &pdp_gem_prime_dmabuf_ops, obj->size,
			      flags, pdp_obj->resv);
#endif
}

struct drm_gem_object *
pdp_gem_prime_import(struct drm_device *dev,
		     struct dma_buf *dma_buf)
{
	struct drm_gem_object *obj = dma_buf->priv;

	if (obj->dev == dev) {
		BUG_ON(dma_buf->ops != &pdp_gem_prime_dmabuf_ops);

		/*
		 * The dmabuf is one of ours, so return the associated
		 * PDP GEM object, rather than create a new one.
		 */
		drm_gem_object_get(obj);

		return obj;
	}

	return drm_gem_prime_import(dev, dma_buf);
}

struct drm_gem_object *
pdp_gem_prime_import_sg_table(struct drm_device *dev,
			      struct dma_buf_attachment *attach,
			      struct sg_table *sgt)
{
	struct pdp_gem_platform_data *pdata =
		to_platform_device(dev->dev)->dev.platform_data;
	struct pdp_gem_object *pdp_obj;
	int err;

	pdp_obj = pdp_gem_private_object_create(dev,
						attach->dmabuf->size,
						attach->dmabuf->resv);
	if (!pdp_obj) {
		err = -ENOMEM;
		goto err_exit;
	}

	pdp_obj->sgt = sgt;

	/* We only expect a single entry for card memory */
	if (pdp_obj->sgt->nents != 1) {
		err = -EINVAL;
		goto err_obj_unref;
	}

	pdp_obj->dev_addr = sg_dma_address(pdp_obj->sgt->sgl);
	pdp_obj->cpu_addr = pdata->memory_base + pdp_obj->dev_addr;
	pdp_obj->resv = attach->dmabuf->resv;

	return &pdp_obj->base;

err_obj_unref:
	drm_gem_object_put(&pdp_obj->base);
err_exit:
	return ERR_PTR(err);
}

int pdp_gem_dumb_create_priv(struct drm_file *file,
			     struct drm_device *dev,
			     struct pdp_gem_private *gem_priv,
			     struct drm_mode_create_dumb *args)
{
	struct drm_gem_object *obj;
	u32 handle;
	u32 pitch;
	size_t size;
	int err;

	pitch = args->width * (ALIGN(args->bpp, 8) >> 3);
	size = PAGE_ALIGN(pitch * args->height);

	obj = pdp_gem_object_create(dev, gem_priv, size, 0);
	if (IS_ERR(obj))
		return PTR_ERR(obj);

	err = drm_gem_handle_create(file, obj, &handle);
	if (err)
		goto exit;

	args->handle = handle;
	args->pitch = pitch;
	args->size = size;

exit:
	drm_gem_object_put(obj);
	return err;
}

int pdp_gem_dumb_map_offset(struct drm_file *file,
			    struct drm_device *dev,
			    uint32_t handle,
			    uint64_t *offset)
{
	struct drm_gem_object *obj;
	int err;

	mutex_lock(&dev->struct_mutex);

	err = pdp_gem_lookup_our_object(file, handle, &obj);
	if (err)
		goto exit_unlock;

	err = drm_gem_create_mmap_offset(obj);
	if (err)
		goto exit_obj_unref;

	*offset = drm_vma_node_offset_addr(&obj->vma_node);

exit_obj_unref:
	drm_gem_object_put(obj);
exit_unlock:
	mutex_unlock(&dev->struct_mutex);
	return err;
}

struct pdp_gem_private *pdp_gem_init(struct drm_device *dev)
{
#if !defined(SUPPORT_ION) || defined(SUPPORT_GEM_ALLOC)
	struct pdp_gem_platform_data *pdata =
		to_platform_device(dev->dev)->dev.platform_data;
#endif
	struct pdp_gem_private *gem_priv =
					kmalloc(sizeof(*gem_priv), GFP_KERNEL);

	if (!gem_priv)
		return NULL;

	mutex_init(&gem_priv->vram_lock);

	memset(&gem_priv->vram, 0, sizeof(gem_priv->vram));

#if defined(SUPPORT_ION) && !defined(SUPPORT_GEM_ALLOC)
	drm_mm_init(&gem_priv->vram, 0, 0);
	DRM_INFO("%s has no directly allocatable memory; the memory is managed by ION\n",
		dev->driver->name);
#else
	drm_mm_init(&gem_priv->vram,
			pdata->pdp_heap_memory_base - pdata->memory_base,
			pdata->pdp_heap_memory_size);

	DRM_INFO("%s has %pa bytes of allocatable memory at 0x%llx = (0x%llx - 0x%llx)\n",
		dev->driver->name, &pdata->pdp_heap_memory_size,
		(u64)(pdata->pdp_heap_memory_base - pdata->memory_base),
		(u64)pdata->pdp_heap_memory_base, (u64)pdata->memory_base);
#endif
	return gem_priv;
}

void pdp_gem_cleanup(struct pdp_gem_private *gem_priv)
{
	drm_mm_takedown(&gem_priv->vram);
	mutex_destroy(&gem_priv->vram_lock);

	kfree(gem_priv);
}

struct dma_resv *pdp_gem_get_resv(struct drm_gem_object *obj)
{
	return (to_pdp_obj(obj)->resv);
}

u64 pdp_gem_get_dev_addr(struct drm_gem_object *obj)
{
	struct pdp_gem_object *pdp_obj = to_pdp_obj(obj);

	return pdp_obj->dev_addr;
}

int pdp_gem_object_create_ioctl_priv(struct drm_device *dev,
				struct pdp_gem_private *gem_priv,
				void *data,
				struct drm_file *file)
{
	struct drm_pdp_gem_create *args = data;
	struct drm_gem_object *obj;
	int err;

	if (args->flags) {
		DRM_ERROR("invalid flags: %#08x\n", args->flags);
		return -EINVAL;
	}

	if (args->handle) {
		DRM_ERROR("invalid handle (this should always be 0)\n");
		return -EINVAL;
	}

	obj = pdp_gem_object_create(dev,
					gem_priv,
					PAGE_ALIGN(args->size),
					args->flags);
	if (IS_ERR(obj))
		return PTR_ERR(obj);

	err = drm_gem_handle_create(file, obj, &args->handle);
	drm_gem_object_put(obj);

	return err;

}

int pdp_gem_object_mmap_ioctl(struct drm_device *dev, void *data,
			      struct drm_file *file)
{
	struct drm_pdp_gem_mmap *args = (struct drm_pdp_gem_mmap *)data;

	if (args->pad) {
		DRM_ERROR("invalid pad (this should always be 0)\n");
		return -EINVAL;
	}

	if (args->offset) {
		DRM_ERROR("invalid offset (this should always be 0)\n");
		return -EINVAL;
	}

	return pdp_gem_dumb_map_offset(file, dev, args->handle, &args->offset);
}

int pdp_gem_object_cpu_prep_ioctl(struct drm_device *dev, void *data,
				  struct drm_file *file)
{
	struct drm_pdp_gem_cpu_prep *args = (struct drm_pdp_gem_cpu_prep *)data;
	struct drm_gem_object *obj;
	struct pdp_gem_object *pdp_obj;
	bool write = !!(args->flags & PDP_GEM_CPU_PREP_WRITE);
	bool wait = !(args->flags & PDP_GEM_CPU_PREP_NOWAIT);
	int err = 0;

	if (args->flags & ~(PDP_GEM_CPU_PREP_READ |
			    PDP_GEM_CPU_PREP_WRITE |
			    PDP_GEM_CPU_PREP_NOWAIT)) {
		DRM_ERROR("invalid flags: %#08x\n", args->flags);
		return -EINVAL;
	}

	mutex_lock(&dev->struct_mutex);

	err = pdp_gem_lookup_our_object(file, args->handle, &obj);
	if (err)
		goto exit_unlock;

	pdp_obj = to_pdp_obj(obj);

	if (pdp_obj->cpu_prep) {
		err = -EBUSY;
		goto exit_unref;
	}

	if (wait) {
		long lerr;

		lerr = dma_resv_wait_timeout_rcu(pdp_obj->resv,
						 write,
						 true,
						 30 * HZ);
		if (!lerr)
			err = -EBUSY;
		else if (lerr < 0)
			err = lerr;
	} else {
		if (!dma_resv_test_signaled_rcu(pdp_obj->resv,
						write))
			err = -EBUSY;
	}

	if (!err)
		pdp_obj->cpu_prep = true;

exit_unref:
	drm_gem_object_put(obj);
exit_unlock:
	mutex_unlock(&dev->struct_mutex);
	return err;
}

int pdp_gem_object_cpu_fini_ioctl(struct drm_device *dev, void *data,
				  struct drm_file *file)
{
	struct drm_pdp_gem_cpu_fini *args = (struct drm_pdp_gem_cpu_fini *)data;
	struct drm_gem_object *obj;
	struct pdp_gem_object *pdp_obj;
	int err = 0;

	if (args->pad) {
		DRM_ERROR("invalid pad (this should always be 0)\n");
		return -EINVAL;
	}

	mutex_lock(&dev->struct_mutex);

	err = pdp_gem_lookup_our_object(file, args->handle, &obj);
	if (err)
		goto exit_unlock;

	pdp_obj = to_pdp_obj(obj);

	if (!pdp_obj->cpu_prep) {
		err = -EINVAL;
		goto exit_unref;
	}

	pdp_obj->cpu_prep = false;

exit_unref:
	drm_gem_object_put(obj);
exit_unlock:
	mutex_unlock(&dev->struct_mutex);
	return err;
}
