diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 8fc40317f2b77..bcdca35c9eda5 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -382,6 +382,8 @@ source "drivers/gpu/drm/xlnx/Kconfig"
 
 source "drivers/gpu/drm/gud/Kconfig"
 
+source "drivers/gpu/drm/apu/Kconfig"
+
 config DRM_HYPERV
 	tristate "DRM Support for Hyper-V synthetic video device"
 	depends on DRM && PCI && MMU && HYPERV
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index ad11121548983..f3d8432976558 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -127,4 +127,5 @@ obj-$(CONFIG_DRM_MCDE) += mcde/
 obj-$(CONFIG_DRM_TIDSS) += tidss/
 obj-y			+= xlnx/
 obj-y			+= gud/
+obj-$(CONFIG_DRM_APU) += apu/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
diff --git a/drivers/gpu/drm/apu/Kconfig b/drivers/gpu/drm/apu/Kconfig
new file mode 100644
index 0000000000000..c8471309a0351
--- /dev/null
+++ b/drivers/gpu/drm/apu/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+config DRM_APU
+	tristate "APU (AI Processor Unit)"
+	select REMOTEPROC
+	select DRM_SCHED
+	help
+	  This provides a DRM driver that provides some facilities to
+	  communicate with an accelerated processing unit (APU).
diff --git a/drivers/gpu/drm/apu/Makefile b/drivers/gpu/drm/apu/Makefile
new file mode 100644
index 0000000000000..3e97846b091c9
--- /dev/null
+++ b/drivers/gpu/drm/apu/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+apu_drm-y += apu_drm_drv.o
+apu_drm-y += apu_sched.o
+apu_drm-y += apu_gem.o
+
+obj-$(CONFIG_DRM_APU) += apu_drm.o
diff --git a/drivers/gpu/drm/apu/apu_drm_drv.c b/drivers/gpu/drm/apu/apu_drm_drv.c
new file mode 100644
index 0000000000000..91d8c99e373c0
--- /dev/null
+++ b/drivers/gpu/drm/apu/apu_drm_drv.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2020 BayLibre SAS
+
+#include <linux/dma-map-ops.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/iova.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+
+#include <drm/apu_drm.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_probe_helper.h>
+
+#include <uapi/drm/apu_drm.h>
+
+#include "apu_internal.h"
+
+static LIST_HEAD(apu_devices);
+
+static const struct drm_ioctl_desc ioctls[] = {
+	DRM_IOCTL_DEF_DRV(APU_GEM_NEW, ioctl_gem_new,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(APU_GEM_QUEUE, ioctl_gem_queue,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(APU_GEM_DEQUEUE, ioctl_gem_dequeue,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(APU_GEM_IOMMU_MAP, ioctl_gem_iommu_map,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(APU_GEM_IOMMU_UNMAP, ioctl_gem_iommu_unmap,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(APU_STATE, ioctl_apu_state,
+			  DRM_RENDER_ALLOW),
+};
+
+DEFINE_DRM_GEM_CMA_FOPS(apu_drm_ops);
+
+static struct drm_driver apu_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_SYNCOBJ,
+	.name = "drm_apu",
+	.desc = "APU DRM driver",
+	.date = "20210319",
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+	.ioctls = ioctls,
+	.num_ioctls = ARRAY_SIZE(ioctls),
+	.fops = &apu_drm_ops,
+	DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(drm_gem_cma_dumb_create),
+};
+
+void *apu_drm_priv(struct apu_core *apu_core)
+{
+	return apu_core->priv;
+}
+EXPORT_SYMBOL_GPL(apu_drm_priv);
+
+int apu_drm_reserve_iova(struct apu_core *apu_core, u64 start, u64 size)
+{
+	struct apu_drm *apu_drm = apu_core->apu_drm;
+	struct iova *iova;
+
+	iova = reserve_iova(&apu_drm->iovad, PHYS_PFN(start),
+			    PHYS_PFN(start + size));
+	if (!iova)
+		return -ENOMEM;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(apu_drm_reserve_iova);
+
+static int apu_drm_init_first_core(struct apu_drm *apu_drm,
+				   struct apu_core *apu_core)
+{
+	struct drm_device *drm;
+	struct device *parent;
+	u64 mask;
+
+	drm = apu_drm->drm;
+	parent = apu_core->rproc->dev.parent;
+	drm->dev->iommu_group = parent->iommu_group;
+	apu_drm->domain = iommu_get_domain_for_dev(parent);
+	set_dma_ops(drm->dev, get_dma_ops(parent));
+	mask = dma_get_mask(parent);
+	return dma_coerce_mask_and_coherent(drm->dev, mask);
+}
+
+struct apu_core *apu_drm_register_core(struct rproc *rproc,
+				       struct apu_drm_ops *ops, void *priv)
+{
+	struct apu_drm *apu_drm;
+	struct apu_core *apu_core;
+	int ret;
+
+	list_for_each_entry(apu_drm, &apu_devices, node) {
+		list_for_each_entry(apu_core, &apu_drm->apu_cores, node) {
+			if (apu_core->rproc == rproc) {
+				ret =
+				    apu_drm_init_first_core(apu_drm, apu_core);
+				apu_core->dev = &rproc->dev;
+				apu_core->priv = priv;
+				apu_core->ops = ops;
+
+				ret = apu_drm_job_init(apu_core);
+				if (ret)
+					return NULL;
+
+				return apu_core;
+			}
+		}
+	}
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(apu_drm_register_core);
+
+int apu_drm_unregister_core(void *priv)
+{
+	struct apu_drm *apu_drm;
+	struct apu_core *apu_core;
+
+	list_for_each_entry(apu_drm, &apu_devices, node) {
+		list_for_each_entry(apu_core, &apu_drm->apu_cores, node) {
+			if (apu_core->priv == priv) {
+				apu_sched_fini(apu_core);
+				apu_core->priv = NULL;
+				apu_core->ops = NULL;
+			}
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(apu_drm_unregister_core);
+
+#ifdef CONFIG_OF
+static const struct of_device_id apu_platform_of_match[] = {
+	{ .compatible = "mediatek,apu-drm", },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, apu_platform_of_match);
+#endif
+
+static int apu_platform_probe(struct platform_device *pdev)
+{
+	struct drm_device *drm;
+	struct apu_drm *apu_drm;
+	struct of_phandle_iterator it;
+	int index = 0;
+	u64 iova[2];
+	int ret;
+
+	apu_drm = devm_kzalloc(&pdev->dev, sizeof(*apu_drm), GFP_KERNEL);
+	if (!apu_drm)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&apu_drm->apu_cores);
+
+	of_phandle_iterator_init(&it, pdev->dev.of_node, "remoteproc", NULL, 0);
+	while (of_phandle_iterator_next(&it) == 0) {
+		struct rproc *rproc = rproc_get_by_phandle(it.phandle);
+		struct apu_core *apu_core;
+
+		if (!rproc)
+			return -EPROBE_DEFER;
+
+		apu_core = devm_kzalloc(&pdev->dev, sizeof(*apu_core),
+					GFP_KERNEL);
+		if (!apu_core)
+			return -ENOMEM;
+
+		apu_core->rproc = rproc;
+		apu_core->device_id = index++;
+		apu_core->apu_drm = apu_drm;
+		spin_lock_init(&apu_core->ctx_lock);
+		INIT_LIST_HEAD(&apu_core->requests);
+		list_add(&apu_core->node, &apu_drm->apu_cores);
+	}
+
+	if (of_property_read_variable_u64_array(pdev->dev.of_node, "iova",
+						iova, ARRAY_SIZE(iova),
+						ARRAY_SIZE(iova)) !=
+	    ARRAY_SIZE(iova))
+		return -EINVAL;
+
+	init_iova_domain(&apu_drm->iovad, PAGE_SIZE, PHYS_PFN(iova[0]));
+	apu_drm->iova_limit_pfn = PHYS_PFN(iova[0] + iova[1]) - 1;
+
+	drm = drm_dev_alloc(&apu_drm_driver, &pdev->dev);
+	if (IS_ERR(drm)) {
+		ret = PTR_ERR(drm);
+		return ret;
+	}
+
+	ret = drm_dev_register(drm, 0);
+	if (ret) {
+		drm_dev_put(drm);
+		return ret;
+	}
+
+	drm->dev_private = apu_drm;
+	apu_drm->drm = drm;
+	apu_drm->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, drm);
+
+	list_add(&apu_drm->node, &apu_devices);
+
+	return 0;
+}
+
+static int apu_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm;
+
+	drm = platform_get_drvdata(pdev);
+
+	drm_dev_unregister(drm);
+	drm_dev_put(drm);
+
+	return 0;
+}
+
+static struct platform_driver apu_platform_driver = {
+	.probe = apu_platform_probe,
+	.remove = apu_platform_remove,
+	.driver = {
+		   .name = "apu_drm",
+		   .of_match_table = of_match_ptr(apu_platform_of_match),
+	},
+};
+
+module_platform_driver(apu_platform_driver);
diff --git a/drivers/gpu/drm/apu/apu_gem.c b/drivers/gpu/drm/apu/apu_gem.c
new file mode 100644
index 0000000000000..c867143dab436
--- /dev/null
+++ b/drivers/gpu/drm/apu/apu_gem.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2020 BayLibre SAS
+
+#include <asm/cacheflush.h>
+
+#include <linux/dma-buf.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/iommu.h>
+#include <linux/iova.h>
+#include <linux/mm.h>
+#include <linux/swap.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include <uapi/drm/apu_drm.h>
+
+#include "apu_internal.h"
+
+struct drm_gem_object *apu_gem_create_object(struct drm_device *dev,
+					     size_t size)
+{
+	struct drm_gem_cma_object *cma_obj;
+
+	cma_obj = drm_gem_cma_create(dev, size);
+	if (!cma_obj)
+		return NULL;
+
+	return &cma_obj->base;
+}
+
+int ioctl_gem_new(struct drm_device *dev, void *data,
+		  struct drm_file *file_priv)
+{
+	struct drm_apu_gem_new *args = data;
+	struct drm_gem_cma_object *cma_obj;
+	struct apu_gem_object *apu_obj;
+	struct drm_gem_object *gem_obj;
+	int ret;
+
+	cma_obj = drm_gem_cma_create(dev, args->size);
+	if (IS_ERR(cma_obj))
+		return PTR_ERR(cma_obj);
+
+	gem_obj = &cma_obj->base;
+	apu_obj = to_apu_bo(gem_obj);
+
+	/*
+	 * Save the size of buffer expected by application instead of the
+	 * aligned one.
+	 */
+	apu_obj->size = args->size;
+	apu_obj->offset = 0;
+	apu_obj->iommu_refcount = 0;
+	mutex_init(&apu_obj->mutex);
+
+	ret = drm_gem_handle_create(file_priv, gem_obj, &args->handle);
+	drm_gem_object_put(gem_obj);
+	if (ret) {
+		drm_gem_cma_free_object(gem_obj);
+		return ret;
+	}
+	args->offset = drm_vma_node_offset_addr(&gem_obj->vma_node);
+
+	return 0;
+}
+
+void apu_bo_iommu_unmap(struct apu_drm *apu_drm, struct apu_gem_object *obj)
+{
+	int iova_pfn;
+	int i;
+
+	if (!obj->iommu_sgt)
+		return;
+
+	mutex_lock(&obj->mutex);
+	obj->iommu_refcount--;
+	if (obj->iommu_refcount) {
+		mutex_unlock(&obj->mutex);
+		return;
+	}
+
+	iova_pfn = PHYS_PFN(obj->iova);
+	for (i = 0; i < obj->iommu_sgt->nents; i++) {
+		iommu_unmap(apu_drm->domain, PFN_PHYS(iova_pfn),
+			    PAGE_ALIGN(obj->iommu_sgt->sgl[i].length));
+		iova_pfn += PHYS_PFN(PAGE_ALIGN(obj->iommu_sgt->sgl[i].length));
+	}
+
+	sg_free_table(obj->iommu_sgt);
+	kfree(obj->iommu_sgt);
+
+	free_iova(&apu_drm->iovad, PHYS_PFN(obj->iova));
+	mutex_unlock(&obj->mutex);
+}
+
+static struct sg_table *apu_get_sg_table(struct drm_gem_object *obj)
+{
+	if (obj->funcs)
+		return obj->funcs->get_sg_table(obj);
+	return NULL;
+}
+
+int apu_bo_iommu_map(struct apu_drm *apu_drm, struct drm_gem_object *obj)
+{
+	struct apu_gem_object *apu_obj = to_apu_bo(obj);
+	struct scatterlist *sgl;
+	phys_addr_t phys;
+	int total_buf_space;
+	int iova_pfn;
+	int iova;
+	int ret;
+	int i;
+
+	mutex_lock(&apu_obj->mutex);
+	apu_obj->iommu_refcount++;
+	if (apu_obj->iommu_refcount != 1) {
+		mutex_unlock(&apu_obj->mutex);
+		return 0;
+	}
+
+	apu_obj->iommu_sgt = apu_get_sg_table(obj);
+	if (IS_ERR(apu_obj->iommu_sgt)) {
+		mutex_unlock(&apu_obj->mutex);
+		return PTR_ERR(apu_obj->iommu_sgt);
+	}
+
+	total_buf_space = obj->size;
+	iova_pfn = alloc_iova_fast(&apu_drm->iovad,
+				   total_buf_space >> PAGE_SHIFT,
+				   apu_drm->iova_limit_pfn, true);
+	apu_obj->iova = PFN_PHYS(iova_pfn);
+
+	if (!iova_pfn) {
+		dev_err(apu_drm->dev, "Failed to allocate iova address\n");
+		mutex_unlock(&apu_obj->mutex);
+		return -ENOMEM;
+	}
+
+	iova = apu_obj->iova;
+	sgl = apu_obj->iommu_sgt->sgl;
+	for (i = 0; i < apu_obj->iommu_sgt->nents; i++) {
+		phys = page_to_phys(sg_page(&sgl[i]));
+		ret =
+		    iommu_map(apu_drm->domain, PFN_PHYS(iova_pfn), phys,
+			      PAGE_ALIGN(sgl[i].length),
+			      IOMMU_READ | IOMMU_WRITE);
+		if (ret) {
+			dev_err(apu_drm->dev, "Failed to iommu map\n");
+			free_iova(&apu_drm->iovad, iova_pfn);
+			mutex_unlock(&apu_obj->mutex);
+			return ret;
+		}
+		iova += sgl[i].offset + sgl[i].length;
+		iova_pfn += PHYS_PFN(PAGE_ALIGN(sgl[i].length));
+	}
+	mutex_unlock(&apu_obj->mutex);
+
+	return 0;
+}
+
+int ioctl_gem_iommu_map(struct drm_device *dev, void *data,
+			struct drm_file *file_priv)
+{
+	struct apu_drm *apu_drm = dev->dev_private;
+	struct drm_apu_gem_iommu_map *args = data;
+	struct drm_gem_object **bos;
+	void __user *bo_handles;
+	int ret;
+	int i;
+
+	u64 *das = kvmalloc_array(args->bo_handle_count,
+				  sizeof(u64), GFP_KERNEL);
+	if (!das)
+		return -ENOMEM;
+
+	bo_handles = (void __user *)(uintptr_t) args->bo_handles;
+	ret = drm_gem_objects_lookup(file_priv, bo_handles,
+				     args->bo_handle_count, &bos);
+	if (ret) {
+		kvfree(das);
+		return ret;
+	}
+
+	for (i = 0; i < args->bo_handle_count; i++) {
+		ret = apu_bo_iommu_map(apu_drm, bos[i]);
+		if (ret) {
+			/* TODO: handle error */
+			break;
+		}
+		das[i] = to_apu_bo(bos[i])->iova + to_apu_bo(bos[i])->offset;
+	}
+
+	if (copy_to_user((void *)args->bo_device_addresses, das,
+			 args->bo_handle_count * sizeof(u64))) {
+		ret = -EFAULT;
+		DRM_DEBUG("Failed to copy device addresses\n");
+		goto out;
+	}
+
+out:
+	kvfree(das);
+	kvfree(bos);
+
+	return 0;
+}
+
+int ioctl_gem_iommu_unmap(struct drm_device *dev, void *data,
+			  struct drm_file *file_priv)
+{
+	struct apu_drm *apu_drm = dev->dev_private;
+	struct drm_apu_gem_iommu_map *args = data;
+	struct drm_gem_object **bos;
+	void __user *bo_handles;
+	int ret;
+	int i;
+
+	bo_handles = (void __user *)(uintptr_t) args->bo_handles;
+	ret = drm_gem_objects_lookup(file_priv, bo_handles,
+				     args->bo_handle_count, &bos);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < args->bo_handle_count; i++)
+		apu_bo_iommu_unmap(apu_drm, to_apu_bo(bos[i]));
+
+	kvfree(bos);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apu/apu_internal.h b/drivers/gpu/drm/apu/apu_internal.h
new file mode 100644
index 0000000000000..b789b2f3ad9c6
--- /dev/null
+++ b/drivers/gpu/drm/apu/apu_internal.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __APU_INTERNAL_H__
+#define __APU_INTERNAL_H__
+
+#include <linux/iova.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/gpu_scheduler.h>
+
+struct apu_gem_object {
+	struct drm_gem_cma_object base;
+	struct mutex mutex;
+	struct sg_table *iommu_sgt;
+	int iommu_refcount;
+	size_t size;
+	u32 iova;
+	u32 offset;
+};
+
+struct apu_sched;
+struct apu_core {
+	int device_id;
+	struct device *dev;
+	struct rproc *rproc;
+	struct apu_drm_ops *ops;
+	struct apu_drm *apu_drm;
+
+	spinlock_t ctx_lock;
+	struct list_head requests;
+
+	struct list_head node;
+	void *priv;
+
+	struct apu_sched *sched;
+	u32 flags;
+};
+
+struct apu_drm {
+	struct device *dev;
+	struct drm_device *drm;
+
+	struct iommu_domain *domain;
+	struct iova_domain iovad;
+	int iova_limit_pfn;
+
+	struct list_head apu_cores;
+	struct list_head node;
+};
+
+static inline struct apu_gem_object *to_apu_bo(struct drm_gem_object *obj)
+{
+	return container_of(to_drm_gem_cma_obj(obj), struct apu_gem_object,
+			    base);
+}
+
+struct apu_gem_object *to_apu_bo(struct drm_gem_object *obj);
+struct drm_gem_object *apu_gem_create_object(struct drm_device *dev,
+					     size_t size);
+
+int apu_bo_iommu_map(struct apu_drm *apu_drm, struct drm_gem_object *obj);
+void apu_bo_iommu_unmap(struct apu_drm *apu_drm, struct apu_gem_object *obj);
+struct drm_gem_object *apu_gem_create_object(struct drm_device *dev,
+					     size_t size);
+int ioctl_gem_new(struct drm_device *dev, void *data,
+		  struct drm_file *file_priv);
+int ioctl_gem_user_new(struct drm_device *dev, void *data,
+		       struct drm_file *file_priv);
+int ioctl_gem_iommu_map(struct drm_device *dev, void *data,
+			struct drm_file *file_priv);
+int ioctl_gem_iommu_unmap(struct drm_device *dev, void *data,
+			  struct drm_file *file_priv);
+int ioctl_gem_queue(struct drm_device *dev, void *data,
+		    struct drm_file *file_priv);
+int ioctl_gem_dequeue(struct drm_device *dev, void *data,
+		      struct drm_file *file_priv);
+int ioctl_apu_state(struct drm_device *dev, void *data,
+		    struct drm_file *file_priv);
+struct dma_buf *apu_gem_prime_export(struct drm_gem_object *gem,
+				     int flags);
+
+struct apu_job;
+
+int apu_drm_job_init(struct apu_core *core);
+void apu_sched_fini(struct apu_core *core);
+int apu_job_push(struct apu_job *job);
+void apu_job_put(struct apu_job *job);
+
+#endif /* __APU_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/apu/apu_sched.c b/drivers/gpu/drm/apu/apu_sched.c
new file mode 100644
index 0000000000000..cebb0155c7783
--- /dev/null
+++ b/drivers/gpu/drm/apu/apu_sched.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2020 BayLibre SAS
+
+#include <drm/apu_drm.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_syncobj.h>
+#include <drm/gpu_scheduler.h>
+
+#include <uapi/drm/apu_drm.h>
+
+#include "apu_internal.h"
+
+struct apu_queue_state {
+	struct drm_gpu_scheduler sched;
+
+	u64 fence_context;
+	u64 seqno;
+};
+
+struct apu_request {
+	struct list_head node;
+	void *job;
+};
+
+struct apu_sched {
+	struct apu_queue_state apu_queue;
+	spinlock_t job_lock;
+	struct drm_sched_entity sched_entity;
+};
+
+struct apu_event {
+	struct drm_pending_event pending_event;
+	union {
+		struct drm_event base;
+		struct apu_job_event job_event;
+	};
+};
+
+struct apu_job {
+	struct drm_sched_job base;
+
+	struct kref refcount;
+
+	struct apu_core *apu_core;
+	struct apu_drm *apu_drm;
+
+	/* Fence to be signaled by IRQ handler when the job is complete. */
+	struct dma_fence *done_fence;
+
+	__u32 cmd;
+
+	/* Exclusive fences we have taken from the BOs to wait for */
+	struct dma_fence **implicit_fences;
+	struct drm_gem_object **bos;
+	u32 bo_count;
+
+	/* Fence to be signaled by drm-sched once its done with the job */
+	struct dma_fence *render_done_fence;
+
+	void *data_in;
+	uint16_t size_in;
+	void *data_out;
+	uint16_t size_out;
+	uint16_t result;
+	uint16_t id;
+
+	struct list_head node;
+	struct drm_syncobj *sync_out;
+
+	struct apu_event *event;
+};
+
+static DEFINE_IDA(req_ida);
+static LIST_HEAD(complete_node);
+
+int apu_drm_callback(struct apu_core *apu_core, void *data, int len)
+{
+	struct apu_request *apu_req, *tmp;
+	struct apu_dev_request *hdr = data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&apu_core->ctx_lock, flags);
+	list_for_each_entry_safe(apu_req, tmp, &apu_core->requests, node) {
+		struct apu_job *job = apu_req->job;
+
+		if (job && hdr->id == job->id) {
+			kref_get(&job->refcount);
+			job->result = hdr->result;
+			if (job->size_out)
+				memcpy(job->data_out, hdr->data + job->size_in,
+				       min(job->size_out, hdr->size_out));
+			job->size_out = hdr->size_out;
+			list_add(&job->node, &complete_node);
+			list_del(&apu_req->node);
+			ida_simple_remove(&req_ida, hdr->id);
+			kfree(apu_req);
+			drm_send_event(job->apu_drm->drm,
+				       &job->event->pending_event);
+			dma_fence_signal_locked(job->done_fence);
+		}
+	}
+	spin_unlock_irqrestore(&apu_core->ctx_lock, flags);
+
+	return 0;
+}
+
+void apu_sched_fini(struct apu_core *core)
+{
+	drm_sched_fini(&core->sched->apu_queue.sched);
+	devm_kfree(core->dev, core->sched);
+	core->flags &= ~APU_ONLINE;
+	core->sched = NULL;
+}
+
+static void apu_job_cleanup(struct kref *ref)
+{
+	struct apu_job *job = container_of(ref, struct apu_job,
+					   refcount);
+	unsigned int i;
+
+	if (job->implicit_fences) {
+		for (i = 0; i < job->bo_count; i++)
+			dma_fence_put(job->implicit_fences[i]);
+		kvfree(job->implicit_fences);
+	}
+	dma_fence_put(job->done_fence);
+	dma_fence_put(job->render_done_fence);
+
+	if (job->bos) {
+		for (i = 0; i < job->bo_count; i++) {
+			struct apu_gem_object *apu_obj;
+
+			apu_obj = to_apu_bo(job->bos[i]);
+			apu_bo_iommu_unmap(job->apu_drm, apu_obj);
+			drm_gem_object_put(job->bos[i]);
+		}
+
+		kvfree(job->bos);
+	}
+
+	kfree(job->data_out);
+	kfree(job->data_in);
+	kfree(job);
+}
+
+void apu_job_put(struct apu_job *job)
+{
+	kref_put(&job->refcount, apu_job_cleanup);
+}
+
+static void apu_acquire_object_fences(struct drm_gem_object **bos,
+				      int bo_count,
+				      struct dma_fence **implicit_fences)
+{
+	int i;
+
+	for (i = 0; i < bo_count; i++)
+		implicit_fences[i] = dma_resv_get_excl_unlocked(bos[i]->resv);
+}
+
+static void apu_attach_object_fences(struct drm_gem_object **bos,
+				     int bo_count, struct dma_fence *fence)
+{
+	int i;
+
+	for (i = 0; i < bo_count; i++)
+		dma_resv_add_excl_fence(bos[i]->resv, fence);
+}
+
+int apu_job_push(struct apu_job *job)
+{
+	struct drm_sched_entity *entity = &job->apu_core->sched->sched_entity;
+	struct ww_acquire_ctx acquire_ctx;
+	int ret = 0;
+
+	ret = drm_gem_lock_reservations(job->bos, job->bo_count, &acquire_ctx);
+	if (ret)
+		return ret;
+
+	ret = drm_sched_job_init(&job->base, entity, NULL);
+	if (ret)
+		goto unlock;
+
+	job->render_done_fence = dma_fence_get(&job->base.s_fence->finished);
+
+	kref_get(&job->refcount);	/* put by scheduler job completion */
+
+	apu_acquire_object_fences(job->bos, job->bo_count,
+				  job->implicit_fences);
+
+	drm_sched_entity_push_job(&job->base, entity);
+
+	apu_attach_object_fences(job->bos, job->bo_count,
+				 job->render_done_fence);
+
+unlock:
+	drm_gem_unlock_reservations(job->bos, job->bo_count, &acquire_ctx);
+
+	return ret;
+}
+
+static const char *apu_fence_get_driver_name(struct dma_fence *fence)
+{
+	return "apu";
+}
+
+static const char *apu_fence_get_timeline_name(struct dma_fence *fence)
+{
+	return "apu-0";
+}
+
+static void apu_fence_release(struct dma_fence *f)
+{
+	kfree(f);
+}
+
+static const struct dma_fence_ops apu_fence_ops = {
+	.get_driver_name = apu_fence_get_driver_name,
+	.get_timeline_name = apu_fence_get_timeline_name,
+	.release = apu_fence_release,
+};
+
+static struct dma_fence *apu_fence_create(struct apu_sched *sched)
+{
+	struct dma_fence *fence;
+	struct apu_queue_state *apu_queue = &sched->apu_queue;
+
+	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
+	if (!fence)
+		return ERR_PTR(-ENOMEM);
+
+	dma_fence_init(fence, &apu_fence_ops, &sched->job_lock,
+		       apu_queue->fence_context, apu_queue->seqno++);
+
+	return fence;
+}
+
+static struct apu_job *to_apu_job(struct drm_sched_job *sched_job)
+{
+	return container_of(sched_job, struct apu_job, base);
+}
+
+static struct dma_fence *apu_job_dependency(struct drm_sched_job *sched_job,
+					    struct drm_sched_entity *s_entity)
+{
+	struct apu_job *job = to_apu_job(sched_job);
+	struct dma_fence *fence;
+	unsigned int i;
+
+	/* Implicit fences, max. one per BO */
+	for (i = 0; i < job->bo_count; i++) {
+		if (job->implicit_fences[i]) {
+			fence = job->implicit_fences[i];
+			job->implicit_fences[i] = NULL;
+			return fence;
+		}
+	}
+
+	return NULL;
+}
+
+static int apu_job_hw_submit(struct apu_job *job)
+{
+	int ret;
+	struct apu_core *apu_core = job->apu_core;
+	struct apu_dev_request *dev_req;
+	struct apu_request *apu_req;
+	unsigned long flags;
+
+	int size = sizeof(*dev_req) + sizeof(u32) * job->bo_count * 2;
+	u32 *dev_req_da;
+	u32 *dev_req_buffer_size;
+	int i;
+
+	dev_req = kmalloc(size + job->size_in + job->size_out, GFP_KERNEL);
+	if (!dev_req)
+		return -ENOMEM;
+
+	dev_req->cmd = job->cmd;
+	dev_req->size_in = job->size_in;
+	dev_req->size_out = job->size_out;
+	dev_req->count = job->bo_count;
+	dev_req_da =
+	    (u32 *) (dev_req->data + dev_req->size_in + dev_req->size_out);
+	dev_req_buffer_size = (u32 *) (dev_req_da + dev_req->count);
+	memcpy(dev_req->data, job->data_in, job->size_in);
+
+	apu_req = kzalloc(sizeof(*apu_req), GFP_KERNEL);
+	if (!apu_req)
+		return -ENOMEM;
+
+	for (i = 0; i < job->bo_count; i++) {
+		struct apu_gem_object *obj = to_apu_bo(job->bos[i]);
+
+		dev_req_da[i] = obj->iova + obj->offset;
+		dev_req_buffer_size[i] = obj->size;
+	}
+
+	ret = ida_simple_get(&req_ida, 0, 0xffff, GFP_KERNEL);
+	if (ret < 0)
+		goto err_free_memory;
+
+	dev_req->id = ret;
+
+	job->id = dev_req->id;
+	apu_req->job = job;
+	spin_lock_irqsave(&apu_core->ctx_lock, flags);
+	list_add(&apu_req->node, &apu_core->requests);
+	spin_unlock_irqrestore(&apu_core->ctx_lock, flags);
+	ret =
+	    apu_core->ops->send(apu_core, dev_req,
+				size + dev_req->size_in + dev_req->size_out);
+	if (ret < 0)
+		goto err;
+	kfree(dev_req);
+
+	return 0;
+
+err:
+	list_del(&apu_req->node);
+	ida_simple_remove(&req_ida, dev_req->id);
+err_free_memory:
+	kfree(apu_req);
+	kfree(dev_req);
+
+	return ret;
+}
+
+static struct dma_fence *apu_job_run(struct drm_sched_job *sched_job)
+{
+	struct apu_job *job = to_apu_job(sched_job);
+	struct dma_fence *fence = NULL;
+
+	if (unlikely(job->base.s_fence->finished.error))
+		return NULL;
+
+	fence = apu_fence_create(job->apu_core->sched);
+	if (IS_ERR(fence))
+		return NULL;
+
+	job->done_fence = dma_fence_get(fence);
+
+	apu_job_hw_submit(job);
+
+	return fence;
+}
+
+static void apu_update_rpoc_state(struct apu_core *core)
+{
+	if (core->rproc) {
+		if (core->rproc->state == RPROC_CRASHED)
+			core->flags |= APU_CRASHED;
+		if (core->rproc->state == RPROC_OFFLINE)
+			core->flags &= ~APU_ONLINE;
+	}
+}
+
+static enum drm_gpu_sched_stat apu_job_timedout(struct drm_sched_job *sched_job)
+{
+	struct apu_request *apu_req, *tmp;
+	struct apu_job *job = to_apu_job(sched_job);
+
+	if (dma_fence_is_signaled(job->done_fence))
+		return DRM_GPU_SCHED_STAT_NOMINAL;
+
+	list_for_each_entry_safe(apu_req, tmp, &job->apu_core->requests, node) {
+		/* Remove the request and notify user about timeout */
+		if (apu_req->job == job) {
+			kref_get(&job->refcount);
+			job->apu_core->flags |= APU_TIMEDOUT;
+			apu_update_rpoc_state(job->apu_core);
+			job->result = ETIMEDOUT;
+			list_add(&job->node, &complete_node);
+			list_del(&apu_req->node);
+			ida_simple_remove(&req_ida, job->id);
+			kfree(apu_req);
+			drm_send_event(job->apu_drm->drm,
+				       &job->event->pending_event);
+			dma_fence_signal_locked(job->done_fence);
+		}
+	}
+
+	return DRM_GPU_SCHED_STAT_NOMINAL;
+}
+
+static void apu_job_free(struct drm_sched_job *sched_job)
+{
+	struct apu_job *job = to_apu_job(sched_job);
+
+	drm_sched_job_cleanup(sched_job);
+
+	apu_job_put(job);
+}
+
+static const struct drm_sched_backend_ops apu_sched_ops = {
+	.dependency = apu_job_dependency,
+	.run_job = apu_job_run,
+	.timedout_job = apu_job_timedout,
+	.free_job = apu_job_free
+};
+
+int apu_drm_job_init(struct apu_core *core)
+{
+	int ret;
+	struct apu_sched *apu_sched;
+	struct drm_gpu_scheduler *sched;
+
+	apu_sched = devm_kzalloc(core->dev, sizeof(*apu_sched), GFP_KERNEL);
+	if (!apu_sched)
+		return -ENOMEM;
+
+	sched = &apu_sched->apu_queue.sched;
+	apu_sched->apu_queue.fence_context = dma_fence_context_alloc(1);
+	ret = drm_sched_init(sched, &apu_sched_ops,
+			     1, 0, msecs_to_jiffies(500),
+			     NULL, NULL, "apu_js");
+	if (ret) {
+		dev_err(core->dev, "Failed to create scheduler: %d.", ret);
+		return ret;
+	}
+
+	ret = drm_sched_entity_init(&apu_sched->sched_entity,
+				    DRM_SCHED_PRIORITY_NORMAL,
+				    &sched, 1, NULL);
+
+	core->sched = apu_sched;
+	core->flags = APU_ONLINE;
+
+	return ret;
+}
+
+static struct apu_core *get_apu_core(struct apu_drm *apu_drm, int device_id)
+{
+	struct apu_core *apu_core;
+
+	list_for_each_entry(apu_core, &apu_drm->apu_cores, node) {
+		if (apu_core->device_id == device_id)
+			return apu_core;
+	}
+
+	return NULL;
+}
+
+static int apu_core_is_running(struct apu_core *core)
+{
+	return core->ops && core->priv && core->sched;
+}
+
+static int
+apu_lookup_bos(struct drm_device *dev,
+	       struct drm_file *file_priv,
+	       struct drm_apu_gem_queue *args, struct apu_job *job)
+{
+	void __user *bo_handles;
+	unsigned int i;
+	int ret;
+
+	job->bo_count = args->bo_handle_count;
+
+	if (!job->bo_count)
+		return 0;
+
+	job->implicit_fences = kvmalloc_array(job->bo_count,
+					      sizeof(struct dma_fence *),
+					      GFP_KERNEL | __GFP_ZERO);
+	if (!job->implicit_fences)
+		return -ENOMEM;
+
+	bo_handles = (void __user *)(uintptr_t) args->bo_handles;
+	ret = drm_gem_objects_lookup(file_priv, bo_handles,
+				     job->bo_count, &job->bos);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < job->bo_count; i++) {
+		ret = apu_bo_iommu_map(job->apu_drm, job->bos[i]);
+		if (ret) {
+			/* TODO: handle error */
+			break;
+		}
+	}
+
+	return ret;
+}
+
+int ioctl_gem_queue(struct drm_device *dev, void *data,
+		    struct drm_file *file_priv)
+{
+	struct apu_drm *apu_drm = dev->dev_private;
+	struct drm_apu_gem_queue *args = data;
+	struct apu_event *event;
+	struct apu_core *core;
+	struct drm_syncobj *sync_out = NULL;
+	struct apu_job *job;
+	int ret = 0;
+
+	core = get_apu_core(apu_drm, args->device);
+	if (!apu_core_is_running(core))
+		return -ENODEV;
+
+	if (args->out_sync > 0) {
+		sync_out = drm_syncobj_find(file_priv, args->out_sync);
+		if (!sync_out)
+			return -ENODEV;
+	}
+
+	job = kzalloc(sizeof(*job), GFP_KERNEL);
+	if (!job) {
+		ret = -ENOMEM;
+		goto fail_out_sync;
+	}
+
+	kref_init(&job->refcount);
+
+	job->apu_drm = apu_drm;
+	job->apu_core = core;
+	job->cmd = args->cmd;
+	job->size_in = args->size_in;
+	job->size_out = args->size_out;
+	job->sync_out = sync_out;
+	if (job->size_in) {
+		job->data_in = kmalloc(job->size_in, GFP_KERNEL);
+		if (!job->data_in) {
+			ret = -ENOMEM;
+			goto fail_job;
+		}
+
+		ret =
+		    copy_from_user(job->data_in,
+				   (void __user *)(uintptr_t) args->data,
+				   job->size_in);
+		if (ret)
+			goto fail_job;
+	}
+
+	if (job->size_out) {
+		job->data_out = kmalloc(job->size_out, GFP_KERNEL);
+		if (!job->data_out) {
+			ret = -ENOMEM;
+			goto fail_job;
+		}
+	}
+
+	ret = apu_lookup_bos(dev, file_priv, args, job);
+	if (ret)
+		goto fail_job;
+
+	event = kzalloc(sizeof(*event), GFP_KERNEL);
+	event->base.length = sizeof(struct apu_job_event);
+	event->base.type = APU_JOB_COMPLETED;
+	event->job_event.out_sync = args->out_sync;
+	job->event = event;
+	ret = drm_event_reserve_init(dev, file_priv, &job->event->pending_event,
+				     &job->event->base);
+	if (ret)
+		goto fail_job;
+
+	ret = apu_job_push(job);
+	if (ret) {
+		drm_event_cancel_free(dev, &job->event->pending_event);
+		goto fail_job;
+	}
+
+	/* Update the return sync object for the job */
+	if (sync_out)
+		drm_syncobj_replace_fence(sync_out, job->render_done_fence);
+
+fail_job:
+	apu_job_put(job);
+fail_out_sync:
+	if (sync_out)
+		drm_syncobj_put(sync_out);
+
+	return ret;
+}
+
+int ioctl_gem_dequeue(struct drm_device *dev, void *data,
+		      struct drm_file *file_priv)
+{
+	struct drm_apu_gem_dequeue *args = data;
+	struct drm_syncobj *sync_out = NULL;
+	struct apu_job *job;
+	int ret = 0;
+
+	if (args->out_sync > 0) {
+		sync_out = drm_syncobj_find(file_priv, args->out_sync);
+		if (!sync_out)
+			return -ENODEV;
+	}
+
+	list_for_each_entry(job, &complete_node, node) {
+		if (job->sync_out == sync_out) {
+			if (job->data_out) {
+				ret = copy_to_user((void __user *)(uintptr_t)
+						   args->data, job->data_out,
+						   job->size_out);
+				args->size = job->size_out;
+			}
+			args->result = job->result;
+			list_del(&job->node);
+			apu_job_put(job);
+			drm_syncobj_put(sync_out);
+
+			return ret;
+		}
+	}
+
+	if (sync_out)
+		drm_syncobj_put(sync_out);
+
+	return 0;
+}
+
+int ioctl_apu_state(struct drm_device *dev, void *data,
+		    struct drm_file *file_priv)
+{
+	struct apu_drm *apu_drm = dev->dev_private;
+	struct drm_apu_state *args = data;
+	struct apu_core *core;
+
+	args->flags = 0;
+
+	core = get_apu_core(apu_drm, args->device);
+	if (!core)
+		return -ENODEV;
+	args->flags |= core->flags;
+
+	/* Reset APU flags */
+	core->flags &= ~(APU_TIMEDOUT | APU_CRASHED);
+
+	return 0;
+}
diff --git a/include/drm/apu_drm.h b/include/drm/apu_drm.h
new file mode 100644
index 0000000000000..f044ed0427fdd
--- /dev/null
+++ b/include/drm/apu_drm.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __APU_DRM_H__
+#define __APU_DRM_H__
+
+#include <linux/iova.h>
+#include <linux/remoteproc.h>
+
+struct apu_core;
+struct apu_drm;
+
+struct apu_drm_ops {
+	int (*send)(struct apu_core *apu_core, void *data, int len);
+	int (*callback)(struct apu_core *apu_core, void *data, int len);
+};
+
+#ifdef CONFIG_DRM_APU
+
+struct apu_core *apu_drm_register_core(struct rproc *rproc,
+				       struct apu_drm_ops *ops, void *priv);
+int apu_drm_reserve_iova(struct apu_core *apu_core, u64 start, u64 size);
+int apu_drm_unregister_core(void *priv);
+int apu_drm_callback(struct apu_core *apu_core, void *data, int len);
+void *apu_drm_priv(struct apu_core *apu_core);
+
+#else /* CONFIG_DRM_APU */
+
+static inline
+struct apu_core *apu_drm_register_core(struct rproc *rproc,
+				       struct apu_drm_ops *ops, void *priv)
+{
+	return NULL;
+}
+
+static inline
+int apu_drm_reserve_iova(struct apu_core *apu_core, u64 start, u64 size)
+{
+	return -ENOMEM;
+}
+
+static inline
+int apu_drm_uregister_core(void *priv)
+{
+	return -ENODEV;
+}
+
+static inline
+int apu_drm_callback(struct apu_core *apu_core, void *data, int len)
+{
+	return -ENODEV;
+}
+
+static inline void *apu_drm_priv(struct apu_core *apu_core)
+{
+	return NULL;
+}
+#endif /* CONFIG_DRM_APU */
+
+
+#endif /* __APU_DRM_H__ */
diff --git a/include/uapi/drm/apu_drm.h b/include/uapi/drm/apu_drm.h
new file mode 100644
index 0000000000000..c52e187bb0599
--- /dev/null
+++ b/include/uapi/drm/apu_drm.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __UAPI_APU_DRM_H__
+#define __UAPI_APU_DRM_H__
+
+#include "drm.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define APU_JOB_COMPLETED 0x80000000
+
+/*
+ * Please note that modifications to all structs defined here are
+ * subject to backwards-compatibility constraints.
+ */
+
+/*
+ * Firmware request, must be aligned with the one defined in firmware.
+ * @id: Request id, used in the case of reply, to find the pending request
+ * @cmd: The command id to execute in the firmware
+ * @result: The result of the command executed on the firmware
+ * @size: The size of the data available in this request
+ * @count: The number of shared buffer
+ * @data: Contains the data attached with the request if size is greater than
+ *        zero, and the addresses of shared buffers if count is greater than
+ *        zero. Both the data and the shared buffer could be read and write
+ *        by the APU.
+ */
+struct  apu_dev_request {
+	u16 id;
+	u16 cmd;
+	u16 result;
+	u16 size_in;
+	u16 size_out;
+	u16 count;
+	u8 data[0];
+} __packed;
+
+struct drm_apu_gem_new {
+	__u32 size;			/* in */
+	__u32 flags;			/* in */
+	__u32 handle;			/* out */
+	__u64 offset;			/* out */
+};
+
+struct drm_apu_gem_queue {
+	__u32 device;
+	__u32 cmd;
+	__u32 out_sync;
+	__u64 bo_handles;
+	__u32 bo_handle_count;
+	__u16 size_in;
+	__u16 size_out;
+	__u64 data;
+};
+
+struct drm_apu_gem_dequeue {
+	__u32 out_sync;
+	__u16 result;
+	__u16 size;
+	__u64 data;
+};
+
+struct drm_apu_gem_iommu_map {
+	__u64 bo_handles;
+	__u32 bo_handle_count;
+	__u64 bo_device_addresses;
+};
+
+struct apu_job_event {
+	struct drm_event base;
+	__u32 out_sync;
+};
+
+#define APU_ONLINE		BIT(0)
+#define APU_CRASHED		BIT(1)
+#define APU_TIMEDOUT		BIT(2)
+
+struct drm_apu_state {
+	__u32 device;
+	__u32 flags;
+};
+
+#define DRM_APU_GEM_NEW			0x00
+#define DRM_APU_GEM_QUEUE		0x01
+#define DRM_APU_GEM_DEQUEUE		0x02
+#define DRM_APU_GEM_IOMMU_MAP		0x03
+#define DRM_APU_GEM_IOMMU_UNMAP		0x04
+#define DRM_APU_STATE			0x05
+#define DRM_APU_NUM_IOCTLS		0x06
+
+#define DRM_IOCTL_APU_GEM_NEW		DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_NEW, struct drm_apu_gem_new)
+#define DRM_IOCTL_APU_GEM_USER_NEW	DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_USER_NEW, struct drm_apu_gem_user_new)
+#define DRM_IOCTL_APU_GEM_QUEUE		DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_QUEUE, struct drm_apu_gem_queue)
+#define DRM_IOCTL_APU_GEM_DEQUEUE	DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_DEQUEUE, struct drm_apu_gem_dequeue)
+#define DRM_IOCTL_APU_GEM_IOMMU_MAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_IOMMU_MAP, struct drm_apu_gem_iommu_map)
+#define DRM_IOCTL_APU_GEM_IOMMU_UNMAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_GEM_IOMMU_UNMAP, struct drm_apu_gem_iommu_map)
+#define DRM_IOCTL_APU_STATE		DRM_IOWR(DRM_COMMAND_BASE + DRM_APU_STATE, struct drm_apu_state)
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __UAPI_APU_DRM_H__ */
