diff mbox series

[v3,09/17] drm/imagination: Implement power management

Message ID 20230613144800.52657-10-sarah.walker@imgtec.com (mailing list archive)
State New, archived
Headers show
Series Imagination Technologies PowerVR DRM driver | expand

Commit Message

Sarah Walker June 13, 2023, 2:47 p.m. UTC
Add power management to the driver, using runtime pm. The power off
sequence depends on firmware commands which are not implemented in this
patch.

Signed-off-by: Sarah Walker <sarah.walker@imgtec.com>
---
 drivers/gpu/drm/imagination/Makefile     |   1 +
 drivers/gpu/drm/imagination/pvr_device.c |  20 +-
 drivers/gpu/drm/imagination/pvr_device.h |  17 ++
 drivers/gpu/drm/imagination/pvr_drv.c    |  35 +++-
 drivers/gpu/drm/imagination/pvr_power.c  | 239 +++++++++++++++++++++++
 drivers/gpu/drm/imagination/pvr_power.h  |  41 ++++
 6 files changed, 351 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/imagination/pvr_power.c
 create mode 100644 drivers/gpu/drm/imagination/pvr_power.h

Comments

Maxime Ripard July 7, 2023, 12:48 p.m. UTC | #1
On Tue, Jun 13, 2023 at 03:47:52PM +0100, Sarah Walker wrote:
> @@ -503,21 +506,31 @@ pvr_device_init(struct pvr_device *pvr_dev)
>       if (err)
>               goto err_device_clk_fini;
>
> +     /* Explicitly power the GPU so we can access control registers before the FW is booted. */
> +     err = pm_runtime_resume_and_get(dev);
> +     if (err)
> +             goto err_device_clk_fini;
> +
>       /* Map the control registers into memory. */
>       err = pvr_device_reg_init(pvr_dev);
>       if (err)
> -             goto err_device_clk_fini;
> +             goto err_pm_runtime_put;
>
>       /* Perform GPU-specific initialization steps. */
>       err = pvr_device_gpu_init(pvr_dev);
>       if (err)
>               goto err_device_reg_fini;
>
> +     pm_runtime_put_autosuspend(dev);
> +

You probably can use pm_runtime_put here

> @@ -532,12 +545,17 @@ pvr_device_init(struct pvr_device *pvr_dev)
>  void
>  pvr_device_fini(struct pvr_device *pvr_dev)
>  {
> +     struct drm_device *drm_dev = from_pvr_device(pvr_dev);
> +     struct device *dev = drm_dev->dev;
> +
>       /*
>        * Deinitialization stages are performed in reverse order compared to
>        * the initialization stages in pvr_device_init().
>        */
> +     pm_runtime_get_sync(dev);
>       pvr_device_gpu_fini(pvr_dev);
>       pvr_device_reg_fini(pvr_dev);

AFAIK gpu_fini releases the firmware and reg_fini drops the register
mapping address, I don't think you need the device powered up for that.

> @@ -130,6 +133,20 @@ struct pvr_device {
>
>       /** @fw_dev: Firmware related data. */
>       struct pvr_fw_device fw_dev;
> +
> +     struct {
> +             /** @work: Work item for watchdog callback. */
> +             struct delayed_work work;
> +
> +             /** @old_kccb_cmds_executed: KCCB command execution count at last watchdog poll. */
> +             u32 old_kccb_cmds_executed;
> +
> +             /** @kccb_stall_count: Number of watchdog polls KCCB has been stalled for. */
> +             u32 kccb_stall_count;
> +     } watchdog;
> +
> +     /** @lost: %true if the device has been lost. */
> +     bool lost;

The device being "lost" isn't clear to me. Does it mean it's
unresponsive or stuck somehow?

> @@ -1285,9 +1303,15 @@ pvr_probe(struct platform_device *plat_dev)
>
>       platform_set_drvdata(plat_dev, drm_dev);
>
> +     devm_pm_runtime_enable(&plat_dev->dev);
> +
> +     pm_runtime_set_autosuspend_delay(&plat_dev->dev, 50);
> +     pm_runtime_use_autosuspend(&plat_dev->dev);
> +     pvr_power_init(pvr_dev);

The name threw me off a bit. It doesn't look like it's power related but
you init the watchdog timer?

I can't really tell from that patch, but if it's not done in a later
patch you'll probably need a call to sprinkle your driver with a few
_mark_last_busy calls (at least in the job submission path?)

Maxime
Frank Binns July 14, 2023, 1:47 p.m. UTC | #2
Hi Maxime,

On Fri, 2023-07-07 at 14:48 +0200, Maxime Ripard wrote:
> On Tue, Jun 13, 2023 at 03:47:52PM +0100, Sarah Walker wrote:
> > @@ -503,21 +506,31 @@ pvr_device_init(struct pvr_device *pvr_dev)
> >       if (err)
> >               goto err_device_clk_fini;
> > 
> > +     /* Explicitly power the GPU so we can access control registers before the FW is booted. */
> > +     err = pm_runtime_resume_and_get(dev);
> > +     if (err)
> > +             goto err_device_clk_fini;
> > +
> >       /* Map the control registers into memory. */
> >       err = pvr_device_reg_init(pvr_dev);
> >       if (err)
> > -             goto err_device_clk_fini;
> > +             goto err_pm_runtime_put;
> > 
> >       /* Perform GPU-specific initialization steps. */
> >       err = pvr_device_gpu_init(pvr_dev);
> >       if (err)
> >               goto err_device_reg_fini;
> > 
> > +     pm_runtime_put_autosuspend(dev);
> > +
> 
> You probably can use pm_runtime_put here

Ack

> 
> > @@ -532,12 +545,17 @@ pvr_device_init(struct pvr_device *pvr_dev)
> >  void
> >  pvr_device_fini(struct pvr_device *pvr_dev)
> >  {
> > +     struct drm_device *drm_dev = from_pvr_device(pvr_dev);
> > +     struct device *dev = drm_dev->dev;
> > +
> >       /*
> >        * Deinitialization stages are performed in reverse order compared to
> >        * the initialization stages in pvr_device_init().
> >        */
> > +     pm_runtime_get_sync(dev);
> >       pvr_device_gpu_fini(pvr_dev);
> >       pvr_device_reg_fini(pvr_dev);
> 
> AFAIK gpu_fini releases the firmware and reg_fini drops the register
> mapping address, I don't think you need the device powered up for that.

Very true, we'll fix that :)

> 
> > @@ -130,6 +133,20 @@ struct pvr_device {
> > 
> >       /** @fw_dev: Firmware related data. */
> >       struct pvr_fw_device fw_dev;
> > +
> > +     struct {
> > +             /** @work: Work item for watchdog callback. */
> > +             struct delayed_work work;
> > +
> > +             /** @old_kccb_cmds_executed: KCCB command execution count at last watchdog poll. */
> > +             u32 old_kccb_cmds_executed;
> > +
> > +             /** @kccb_stall_count: Number of watchdog polls KCCB has been stalled for. */
> > +             u32 kccb_stall_count;
> > +     } watchdog;
> > +
> > +     /** @lost: %true if the device has been lost. */
> > +     bool lost;
> 
> The device being "lost" isn't clear to me. Does it mean it's
> unresponsive or stuck somehow?

The former. For example, this will be set when the firmware has become
unresponsive and hard resetting the GPU doesn't resolve this. We'll update the
docs to clarify.

> 
> > @@ -1285,9 +1303,15 @@ pvr_probe(struct platform_device *plat_dev)
> > 
> >       platform_set_drvdata(plat_dev, drm_dev);
> > 
> > +     devm_pm_runtime_enable(&plat_dev->dev);
> > +
> > +     pm_runtime_set_autosuspend_delay(&plat_dev->dev, 50);
> > +     pm_runtime_use_autosuspend(&plat_dev->dev);
> > +     pvr_power_init(pvr_dev);
> 
> The name threw me off a bit. It doesn't look like it's power related but
> you init the watchdog timer?

Yup, we'll update the name to reflect its real purpose.

> 
> I can't really tell from that patch, but if it's not done in a later
> patch you'll probably need a call to sprinkle your driver with a few
> _mark_last_busy calls (at least in the job submission path?)

We weren't doing this before so we'll add some calls in.

Thanks
Frank

> 
> Maxime
diff mbox series

Patch

diff --git a/drivers/gpu/drm/imagination/Makefile b/drivers/gpu/drm/imagination/Makefile
index 93d89909a8d7..00036b075505 100644
--- a/drivers/gpu/drm/imagination/Makefile
+++ b/drivers/gpu/drm/imagination/Makefile
@@ -8,6 +8,7 @@  powervr-y := \
 	pvr_device_info.o \
 	pvr_drv.o \
 	pvr_gem.o \
+	pvr_power.o \
 	pvr_vm.o
 
 obj-$(CONFIG_DRM_POWERVR) += powervr.o
diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c
index 6f7ef3767e56..8428e1270a22 100644
--- a/drivers/gpu/drm/imagination/pvr_device.c
+++ b/drivers/gpu/drm/imagination/pvr_device.c
@@ -5,6 +5,7 @@ 
 #include "pvr_device_info.h"
 
 #include "pvr_fw.h"
+#include "pvr_power.h"
 #include "pvr_rogue_cr_defs.h"
 #include "pvr_vm.h"
 
@@ -492,6 +493,8 @@  pvr_device_gpu_fini(struct pvr_device *pvr_dev)
 int
 pvr_device_init(struct pvr_device *pvr_dev)
 {
+	struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+	struct device *dev = drm_dev->dev;
 	int err;
 
 	/* Enable and initialize clocks required for the device to operate. */
@@ -503,21 +506,31 @@  pvr_device_init(struct pvr_device *pvr_dev)
 	if (err)
 		goto err_device_clk_fini;
 
+	/* Explicitly power the GPU so we can access control registers before the FW is booted. */
+	err = pm_runtime_resume_and_get(dev);
+	if (err)
+		goto err_device_clk_fini;
+
 	/* Map the control registers into memory. */
 	err = pvr_device_reg_init(pvr_dev);
 	if (err)
-		goto err_device_clk_fini;
+		goto err_pm_runtime_put;
 
 	/* Perform GPU-specific initialization steps. */
 	err = pvr_device_gpu_init(pvr_dev);
 	if (err)
 		goto err_device_reg_fini;
 
+	pm_runtime_put_autosuspend(dev);
+
 	return 0;
 
 err_device_reg_fini:
 	pvr_device_reg_fini(pvr_dev);
 
+err_pm_runtime_put:
+	pm_runtime_put_sync_suspend(dev);
+
 err_device_clk_fini:
 	pvr_device_clk_fini(pvr_dev);
 
@@ -532,12 +545,17 @@  pvr_device_init(struct pvr_device *pvr_dev)
 void
 pvr_device_fini(struct pvr_device *pvr_dev)
 {
+	struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+	struct device *dev = drm_dev->dev;
+
 	/*
 	 * Deinitialization stages are performed in reverse order compared to
 	 * the initialization stages in pvr_device_init().
 	 */
+	pm_runtime_get_sync(dev);
 	pvr_device_gpu_fini(pvr_dev);
 	pvr_device_reg_fini(pvr_dev);
+	pm_runtime_put_sync_suspend(dev);
 	pvr_device_clk_fini(pvr_dev);
 }
 
diff --git a/drivers/gpu/drm/imagination/pvr_device.h b/drivers/gpu/drm/imagination/pvr_device.h
index ce10bca36eb1..e5c5e47bf573 100644
--- a/drivers/gpu/drm/imagination/pvr_device.h
+++ b/drivers/gpu/drm/imagination/pvr_device.h
@@ -118,6 +118,9 @@  struct pvr_device {
 	/** @regulator: Power regulator. */
 	struct regulator *regulator;
 
+	/** @irq_wq: Workqueue for actions triggered off the IRQ handler. */
+	struct workqueue_struct *irq_wq;
+
 	/**
 	 * @kernel_vm_ctx: Virtual memory context used for kernel mappings.
 	 *
@@ -130,6 +133,20 @@  struct pvr_device {
 
 	/** @fw_dev: Firmware related data. */
 	struct pvr_fw_device fw_dev;
+
+	struct {
+		/** @work: Work item for watchdog callback. */
+		struct delayed_work work;
+
+		/** @old_kccb_cmds_executed: KCCB command execution count at last watchdog poll. */
+		u32 old_kccb_cmds_executed;
+
+		/** @kccb_stall_count: Number of watchdog polls KCCB has been stalled for. */
+		u32 kccb_stall_count;
+	} watchdog;
+
+	/** @lost: %true if the device has been lost. */
+	bool lost;
 };
 
 /**
diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c
index 547b4fbe355c..9d26cfcc7a16 100644
--- a/drivers/gpu/drm/imagination/pvr_drv.c
+++ b/drivers/gpu/drm/imagination/pvr_drv.c
@@ -4,6 +4,7 @@ 
 #include "pvr_device.h"
 #include "pvr_drv.h"
 #include "pvr_gem.h"
+#include "pvr_power.h"
 #include "pvr_rogue_defs.h"
 #include "pvr_rogue_fwif_client.h"
 #include "pvr_rogue_fwif_shared.h"
@@ -73,6 +74,9 @@  pvr_ioctl_create_bo(struct drm_device *drm_dev, void *raw_args,
 
 	int err;
 
+	if (pvr_dev->lost)
+		return -EIO;
+
 	/* All padding fields must be zeroed. */
 	if (args->_padding_c != 0)
 		return -EINVAL;
@@ -159,11 +163,15 @@  pvr_ioctl_get_bo_mmap_offset(__always_unused struct drm_device *drm_dev,
 			     void *raw_args, struct drm_file *file)
 {
 	struct drm_pvr_ioctl_get_bo_mmap_offset_args *args = raw_args;
+	struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
 	struct pvr_file *pvr_file = to_pvr_file(file);
 	struct pvr_gem_object *pvr_obj;
 	struct drm_gem_object *gem_obj;
 	int ret;
 
+	if (pvr_dev->lost)
+		return -EIO;
+
 	/* All padding fields must be zeroed. */
 	if (args->_padding_4 != 0)
 		return -EINVAL;
@@ -647,6 +655,9 @@  pvr_ioctl_dev_query(struct drm_device *drm_dev, void *raw_args,
 	struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
 	struct drm_pvr_ioctl_dev_query_args *args = raw_args;
 
+	if (pvr_dev->lost)
+		return -EIO;
+
 	switch ((enum drm_pvr_dev_query)args->type) {
 	case DRM_PVR_DEV_QUERY_GPU_INFO_GET:
 		return pvr_dev_query_gpu_info_get(pvr_dev, args);
@@ -813,10 +824,14 @@  pvr_ioctl_create_vm_context(struct drm_device *drm_dev, void *raw_args,
 			    struct drm_file *file)
 {
 	struct drm_pvr_ioctl_create_vm_context_args *args = raw_args;
+	struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
 	struct pvr_file *pvr_file = to_pvr_file(file);
 	struct pvr_vm_context *vm_ctx;
 	int err;
 
+	if (pvr_dev->lost)
+		return -EIO;
+
 	if (args->_padding_4)
 		return -EINVAL;
 
@@ -911,6 +926,9 @@  pvr_ioctl_vm_map(struct drm_device *drm_dev, void *raw_args,
 	u64 offset_plus_size;
 	int err;
 
+	if (pvr_dev->lost)
+		return -EIO;
+
 	/* Initial validation of args. */
 	if (args->_padding_14)
 		return -EINVAL;
@@ -1285,9 +1303,15 @@  pvr_probe(struct platform_device *plat_dev)
 
 	platform_set_drvdata(plat_dev, drm_dev);
 
+	devm_pm_runtime_enable(&plat_dev->dev);
+
+	pm_runtime_set_autosuspend_delay(&plat_dev->dev, 50);
+	pm_runtime_use_autosuspend(&plat_dev->dev);
+	pvr_power_init(pvr_dev);
+
 	err = pvr_device_init(pvr_dev);
 	if (err)
-		goto err_out;
+		goto err_power_fini;
 
 	err = drm_dev_register(drm_dev, 0);
 	if (err)
@@ -1298,6 +1322,9 @@  pvr_probe(struct platform_device *plat_dev)
 err_device_fini:
 	pvr_device_fini(pvr_dev);
 
+err_power_fini:
+	pvr_power_fini(pvr_dev);
+
 err_out:
 	return err;
 }
@@ -1310,6 +1337,7 @@  pvr_remove(struct platform_device *plat_dev)
 
 	drm_dev_unregister(drm_dev);
 	pvr_device_fini(pvr_dev);
+	pvr_power_fini(pvr_dev);
 
 	return 0;
 }
@@ -1321,11 +1349,16 @@  static const struct of_device_id dt_match[] = {
 };
 MODULE_DEVICE_TABLE(of, dt_match);
 
+static const struct dev_pm_ops pvr_pm_ops = {
+	SET_RUNTIME_PM_OPS(pvr_power_device_suspend, pvr_power_device_resume, pvr_power_device_idle)
+};
+
 static struct platform_driver pvr_driver = {
 	.probe = pvr_probe,
 	.remove = pvr_remove,
 	.driver = {
 		.name = PVR_DRIVER_NAME,
+		.pm = &pvr_pm_ops,
 		.of_match_table = dt_match,
 	},
 };
diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c
new file mode 100644
index 000000000000..fa1a84934127
--- /dev/null
+++ b/drivers/gpu/drm/imagination/pvr_power.c
@@ -0,0 +1,239 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/* Copyright (c) 2022 Imagination Technologies Ltd. */
+
+#include "pvr_device.h"
+#include "pvr_fw.h"
+#include "pvr_power.h"
+#include "pvr_rogue_fwif.h"
+
+#include <drm/drm_managed.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#define POWER_SYNC_TIMEOUT_US (1000000) /* 1s */
+
+#define WATCHDOG_TIME_MS (500)
+
+static void
+pvr_device_lost(struct pvr_device *pvr_dev)
+{
+	if (!pvr_dev->lost)
+		pvr_dev->lost = true;
+}
+
+static int
+pvr_power_send_command(struct pvr_device *pvr_dev, struct rogue_fwif_kccb_cmd *pow_cmd)
+{
+	/* TODO: implement */
+	return -ENODEV;
+}
+
+int
+pvr_power_request_idle(struct pvr_device *pvr_dev)
+{
+	struct rogue_fwif_kccb_cmd pow_cmd;
+
+	/* Send FORCED_IDLE request to FW. */
+	pow_cmd.cmd_type = ROGUE_FWIF_KCCB_CMD_POW;
+	pow_cmd.cmd_data.pow_data.pow_type = ROGUE_FWIF_POW_FORCED_IDLE_REQ;
+	pow_cmd.cmd_data.pow_data.power_req_data.pow_request_type = ROGUE_FWIF_POWER_FORCE_IDLE;
+
+	return pvr_power_send_command(pvr_dev, &pow_cmd);
+}
+
+int
+pvr_power_request_pwr_off(struct pvr_device *pvr_dev)
+{
+	struct rogue_fwif_kccb_cmd pow_cmd;
+
+	/* Send POW_OFF request to firmware. */
+	pow_cmd.cmd_type = ROGUE_FWIF_KCCB_CMD_POW;
+	pow_cmd.cmd_data.pow_data.pow_type = ROGUE_FWIF_POW_OFF_REQ;
+	pow_cmd.cmd_data.pow_data.power_req_data.forced = true;
+
+	return pvr_power_send_command(pvr_dev, &pow_cmd);
+}
+
+static int
+pvr_power_fw_disable(struct pvr_device *pvr_dev)
+{
+	int err;
+
+	cancel_delayed_work_sync(&pvr_dev->watchdog.work);
+
+	err = pvr_power_request_idle(pvr_dev);
+	if (err)
+		return err;
+
+	err = pvr_power_request_pwr_off(pvr_dev);
+	if (err)
+		return err;
+
+	/* TODO: stop firmware */
+	return -ENODEV;
+}
+
+static int
+pvr_power_fw_enable(struct pvr_device *pvr_dev)
+{
+	int err;
+
+	/* TODO: start firmware */
+	err = -ENODEV;
+	if (err)
+		return err;
+
+	queue_delayed_work(pvr_dev->irq_wq, &pvr_dev->watchdog.work,
+			   msecs_to_jiffies(WATCHDOG_TIME_MS));
+
+	return 0;
+}
+
+bool
+pvr_power_is_idle(struct pvr_device *pvr_dev)
+{
+	/* TODO: implement */
+	return true;
+}
+
+static bool
+pvr_watchdog_kccb_stalled(struct pvr_device *pvr_dev)
+{
+	/* TODO: implement */
+	return false;
+}
+
+static void
+pvr_watchdog_worker(struct work_struct *work)
+{
+	struct pvr_device *pvr_dev = container_of(work, struct pvr_device,
+						  watchdog.work.work);
+	bool stalled;
+
+	if (pvr_dev->lost)
+		return;
+
+	if (pm_runtime_get_if_in_use(from_pvr_device(pvr_dev)->dev) <= 0)
+		return;
+
+	stalled = pvr_watchdog_kccb_stalled(pvr_dev);
+
+	if (stalled) {
+		drm_err(from_pvr_device(pvr_dev), "GPU device lost");
+		pvr_device_lost(pvr_dev);
+	}
+
+	if (!pvr_dev->lost) {
+		queue_delayed_work(pvr_dev->irq_wq, &pvr_dev->watchdog.work,
+				   msecs_to_jiffies(WATCHDOG_TIME_MS));
+	}
+
+	pm_runtime_put(from_pvr_device(pvr_dev)->dev);
+}
+
+/**
+ * pvr_power_init() - Initialise power management for device
+ * @pvr_dev: Target PowerVR device.
+ *
+ * Returns:
+ *  * 0 on success, or
+ *  * -%ENOMEM on out of memory.
+ */
+int
+pvr_power_init(struct pvr_device *pvr_dev)
+{
+	INIT_DELAYED_WORK(&pvr_dev->watchdog.work, pvr_watchdog_worker);
+
+	return 0;
+}
+
+int
+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);
+
+	clk_disable(pvr_dev->mem_clk);
+	clk_disable(pvr_dev->sys_clk);
+	clk_disable(pvr_dev->core_clk);
+
+	if (pvr_dev->regulator)
+		regulator_disable(pvr_dev->regulator);
+
+	return 0;
+}
+
+int
+pvr_power_device_resume(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;
+
+	if (pvr_dev->regulator) {
+		err = regulator_enable(pvr_dev->regulator);
+		if (err)
+			return err;
+	}
+
+	clk_enable(pvr_dev->core_clk);
+	clk_enable(pvr_dev->sys_clk);
+	clk_enable(pvr_dev->mem_clk);
+
+	return 0;
+}
+
+int
+pvr_power_device_idle(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);
+
+	return pvr_power_is_idle(pvr_dev) ? 0 : -EBUSY;
+}
+
+int
+pvr_power_reset(struct pvr_device *pvr_dev)
+{
+	int err;
+
+	/*
+	 * Take a power reference during the reset. This should prevent any interference with the
+	 * power state during reset.
+	 */
+	err = pvr_power_get(pvr_dev);
+	if (err)
+		goto err_out;
+
+	err = pvr_power_fw_disable(pvr_dev);
+	if (err)
+		goto err_power_put;
+
+	err = pvr_power_fw_enable(pvr_dev);
+
+err_power_put:
+	pvr_power_put(pvr_dev);
+
+err_out:
+	return err;
+}
+
+/**
+ * pvr_power_fini() - Shutdown power management for device
+ * @pvr_dev: Target PowerVR device.
+ */
+void
+pvr_power_fini(struct pvr_device *pvr_dev)
+{
+	cancel_delayed_work_sync(&pvr_dev->watchdog.work);
+}
diff --git a/drivers/gpu/drm/imagination/pvr_power.h b/drivers/gpu/drm/imagination/pvr_power.h
new file mode 100644
index 000000000000..557457e7bdcd
--- /dev/null
+++ b/drivers/gpu/drm/imagination/pvr_power.h
@@ -0,0 +1,41 @@ 
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/* Copyright (c) 2022 Imagination Technologies Ltd. */
+
+#ifndef PVR_POWER_H
+#define PVR_POWER_H
+
+#include "pvr_device.h"
+
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+
+int pvr_power_init(struct pvr_device *pvr_dev);
+void pvr_power_fini(struct pvr_device *pvr_dev);
+bool pvr_power_is_idle(struct pvr_device *pvr_dev);
+
+int pvr_power_request_idle(struct pvr_device *pvr_dev);
+int pvr_power_request_pwr_off(struct pvr_device *pvr_dev);
+
+int pvr_power_device_suspend(struct device *dev);
+int pvr_power_device_resume(struct device *dev);
+int pvr_power_device_idle(struct device *dev);
+
+int pvr_power_reset(struct pvr_device *pvr_dev);
+
+static __always_inline int
+pvr_power_get(struct pvr_device *pvr_dev)
+{
+	struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+
+	return pm_runtime_resume_and_get(drm_dev->dev);
+}
+
+static __always_inline int
+pvr_power_put(struct pvr_device *pvr_dev)
+{
+	struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+
+	return pm_runtime_put(drm_dev->dev);
+}
+
+#endif /* PVR_POWER_H */