diff options
author | Sarah Walker <sarah.walker@imgtec.com> | 2023-11-22 19:34:34 +0300 |
---|---|---|
committer | Maxime Ripard <mripard@kernel.org> | 2023-11-23 11:01:46 +0300 |
commit | cc1aeedb98ad347c06ff59e991b2f94dfb4c565d (patch) | |
tree | 8ab0d584a8950ce0bbcb6f49d8c6b3f6335fa9e5 /drivers/gpu/drm/imagination/pvr_power.c | |
parent | 727538a4bbff07736ecfd704efd7e21718fca3e4 (diff) | |
download | linux-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.c | 166 |
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; |