[PATCH v2 25/28] drm/panthor: Route HW component PM through access windows
From: Karunika Choo
Date: Tue Nov 10 2026 - 10:50:18 EST
Make access-window resume and suspend own GPU access when AW support is
present, while keeping the direct HW component PM path for older GPUs.
Defer post-yield cleanup to workqueue context, and make
panthor_device_resume_and_get() ensure that GPU access is obtained
before callers touch hardware.
Signed-off-by: Karunika Choo <karunika.choo@xxxxxxx>
---
v2:
- Align with state lock changes.
- Fix case where autosuspend would not be triggered if tick_work drops
its PM ref earlier and post_yield_work becomes the last PM ref holder.
- Fix case where post_yield_work could run after FW/MMU was unplugged.
- Wait for AW state transition before retrying runtime resume if -EAGAIN
was received the first time around.
- Guard panthor_sched_resume against access if scheduler is not
initialized or already torn down.
drivers/gpu/drm/panthor/panthor_aw.c | 146 ++++++++++++++++++++++-
drivers/gpu/drm/panthor/panthor_aw.h | 6 +
drivers/gpu/drm/panthor/panthor_device.c | 10 +-
drivers/gpu/drm/panthor/panthor_device.h | 28 ++++-
drivers/gpu/drm/panthor/panthor_sched.c | 3 +
5 files changed, 181 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/panthor/panthor_aw.c b/drivers/gpu/drm/panthor/panthor_aw.c
index 5ab1d44f0a22e..3b418c816d824 100644
--- a/drivers/gpu/drm/panthor/panthor_aw.c
+++ b/drivers/gpu/drm/panthor/panthor_aw.c
@@ -81,6 +81,9 @@ struct panthor_aw {
/** @msg_retry_work: Work to resend messages */
struct work_struct msg_retry_work;
+
+ /** @post_yield_work: Work to yield the GPU */
+ struct work_struct post_yield_work;
};
static bool panthor_aw_is_open(struct panthor_aw *aw)
@@ -114,6 +117,66 @@ static int panthor_aw_state_wait(struct panthor_aw *aw,
return panthor_aw_wait_cond(aw, atomic_read(&aw->state) == target, timeout_ms);
}
+static int panthor_aw_state_wait_transition(struct panthor_aw *aw, u32 timeout_ms)
+{
+ return panthor_aw_wait_cond(
+ aw,
+ (atomic_read(&aw->state) == PANTHOR_AW_STATE_READY ||
+ atomic_read(&aw->state) == PANTHOR_AW_STATE_GPU_GRANTED),
+ timeout_ms);
+}
+
+static void panthor_aw_post_yield_work(struct work_struct *work)
+{
+ struct panthor_aw *aw =
+ container_of(work, struct panthor_aw, post_yield_work);
+ struct panthor_device *ptdev = aw->ptdev;
+ struct device *dev = ptdev->base.dev;
+ int ret;
+
+ scoped_guard(spinlock_irqsave, &aw->lock) {
+ /* Something else has progressed the state to READY. */
+ if (atomic_read(&aw->state) != PANTHOR_AW_STATE_SUSPEND_PENDING)
+ return;
+ }
+
+ /*
+ * Ensure in-progress resume is completed and prevent future RPM suspend
+ * to keep the clocks turned on when accessing HW registers.
+ */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_barrier(dev);
+
+ ret = panthor_device_suspend_hw_components(ptdev);
+ if (ret)
+ dev_warn(dev, "Failed to suspend hardware components.");
+
+ scoped_guard(spinlock_irqsave, &aw->lock)
+ panthor_aw_state_set(aw, PANTHOR_AW_STATE_READY);
+
+ /*
+ * schedule an immediate tick to re-evaluate if there is any additional
+ * work. This will request access to the GPU again and if the driver has
+ * gone idle, it will yield GPU access via the runtime suspend path.
+ */
+ panthor_sched_resume(ptdev);
+
+ pm_runtime_put_autosuspend(dev);
+}
+
+static bool panthor_aw_schedule_post_yield_work(struct panthor_aw *aw)
+{
+ lockdep_assert_held(&aw->lock);
+
+ if (panthor_aw_state_try_set(aw, PANTHOR_AW_STATE_GPU_GRANTED,
+ PANTHOR_AW_STATE_SUSPEND_PENDING)) {
+ queue_work(aw->wq, &aw->post_yield_work);
+ return true;
+ }
+
+ return false;
+}
+
static void panthor_aw_msg_retry_work(struct work_struct *work)
{
struct panthor_aw *aw =
@@ -213,7 +276,7 @@ static void panthor_aw_handle_window_closed(struct panthor_aw *aw)
switch (atomic_read(&aw->state)) {
case PANTHOR_AW_STATE_GPU_GRANTED:
- /* TODO: schedule cleanup */
+ panthor_aw_schedule_post_yield_work(aw);
break;
case PANTHOR_AW_STATE_GPU_REQUEST:
/*
@@ -416,6 +479,7 @@ int panthor_aw_init(struct panthor_device *ptdev)
aw->iomem = ptdev->iomem;
INIT_WORK(&aw->msg_retry_work, panthor_aw_msg_retry_work);
+ INIT_WORK(&aw->post_yield_work, panthor_aw_post_yield_work);
init_waitqueue_head(&aw->waitqueue);
spin_lock_init(&aw->lock);
@@ -443,6 +507,16 @@ int panthor_aw_init(struct panthor_device *ptdev)
return ret;
}
+void panthor_aw_cancel(struct panthor_device *ptdev)
+{
+ struct panthor_aw *aw = ptdev->aw;
+
+ if (!aw)
+ return;
+
+ disable_work_sync(&aw->post_yield_work);
+}
+
void panthor_aw_unplug(struct panthor_device *ptdev)
{
struct panthor_aw *aw = ptdev->aw;
@@ -456,6 +530,7 @@ void panthor_aw_unplug(struct panthor_device *ptdev)
}
disable_work_sync(&aw->msg_retry_work);
+ panthor_aw_cancel(ptdev);
}
int panthor_aw_resume(struct panthor_device *ptdev)
@@ -463,21 +538,41 @@ int panthor_aw_resume(struct panthor_device *ptdev)
struct panthor_aw *aw = ptdev->aw;
int ret;
+ /* resume hw components directly if AW is not supported */
if (!aw)
- return 0;
+ return panthor_device_resume_hw_components(ptdev);
panthor_aw_irq_resume(&aw->irq);
+again:
ret = panthor_aw_request(aw);
+ if (ret)
+ goto err_out;
+
+ ret = panthor_device_resume_hw_components(ptdev);
if (ret) {
- drm_warn(&ptdev->base, "Timedout waiting for GPU to be granted");
+ /* Yield GPU access if HW components failed to resume */
+ __panthor_aw_suspend(aw);
goto err_out;
}
- return 0;
+ switch (atomic_read(&aw->state)) {
+ case PANTHOR_AW_STATE_SUSPEND_PENDING:
+ /*
+ * WINDOW_CLOSED won the race. Suspend components before the
+ * caller can disable clocks. post_yield_work will transition
+ * state to READY after this PM transition completes.
+ */
+ panthor_device_suspend_hw_components(ptdev);
+ ret = -EAGAIN;
+ break;
+ case PANTHOR_AW_STATE_GPU_GRANTED:
+ return 0;
+ default:
+ goto again;
+ }
err_out:
- panthor_aw_irq_suspend(&aw->irq);
return ret;
}
@@ -488,11 +583,50 @@ int panthor_aw_suspend(struct panthor_device *ptdev)
/* suspend hw components directly if AW is not supported */
if (!aw)
- return 0;
+ return panthor_device_suspend_hw_components(ptdev);
+
+ ret = panthor_device_suspend_hw_components(ptdev);
+ if (ret)
+ goto out_irq_suspend;
ret = __panthor_aw_suspend(aw);
+out_irq_suspend:
panthor_aw_irq_suspend(&aw->irq);
return ret;
}
+
+int panthor_aw_wait_transition_complete(struct panthor_device *ptdev)
+{
+ /*
+ * This is only called from panthor_device_resume_and_get. If the error
+ * condition with which this is called changes in the future, the return
+ * value needs reconsidering.
+ */
+ if (!ptdev->aw)
+ return -EAGAIN;
+
+ return panthor_aw_state_wait_transition(
+ ptdev->aw, PANTHOR_AW_STATE_TRANSITION_TIMEOUT_MS);
+}
+
+int panthor_aw_ensure_gpu_access(struct panthor_device *ptdev)
+{
+ int ret;
+
+ if (!ptdev->aw)
+ return 0;
+
+again:
+ ret = panthor_aw_resume(ptdev);
+ if (ret != -EAGAIN)
+ return ret;
+
+ ret = panthor_aw_state_wait_transition(
+ ptdev->aw, PANTHOR_AW_STATE_TRANSITION_TIMEOUT_MS);
+ if (ret)
+ return ret;
+
+ goto again;
+}
diff --git a/drivers/gpu/drm/panthor/panthor_aw.h b/drivers/gpu/drm/panthor/panthor_aw.h
index 60d5c8b6c9afc..d1f43c4ba17e5 100644
--- a/drivers/gpu/drm/panthor/panthor_aw.h
+++ b/drivers/gpu/drm/panthor/panthor_aw.h
@@ -35,8 +35,14 @@ int panthor_aw_init(struct panthor_device *ptdev);
void panthor_aw_unplug(struct panthor_device *ptdev);
+void panthor_aw_cancel(struct panthor_device *ptdev);
+
int panthor_aw_resume(struct panthor_device *ptdev);
int panthor_aw_suspend(struct panthor_device *ptdev);
+int panthor_aw_wait_transition_complete(struct panthor_device *ptdev);
+
+int panthor_aw_ensure_gpu_access(struct panthor_device *ptdev);
+
#endif
diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c
index ba5252c27410f..d1ef460577c99 100644
--- a/drivers/gpu/drm/panthor/panthor_device.c
+++ b/drivers/gpu/drm/panthor/panthor_device.c
@@ -92,6 +92,7 @@ void panthor_device_unplug(struct panthor_device *ptdev)
*/
mutex_unlock(&ptdev->unplug.lock);
+ panthor_aw_cancel(ptdev);
/* Now, try to cleanly shutdown the GPU before the device resources
* get reclaimed.
*/
@@ -325,6 +326,7 @@ int panthor_device_init(struct panthor_device *ptdev)
panthor_sched_unplug(ptdev);
err_unplug_fw:
+ panthor_aw_cancel(ptdev);
panthor_fw_unplug(ptdev);
err_unplug_mmu:
@@ -577,15 +579,17 @@ int panthor_device_resume(struct device *dev)
atomic_set(&ptdev->reset.pending, 0);
}
- ret = panthor_device_resume_hw_components(ptdev);
+ ret = panthor_aw_resume(ptdev);
if (ret && ptdev->reset.fast) {
drm_err(&ptdev->base, "Fast reset failed, trying a slow reset");
ptdev->reset.fast = false;
- ret = panthor_device_resume_hw_components(ptdev);
+ ret = panthor_aw_resume(ptdev);
}
if (!ret)
panthor_sched_resume(ptdev);
+ else
+ panthor_aw_suspend(ptdev);
drm_dev_exit(cookie);
@@ -650,7 +654,7 @@ int panthor_device_suspend(struct device *dev)
* The end of the reset will happen in the resume path though.
*/
panthor_sched_suspend(ptdev);
- panthor_device_suspend_hw_components(ptdev);
+ panthor_aw_suspend(ptdev);
drm_dev_exit(cookie);
}
diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h
index e002ebbc88579..af2282240e692 100644
--- a/drivers/gpu/drm/panthor/panthor_device.h
+++ b/drivers/gpu/drm/panthor/panthor_device.h
@@ -20,6 +20,7 @@
#include <drm/panthor_drm.h>
#include "panthor_device_io.h"
+#include "panthor_aw.h"
struct panthor_aw;
struct panthor_csf;
@@ -456,7 +457,10 @@ int panthor_device_resume_hw_components(struct panthor_device *ptdev);
static inline int panthor_device_resume_and_get(struct panthor_device *ptdev)
{
- int ret = pm_runtime_resume_and_get(ptdev->base.dev);
+ int ret;
+
+again:
+ ret = pm_runtime_resume_and_get(ptdev->base.dev);
/* If the resume failed, we need to clear the runtime_error, which
* can done by forcing the RPM state to suspended. If multiple
@@ -470,8 +474,26 @@ static inline int panthor_device_resume_and_get(struct panthor_device *ptdev)
* succeeded. Given resume errors are not expected, this is probably
* something we can live with.
*/
- if (ret && atomic_cmpxchg(&ptdev->pm.recovery_needed, 1, 0) == 1)
- pm_runtime_set_suspended(ptdev->base.dev);
+ if (ret) {
+ if (atomic_cmpxchg(&ptdev->pm.recovery_needed, 1, 0) == 1)
+ pm_runtime_set_suspended(ptdev->base.dev);
+
+ if (ret == -EAGAIN) {
+ ret = panthor_aw_wait_transition_complete(ptdev);
+ if (ret)
+ return ret;
+
+ goto again;
+ }
+
+ return ret;
+ }
+
+ ret = panthor_aw_ensure_gpu_access(ptdev);
+ if (ret) {
+ pm_runtime_put_autosuspend(ptdev->base.dev);
+ return ret;
+ }
return ret;
}
diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c
index 3b2ed4095f80e..729eeb339fa40 100644
--- a/drivers/gpu/drm/panthor/panthor_sched.c
+++ b/drivers/gpu/drm/panthor/panthor_sched.c
@@ -2889,6 +2889,9 @@ void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev)
void panthor_sched_resume(struct panthor_device *ptdev)
{
+ if (!ptdev->scheduler)
+ return;
+
/* Force a tick to re-evaluate after a resume. */
sched_queue_delayed_work(ptdev->scheduler, tick, 0);
}
--
2.43.0