aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imagination/pvr_device.c
diff options
context:
space:
mode:
authorGravatar Sarah Walker <sarah.walker@imgtec.com> 2023-11-22 16:34:34 +0000
committerGravatar Maxime Ripard <mripard@kernel.org> 2023-11-23 09:01:46 +0100
commitcc1aeedb98ad347c06ff59e991b2f94dfb4c565d (patch)
tree8ab0d584a8950ce0bbcb6f49d8c6b3f6335fa9e5 /drivers/gpu/drm/imagination/pvr_device.c
parentdrm/imagination: Implement power management (diff)
downloadlinux-cc1aeedb98ad347c06ff59e991b2f94dfb4c565d.tar.gz
linux-cc1aeedb98ad347c06ff59e991b2f94dfb4c565d.tar.bz2
linux-cc1aeedb98ad347c06ff59e991b2f94dfb4c565d.zip
drm/imagination: Implement firmware infrastructure and META FW support
The infrastructure includes parsing of the firmware image, initialising FW-side structures, handling the kernel and firmware command ringbuffers and starting & stopping the firmware processor. This patch also adds the necessary support code for the META firmware processor. Changes since v8: - Fix documentation for pvr_fwccb_process() - Corrected license identifiers Changes since v6: - Add a minimum retry count to pvr_kccb_reserve_slot_sync() Changes since v5: - Add workaround for BRN 71242 - Attempt to recover GPU on MMU flush command failure Changes since v4: - Remove use of drm_gem_shmem_get_pages() - Remove interrupt resource name Changes since v3: - Hard reset FW processor on watchdog timeout - Switch to threaded IRQ - Rework FW object creation/initialisation to aid hard reset - Added MODULE_FIRMWARE() - Use drm_dev_{enter,exit} Signed-off-by: Sarah Walker <sarah.walker@imgtec.com> Signed-off-by: Donald Robson <donald.robson@imgtec.com> Link: https://lore.kernel.org/r/bb52a8dc84f296b37dc6668dfe8fbaf2ba551139.1700668843.git.donald.robson@imgtec.com Signed-off-by: Maxime Ripard <mripard@kernel.org>
Diffstat (limited to 'drivers/gpu/drm/imagination/pvr_device.c')
-rw-r--r--drivers/gpu/drm/imagination/pvr_device.c103
1 files changed, 103 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c
index e16282325178..1be14cdbdace 100644
--- a/drivers/gpu/drm/imagination/pvr_device.c
+++ b/drivers/gpu/drm/imagination/pvr_device.c
@@ -114,6 +114,87 @@ static int pvr_device_clk_init(struct pvr_device *pvr_dev)
return 0;
}
+static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data)
+{
+ struct pvr_device *pvr_dev = data;
+ irqreturn_t ret = IRQ_NONE;
+
+ /* We are in the threaded handler, we can keep dequeuing events until we
+ * don't see any. This should allow us to reduce the number of interrupts
+ * when the GPU is receiving a massive amount of short jobs.
+ */
+ while (pvr_fw_irq_pending(pvr_dev)) {
+ pvr_fw_irq_clear(pvr_dev);
+
+ if (pvr_dev->fw_dev.booted) {
+ pvr_fwccb_process(pvr_dev);
+ pvr_kccb_wake_up_waiters(pvr_dev);
+ }
+
+ pm_runtime_mark_last_busy(from_pvr_device(pvr_dev)->dev);
+
+ ret = IRQ_HANDLED;
+ }
+
+ /* Unmask FW irqs before returning, so new interrupts can be received. */
+ pvr_fw_irq_enable(pvr_dev);
+ return ret;
+}
+
+static irqreturn_t pvr_device_irq_handler(int irq, void *data)
+{
+ struct pvr_device *pvr_dev = data;
+
+ if (!pvr_fw_irq_pending(pvr_dev))
+ return IRQ_NONE; /* Spurious IRQ - ignore. */
+
+ /* Mask the FW interrupts before waking up the thread. Will be unmasked
+ * when the thread handler is done processing events.
+ */
+ pvr_fw_irq_disable(pvr_dev);
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * pvr_device_irq_init() - Initialise IRQ required by a PowerVR device
+ * @pvr_dev: Target PowerVR device.
+ *
+ * Returns:
+ * * 0 on success,
+ * * Any error returned by platform_get_irq_byname(), or
+ * * Any error returned by request_irq().
+ */
+static int
+pvr_device_irq_init(struct pvr_device *pvr_dev)
+{
+ struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+ struct platform_device *plat_dev = to_platform_device(drm_dev->dev);
+
+ init_waitqueue_head(&pvr_dev->kccb.rtn_q);
+
+ pvr_dev->irq = platform_get_irq(plat_dev, 0);
+ if (pvr_dev->irq < 0)
+ return pvr_dev->irq;
+
+ /* Clear any pending events before requesting the IRQ line. */
+ pvr_fw_irq_clear(pvr_dev);
+ pvr_fw_irq_enable(pvr_dev);
+
+ return request_threaded_irq(pvr_dev->irq, pvr_device_irq_handler,
+ pvr_device_irq_thread_handler,
+ IRQF_SHARED, "gpu", pvr_dev);
+}
+
+/**
+ * pvr_device_irq_fini() - Deinitialise IRQ required by a PowerVR device
+ * @pvr_dev: Target PowerVR device.
+ */
+static void
+pvr_device_irq_fini(struct pvr_device *pvr_dev)
+{
+ free_irq(pvr_dev->irq, pvr_dev);
+}
+
/**
* pvr_build_firmware_filename() - Construct a PowerVR firmware filename
* @pvr_dev: Target PowerVR device.
@@ -324,7 +405,19 @@ pvr_device_gpu_init(struct pvr_device *pvr_dev)
return PTR_ERR(pvr_dev->kernel_vm_ctx);
}
+ err = pvr_fw_init(pvr_dev);
+ if (err)
+ goto err_vm_ctx_put;
+
return 0;
+
+err_vm_ctx_put:
+ if (pvr_dev->fw_dev.processor_type != PVR_FW_PROCESSOR_TYPE_MIPS) {
+ pvr_vm_context_put(pvr_dev->kernel_vm_ctx);
+ pvr_dev->kernel_vm_ctx = NULL;
+ }
+
+ return err;
}
/**
@@ -334,6 +427,8 @@ pvr_device_gpu_init(struct pvr_device *pvr_dev)
static void
pvr_device_gpu_fini(struct pvr_device *pvr_dev)
{
+ pvr_fw_fini(pvr_dev);
+
if (pvr_dev->fw_dev.processor_type != PVR_FW_PROCESSOR_TYPE_MIPS) {
WARN_ON(!pvr_vm_context_put(pvr_dev->kernel_vm_ctx));
pvr_dev->kernel_vm_ctx = NULL;
@@ -386,10 +481,17 @@ pvr_device_init(struct pvr_device *pvr_dev)
if (err)
goto err_pm_runtime_put;
+ err = pvr_device_irq_init(pvr_dev);
+ if (err)
+ goto err_device_gpu_fini;
+
pm_runtime_put(dev);
return 0;
+err_device_gpu_fini:
+ pvr_device_gpu_fini(pvr_dev);
+
err_pm_runtime_put:
pm_runtime_put_sync_suspend(dev);
@@ -407,6 +509,7 @@ pvr_device_fini(struct pvr_device *pvr_dev)
* Deinitialization stages are performed in reverse order compared to
* the initialization stages in pvr_device_init().
*/
+ pvr_device_irq_fini(pvr_dev);
pvr_device_gpu_fini(pvr_dev);
}