@@ -371,6 +371,8 @@ source "drivers/gpu/drm/solomon/Kconfig"
source "drivers/gpu/drm/sprd/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
@@ -191,6 +191,7 @@ 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/
obj-y += solomon/
obj-$(CONFIG_DRM_SPRD) += sprd/
new file mode 100644
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+config DRM_APU
+ tristate "APU (AI Processor Unit)"
+ select DRM_GEM_DMA_HELPER
+ select DRM_KMS_HELPER
+ help
+ This provides a DRM driver that provides some facilities to
+ communicate with an AI Processor Unit (APU).
+ The driver intends to provide a common infrastructure that may be
+ used to support many different APU.
new file mode 100644
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+drm_apu-y += apu_drv.o
+
+obj-$(CONFIG_DRM_APU) += drm_apu.o
new file mode 100644
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2020 BayLibre SAS
+
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include <drm/apu_drm.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_probe_helper.h>
+
+#include "apu_internal.h"
+
+static LIST_HEAD(apu_devices);
+
+static int ioctl_apu_state(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+static const struct drm_ioctl_desc ioctls[] = {
+ DRM_IOCTL_DEF_DRV(APU_STATE, ioctl_apu_state,
+ DRM_RENDER_ALLOW),
+};
+
+DEFINE_DRM_GEM_DMA_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_DMA_DRIVER_OPS_WITH_DUMB_CREATE(drm_gem_dma_dumb_create),
+};
+
+/**
+ * apu_dev_alloc() - Allocate a new APU device
+ *
+ * @dev: Pointer to the device instance.
+
+ * This allocate an APU device.
+ * The APU describe a hardware accelerator that may have one or more
+ * core (or unit).
+ *
+ * Returns: A pointer or NULL in case of failure.
+ */
+struct apu_drm *apu_dev_alloc(struct device *dev)
+{
+ struct drm_device *drm;
+ struct apu_drm *apu;
+
+ apu = devm_drm_dev_alloc(dev, &apu_drm_driver, typeof(*apu), base);
+ if (IS_ERR(apu))
+ return NULL;
+ INIT_LIST_HEAD(&apu->cores);
+
+ apu->dev = dev;
+ ida_init(&apu->ida);
+ drm = &apu->base;
+ drm->dev_private = apu;
+
+ dev_set_drvdata(dev, drm);
+
+ return apu;
+}
+EXPORT_SYMBOL_GPL(apu_dev_alloc);
+
+/**
+ * apu_dev_register() - Register the APU to DRM
+ *
+ * @apu: Pointer to APU device
+ *
+ * Register an APU device to DRM.
+ * On success, this creates everything required to use the APU.
+ * Note that at this step, the cores (or units) have not been
+ * registered so we can't yet perform any operations.
+ *
+ * Returns: Zero on success, non-zero value on failure.
+ */
+int apu_dev_register(struct apu_drm *apu)
+{
+ struct drm_device *drm = &apu->base;
+ int ret;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ return ret;
+
+ list_add(&apu->node, &apu_devices);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(apu_dev_register);
+
+/**
+ * apu_dev_unregister() - Unregister the APU
+ *
+ * @apu: Pointer to APU device
+ *
+ * This undo what has been done by apu_dev_register();
+ */
+void apu_dev_unregister(struct apu_drm *apu)
+{
+ struct drm_device *drm = &apu->base;
+
+ list_del(&apu->node);
+ drm_dev_unregister(drm);
+}
+EXPORT_SYMBOL_GPL(apu_dev_unregister);
+
+/**
+ * apu_core_alloc() - Allocate an APU core
+ *
+ * @apu: Pointer to APU device
+ * @ops: The operation callbacks to use for this core
+ * @priv:
+ *
+ * Allocate an APU core. This represents a computing unit that could
+ * execute a job. The APU may be composed of different units that doesn't
+ * accept same kind of jobs so we may to use differents callbacks for each
+ * core.
+ *
+ * Returns: A pointer or NULL in case of failure.
+ */
+struct apu_core *apu_core_alloc(struct apu_drm *apu, struct apu_core_ops *ops,
+ void *priv)
+{
+ struct apu_core *core;
+
+ if (!ops || !ops->is_ready)
+ return NULL;
+
+ core = devm_kzalloc(apu->dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return NULL;
+
+ core->device_id = ida_alloc(&apu->ida, GFP_KERNEL);
+ if (core->device_id < 0)
+ return NULL;
+
+ core->apu = apu;
+ core->priv = priv;
+ core->ops = ops;
+
+ list_add(&core->node, &apu->cores);
+
+ return core;
+}
+EXPORT_SYMBOL_GPL(apu_core_alloc);
+
+/**
+ * apu_core_free() - Free an APU core allocated using apu_core_alloc()
+ *
+ * @core: The APU core to release
+ */
+void apu_core_free(struct apu_core *core)
+{
+ ida_free(&core->apu->ida, core->device_id);
+ list_del(&core->node);
+}
+EXPORT_SYMBOL_GPL(apu_core_free);
+
+/**
+ * apu_core_register() - Register a core to APU device
+ *
+ * @dev: Pointer to APU device
+ * @core: Pointer to APU core to register
+ * @priv: Private data attached to this core
+ *
+ * Register an APU core and make it available for computing.
+ * On success, userspace can start using this core.
+ *
+ * Returns: Zero on success, non-zero value on failure.
+ */
+int apu_core_register(struct device *dev, struct apu_core *core, void *priv)
+{
+ int ret;
+
+ core->dev_priv = priv;
+ core->dev = dev;
+
+ if (core->ops->register_core) {
+ ret = core->ops->register_core(core);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(apu_core_register);
+
+/**
+ * apu_core_remove() - Remove a core from the APU device
+ *
+ * @core: Pointer to APU core to remove
+ */
+void apu_core_remove(struct apu_core *core)
+{
+ core->dev_priv = NULL;
+}
+EXPORT_SYMBOL_GPL(apu_core_remove);
+
+/**
+ * apu_find_core_by_priv() - Find a core allocated by apu_core_alloc()
+ *
+ * @priv: The pointer used to allocate the core
+ *
+ * All core allocated using apu_core_alloc() is registered to a list.
+ * This goes through the list to find the core using the @priv field.
+ *
+ * Returns: A pointer or NULL if no core has been found.
+ */
+struct apu_core *apu_find_core_by_priv(void *priv)
+{
+ struct apu_drm *apu;
+ struct apu_core *core;
+
+ list_for_each_entry(apu, &apu_devices, node) {
+ list_for_each_entry(core, &apu->cores, node) {
+ if (core->priv == priv)
+ return core;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(apu_find_core_by_priv);
+
+static struct apu_core *get_apu_core(struct apu_drm *apu, int device_id)
+{
+ struct apu_core *core;
+
+ list_for_each_entry(core, &apu->cores, node) {
+ if (core->device_id == device_id)
+ return core;
+ }
+
+ return NULL;
+}
+
+static void apu_core_update_state(struct apu_core *core)
+{
+ if (!core->ops->is_ready(core))
+ core->flags &= ~APU_ONLINE;
+}
+
+static int ioctl_apu_state(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct apu_drm *apu = dev->dev_private;
+ struct drm_apu_state *args = data;
+ struct apu_core *core;
+
+ args->flags = 0;
+
+ core = get_apu_core(apu, args->device);
+ if (!core)
+ return -ENODEV;
+
+ apu_core_update_state(core);
+ args->flags |= core->flags;
+
+ return 0;
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexandre Bailon");
new file mode 100644
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __APU_INTERNAL_H__
+#define __APU_INTERNAL_H__
+
+#include <drm/drm_drv.h>
+
+struct apu_core {
+ int device_id;
+ struct device *dev;
+ struct apu_core_ops *ops;
+ struct apu_drm *apu;
+
+ struct list_head node;
+ void *priv;
+ void *dev_priv;
+
+ u32 flags;
+};
+
+struct apu_drm {
+ struct drm_device base;
+ struct device *dev;
+
+ struct list_head cores;
+ struct list_head node;
+
+ struct ida ida;
+};
+
+/**
+ * @apu_core_ops: Provides platform specific callbacks
+ */
+struct apu_core_ops {
+ /**
+ * @register_core:
+ *
+ * Optional. Platform specific APU core registration.
+ */
+ int (*register_core)(struct apu_core *core);
+
+ /**
+ * @is_ready:
+ *
+ * Implements platform specific code to test if APU is ready to receive
+ * commands.
+ * Basically, an APU core may be running but not be ready to handle
+ * commands. This allows checking if APU is ready and start executing
+ * requests.
+ *
+ * Returns:
+ *
+ * One if the APU is ready or zero.
+ */
+ int (*is_ready)(struct apu_core *core);
+};
+
+struct apu_drm *apu_dev_alloc(struct device *dev);
+int apu_dev_register(struct apu_drm *apu);
+void apu_dev_unregister(struct apu_drm *apu);
+
+struct apu_core *apu_core_alloc(struct apu_drm *apu, struct apu_core_ops *ops,
+ void *priv);
+void apu_core_free(struct apu_core *core);
+int apu_core_register(struct device *dev, struct apu_core *core, void *priv);
+void apu_core_remove(struct apu_core *core);
+struct apu_core *apu_find_core_by_priv(void *priv);
+
+#endif /* __APU_INTERNAL_H__ */
new file mode 100644
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note*/
+
+#ifndef __UAPI_APU_DRM_H__
+#define __UAPI_APU_DRM_H__
+
+#include "drm.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define APU_ONLINE BIT(0)
+
+struct drm_apu_state {
+ __u32 device;
+ __u32 flags;
+};
+
+#define DRM_APU_STATE 0x00
+#define DRM_APU_NUM_IOCTLS 0x01
+
+#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__ */