summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imagination/pvr_power.c
diff options
context:
space:
mode:
authorSarah Walker <sarah.walker@imgtec.com>2023-11-22 19:34:34 +0300
committerMaxime Ripard <mripard@kernel.org>2023-11-23 11:01:46 +0300
commitcc1aeedb98ad347c06ff59e991b2f94dfb4c565d (patch)
tree8ab0d584a8950ce0bbcb6f49d8c6b3f6335fa9e5 /drivers/gpu/drm/imagination/pvr_power.c
parent727538a4bbff07736ecfd704efd7e21718fca3e4 (diff)
downloadlinux-cc1aeedb98ad347c06ff59e991b2f94dfb4c565d.tar.xz
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_power.c')
-rw-r--r--drivers/gpu/drm/imagination/pvr_power.c166
1 files changed, 150 insertions, 16 deletions
diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c
index 88f14a4d31ab..5f7cb7beb879 100644
--- a/drivers/gpu/drm/imagination/pvr_power.c
+++ b/drivers/gpu/drm/imagination/pvr_power.c
@@ -3,6 +3,7 @@
#include "pvr_device.h"
#include "pvr_fw.h"
+#include "pvr_fw_startstop.h"
#include "pvr_power.h"
#include "pvr_rogue_fwif.h"
@@ -21,11 +22,38 @@
#define WATCHDOG_TIME_MS (500)
+/**
+ * pvr_device_lost() - Mark GPU device as lost
+ * @pvr_dev: Target PowerVR device.
+ *
+ * This will cause the DRM device to be unplugged.
+ */
+void
+pvr_device_lost(struct pvr_device *pvr_dev)
+{
+ if (!pvr_dev->lost) {
+ pvr_dev->lost = true;
+ drm_dev_unplug(from_pvr_device(pvr_dev));
+ }
+}
+
static int
pvr_power_send_command(struct pvr_device *pvr_dev, struct rogue_fwif_kccb_cmd *pow_cmd)
{
- /* TODO: implement */
- return -ENODEV;
+ struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
+ u32 slot_nr;
+ u32 value;
+ int err;
+
+ WRITE_ONCE(*fw_dev->power_sync, 0);
+
+ err = pvr_kccb_send_cmd_powered(pvr_dev, pow_cmd, &slot_nr);
+ if (err)
+ return err;
+
+ /* Wait for FW to acknowledge. */
+ return readl_poll_timeout(pvr_dev->fw_dev.power_sync, value, value != 0, 100,
+ POWER_SYNC_TIMEOUT_US);
}
static int
@@ -71,8 +99,7 @@ pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset)
return err;
}
- /* TODO: stop firmware */
- return -ENODEV;
+ return pvr_fw_stop(pvr_dev);
}
static int
@@ -80,11 +107,17 @@ pvr_power_fw_enable(struct pvr_device *pvr_dev)
{
int err;
- /* TODO: start firmware */
- err = -ENODEV;
+ err = pvr_fw_start(pvr_dev);
if (err)
return err;
+ err = pvr_wait_for_fw_boot(pvr_dev);
+ if (err) {
+ drm_err(from_pvr_device(pvr_dev), "Firmware failed to boot\n");
+ pvr_fw_stop(pvr_dev);
+ return err;
+ }
+
queue_delayed_work(pvr_dev->sched_wq, &pvr_dev->watchdog.work,
msecs_to_jiffies(WATCHDOG_TIME_MS));
@@ -94,14 +127,39 @@ pvr_power_fw_enable(struct pvr_device *pvr_dev)
bool
pvr_power_is_idle(struct pvr_device *pvr_dev)
{
- /* TODO: implement */
- return true;
+ /*
+ * FW power state can be out of date if a KCCB command has been submitted but the FW hasn't
+ * started processing it yet. So also check the KCCB status.
+ */
+ enum rogue_fwif_pow_state pow_state = READ_ONCE(pvr_dev->fw_dev.fwif_sysdata->pow_state);
+ bool kccb_idle = pvr_kccb_is_idle(pvr_dev);
+
+ return (pow_state == ROGUE_FWIF_POW_IDLE) && kccb_idle;
}
static bool
pvr_watchdog_kccb_stalled(struct pvr_device *pvr_dev)
{
- /* TODO: implement */
+ /* Check KCCB commands are progressing. */
+ u32 kccb_cmds_executed = pvr_dev->fw_dev.fwif_osdata->kccb_cmds_executed;
+ bool kccb_is_idle = pvr_kccb_is_idle(pvr_dev);
+
+ if (pvr_dev->watchdog.old_kccb_cmds_executed == kccb_cmds_executed && !kccb_is_idle) {
+ pvr_dev->watchdog.kccb_stall_count++;
+
+ /*
+ * If we have commands pending with no progress for 2 consecutive polls then
+ * consider KCCB command processing stalled.
+ */
+ if (pvr_dev->watchdog.kccb_stall_count == 2) {
+ pvr_dev->watchdog.kccb_stall_count = 0;
+ return true;
+ }
+ } else {
+ pvr_dev->watchdog.old_kccb_cmds_executed = kccb_cmds_executed;
+ pvr_dev->watchdog.kccb_stall_count = 0;
+ }
+
return false;
}
@@ -118,6 +176,9 @@ pvr_watchdog_worker(struct work_struct *work)
if (pm_runtime_get_if_in_use(from_pvr_device(pvr_dev)->dev) <= 0)
goto out_requeue;
+ if (!pvr_dev->fw_dev.booted)
+ goto out_pm_runtime_put;
+
stalled = pvr_watchdog_kccb_stalled(pvr_dev);
if (stalled) {
@@ -127,6 +188,7 @@ pvr_watchdog_worker(struct work_struct *work)
/* Device may be lost at this point. */
}
+out_pm_runtime_put:
pm_runtime_put(from_pvr_device(pvr_dev)->dev);
out_requeue:
@@ -158,18 +220,26 @@ pvr_power_device_suspend(struct device *dev)
struct platform_device *plat_dev = to_platform_device(dev);
struct drm_device *drm_dev = platform_get_drvdata(plat_dev);
struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
+ int err = 0;
int idx;
if (!drm_dev_enter(drm_dev, &idx))
return -EIO;
+ if (pvr_dev->fw_dev.booted) {
+ err = pvr_power_fw_disable(pvr_dev, false);
+ if (err)
+ goto err_drm_dev_exit;
+ }
+
clk_disable_unprepare(pvr_dev->mem_clk);
clk_disable_unprepare(pvr_dev->sys_clk);
clk_disable_unprepare(pvr_dev->core_clk);
+err_drm_dev_exit:
drm_dev_exit(idx);
- return 0;
+ return err;
}
int
@@ -196,10 +266,19 @@ pvr_power_device_resume(struct device *dev)
if (err)
goto err_sys_clk_disable;
+ if (pvr_dev->fw_dev.booted) {
+ err = pvr_power_fw_enable(pvr_dev);
+ if (err)
+ goto err_mem_clk_disable;
+ }
+
drm_dev_exit(idx);
return 0;
+err_mem_clk_disable:
+ clk_disable_unprepare(pvr_dev->mem_clk);
+
err_sys_clk_disable:
clk_disable_unprepare(pvr_dev->sys_clk);
@@ -239,7 +318,6 @@ pvr_power_device_idle(struct device *dev)
int
pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset)
{
- /* TODO: Implement hard reset. */
int err;
/*
@@ -248,13 +326,69 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset)
*/
WARN_ON(pvr_power_get(pvr_dev));
- err = pvr_power_fw_disable(pvr_dev, false);
- if (err)
- goto err_power_put;
+ down_write(&pvr_dev->reset_sem);
+
+ if (pvr_dev->lost) {
+ err = -EIO;
+ goto err_up_write;
+ }
+
+ /* Disable IRQs for the duration of the reset. */
+ disable_irq(pvr_dev->irq);
+
+ do {
+ err = pvr_power_fw_disable(pvr_dev, hard_reset);
+ if (!err) {
+ if (hard_reset) {
+ pvr_dev->fw_dev.booted = false;
+ WARN_ON(pm_runtime_force_suspend(from_pvr_device(pvr_dev)->dev));
+
+ err = pvr_fw_hard_reset(pvr_dev);
+ if (err)
+ goto err_device_lost;
+
+ err = pm_runtime_force_resume(from_pvr_device(pvr_dev)->dev);
+ pvr_dev->fw_dev.booted = true;
+ if (err)
+ goto err_device_lost;
+ } else {
+ /* Clear the FW faulted flags. */
+ pvr_dev->fw_dev.fwif_sysdata->hwr_state_flags &=
+ ~(ROGUE_FWIF_HWR_FW_FAULT |
+ ROGUE_FWIF_HWR_RESTART_REQUESTED);
+ }
+
+ pvr_fw_irq_clear(pvr_dev);
+
+ err = pvr_power_fw_enable(pvr_dev);
+ }
+
+ if (err && hard_reset)
+ goto err_device_lost;
+
+ if (err && !hard_reset) {
+ drm_err(from_pvr_device(pvr_dev), "FW stalled, trying hard reset");
+ hard_reset = true;
+ }
+ } while (err);
+
+ enable_irq(pvr_dev->irq);
+
+ up_write(&pvr_dev->reset_sem);
+
+ pvr_power_put(pvr_dev);
+
+ return 0;
+
+err_device_lost:
+ drm_err(from_pvr_device(pvr_dev), "GPU device lost");
+ pvr_device_lost(pvr_dev);
+
+ /* Leave IRQs disabled if the device is lost. */
- err = pvr_power_fw_enable(pvr_dev);
+err_up_write:
+ up_write(&pvr_dev->reset_sem);
-err_power_put:
pvr_power_put(pvr_dev);
return err;