Add a per-partition arbitration scheduler that queues access-window GPU
requests, grants the active window, asks it to stop when its lease
expires, and force-closes it if it does not yield in time.

Use ordered workqueues and hrtimers for grant/stop sequencing, with
module parameters controlling grant and yield timeouts.

Signed-off-by: Karunika Choo <[email protected]>
---
 drivers/gpu/drm/panthor/arbitration/Makefile  |   1 +
 .../panthor/arbitration/panthor_arbitration.h |   4 +
 .../arbitration/panthor_arbitration_drv.c     |  17 +
 .../arbitration/panthor_arbitration_sched.c   | 692 ++++++++++++++++++
 .../arbitration/panthor_arbitration_sched.h   |  25 +
 5 files changed, 739 insertions(+)
 create mode 100644 
drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
 create mode 100644 
drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h

diff --git a/drivers/gpu/drm/panthor/arbitration/Makefile 
b/drivers/gpu/drm/panthor/arbitration/Makefile
index 0ea5b70d524a..83be25341ba0 100644
--- a/drivers/gpu/drm/panthor/arbitration/Makefile
+++ b/drivers/gpu/drm/panthor/arbitration/Makefile
@@ -4,6 +4,7 @@
 obj-$(CONFIG_DRM_PANTHOR_ARBITRATION) += panthor_arbitration.o
 panthor_arbitration-y := \
        panthor_arbitration_drv.o \
+       panthor_arbitration_sched.o \
        panthor_partition_control.o \
        panthor_resource_group.o
 
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h 
b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
index 38c8f8674a02..0bc683e6a71d 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
@@ -5,6 +5,7 @@
 #define __PANTHOR_ARBITRATION_H__
 
 struct device;
+struct panthor_arbitration_sched;
 struct panthor_partition_control;
 struct panthor_resource_group;
 
@@ -24,6 +25,9 @@ struct panthor_arbitration {
 
        /** @rg: Pointer array to resource group data */
        struct panthor_resource_group *rg[AM_ARB_MAX_RG_COUNT];
+
+       /** @shed: Pointer to scheduler data. 1 sched per partition */
+       struct panthor_arbitration_sched *sched[AM_ARB_MAX_PC_COUNT];
 };
 
 #endif
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c 
b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
index ac395b89b483..5cdefd9ed5c9 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
@@ -9,6 +9,7 @@
 #include <linux/types.h>
 
 #include "panthor_arbitration.h"
+#include "panthor_arbitration_sched.h"
 #include "panthor_partition_control.h"
 #include "panthor_resource_group.h"
 
@@ -19,6 +20,10 @@ static int panthor_arbitration_runtime_suspend(struct device 
*dev)
        struct panthor_arbitration *adev = dev_get_drvdata(dev);
        int ret = 0;
 
+       ret = panthor_arbitration_sched_suspend(adev);
+       if (ret)
+               return ret;
+
        ret = panthor_partition_control_suspend(adev);
        if (ret)
                return ret;
@@ -43,6 +48,10 @@ static int panthor_arbitration_runtime_resume(struct device 
*dev)
        if (ret)
                return ret;
 
+       ret = panthor_arbitration_sched_resume(adev);
+       if (ret)
+               return ret;
+
        return 0;
 }
 
@@ -79,6 +88,10 @@ static int panthor_arbitration_probe(struct platform_device 
*pdev)
        if (ret)
                goto err_term_partition;
 
+       ret = panthor_arbitration_sched_init(adev);
+       if (ret)
+               goto err_term_resgroup;
+
        pm_runtime_set_autosuspend_delay(dev, PANTHOR_PM_AUTOSUSPEND_DELAY_MS);
        pm_runtime_use_autosuspend(dev);
 
@@ -86,6 +99,9 @@ static int panthor_arbitration_probe(struct platform_device 
*pdev)
 
        return 0;
 
+err_term_resgroup:
+       panthor_resource_group_term(adev);
+
 err_term_partition:
        panthor_partition_control_term(adev);
 
@@ -106,6 +122,7 @@ static void panthor_arbitration_remove(struct 
platform_device *pdev)
        if (ret < 0)
                goto out_suspended;
 
+       panthor_arbitration_sched_term(adev);
        panthor_resource_group_term(adev);
        panthor_partition_control_term(adev);
 
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c 
b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
new file mode 100644
index 000000000000..a6c5efee7b8d
--- /dev/null
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
@@ -0,0 +1,692 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+/* Copyright 2026 ARM Limited. All rights reserved. */
+
+#include <linux/device.h>
+#include <linux/kfifo.h>
+#include <linux/types.h>
+#include <linux/time.h>
+
+#include "panthor_arbitration.h"
+#include "panthor_arbitration_sched.h"
+#include "panthor_resource_group.h"
+
+static u64 grant_timeout_us = 1 * USEC_PER_SEC;
+static u64 yield_timeout_us = 1 * USEC_PER_SEC;
+
+module_param(grant_timeout_us, ullong, 0664);
+MODULE_PARM_DESC(grant_timeout_us, "Time the GPU will be granted to the AW in 
microseconds.");
+
+module_param(yield_timeout_us, ullong, 0664);
+MODULE_PARM_DESC(yield_timeout_us, "Time, in microseconds, within which the AW 
must yeild the GPU.");
+
+enum arbitration_sched_phase {
+       ARB_SCHED_PHASE_IDLE = 0,
+       ARB_SCHED_PHASE_GRANTING,
+       ARB_SCHED_PHASE_GRANTED,
+       ARB_SCHED_PHASE_STOPPING,
+       ARB_SCHED_PHASE_LOST,
+};
+
+struct panthor_arbitration_timer {
+       /** @timer: High-resolution timer */
+       struct hrtimer timer;
+
+       /** @epoch: The lease generation for which the timer was triggered */
+       u32 epoch;
+};
+
+struct panthor_arbitration_work {
+       /** @work: Work struct */
+       struct work_struct work;
+
+       /** @aw_id: Window for which the work was queued */
+       u8 aw_id;
+
+       /** @epoch: The lease generation for which the work was queued */
+       u32 epoch;
+};
+
+struct panthor_arbitration_sched {
+       /** @dev: Device pointer */
+       struct device *dev;
+
+       /** @name: Scheduler indentifying name */
+       char *name;
+
+       /** @waitqueue: Event wait queue. Not IRQ safe */
+       wait_queue_head_t waitqueue;
+
+       /** @lock: Protects scheduler state */
+       spinlock_t lock;
+
+       /** @queue: Pending request FIFO */
+       DECLARE_KFIFO(queue, u8, 16);
+
+       /** @queued_mask: Mask of currently queued AWs */
+       DECLARE_BITMAP(queued_mask, AM_ARB_MAX_AW_COUNT);
+
+       /** @active_aw_id: Currently active AW. Can be -1 */
+       s8 active_aw_id;
+
+       /** @phase: Current scheduling phase */
+       enum arbitration_sched_phase phase;
+
+       /** @epoch: Current active lease generation */
+       u32 epoch;
+
+       /** @wq: Scheduler workqueue to dispatch events */
+       struct workqueue_struct *wq;
+
+       /** @grant_work: Work to grant the GPU to the AW */
+       struct panthor_arbitration_work grant_work;
+
+       /** @stop_work: Work to hard-close the AW */
+       struct panthor_arbitration_work stop_work;
+
+       /** @close_work: Work to hard-close the AW */
+       struct panthor_arbitration_work close_work;
+
+       /** @grant_timer: Timer to indicate grant window has lapsed */
+       struct panthor_arbitration_timer grant_timer;
+
+       /** @stop_timer: Timer to indicate yield window has lapsed */
+       struct panthor_arbitration_timer stop_timer;
+
+       /** @disabled: Scheduler disabled. No further scheduling can happen */
+       bool disabled;
+
+       /**
+        * @grant_previously_expired: Grant period previously expired but was
+        * not asked to stop
+        */
+       bool grant_previously_expired;
+};
+
+static inline struct panthor_arbitration *
+to_adev(struct panthor_arbitration_sched *sched)
+{
+       return dev_get_drvdata(sched->dev);
+}
+
+static inline struct panthor_arbitration_work *to_work(struct work_struct *w)
+{
+       return container_of(w, struct panthor_arbitration_work, work);
+}
+
+static bool arb_sched_phase_reached(struct panthor_arbitration_sched *sched,
+                                   enum arbitration_sched_phase phase)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       return sched->phase == phase;
+}
+
+/**
+ * sched_wait_phase - Wait for scheduler to enter a phase
+ *
+ * This method is not IRQ safe as it sleeps.
+ */
+static int arb_sched_wait_phase(struct panthor_arbitration_sched *sched,
+                               enum arbitration_sched_phase phase,
+                               u64 timeout_us)
+{
+       if (!wait_event_timeout(
+                   sched->waitqueue, arb_sched_phase_reached(sched, phase),
+                   usecs_to_jiffies(timeout_us))) {
+
+               if (!arb_sched_phase_reached(sched, phase))
+                       return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static void arb_sched_disable(struct panthor_arbitration_sched *sched)
+{
+       scoped_guard(spinlock_irqsave, &sched->lock)
+               sched->disabled = true;
+
+       hrtimer_cancel(&sched->grant_timer.timer);
+}
+
+static void arb_sched_enable(struct panthor_arbitration_sched *sched)
+{
+       scoped_guard(spinlock_irqsave, &sched->lock)
+               sched->disabled = false;
+}
+
+static void arb_sched_queue_work(struct panthor_arbitration_sched *sched,
+                                struct panthor_arbitration_work *work)
+{
+       lockdep_assert_held(&sched->lock);
+
+       work->aw_id = sched->active_aw_id;
+       work->epoch = sched->epoch;
+       queue_work(sched->wq, &work->work);
+}
+
+static void arb_sched_request_stop_locked(struct panthor_arbitration_sched 
*sched)
+{
+       lockdep_assert_held(&sched->lock);
+
+       if (sched->phase != ARB_SCHED_PHASE_GRANTED)
+               return;
+
+       if (sched->active_aw_id < 0)
+               return;
+
+       /*
+        * In the event of a single requesting AW, let it continue to
+        * lease the GPU until another request comes in.
+        */
+       if (!sched->disabled && kfifo_is_empty(&sched->queue)) {
+               sched->grant_previously_expired = true;
+               return;
+       }
+
+       sched->phase = ARB_SCHED_PHASE_STOPPING;
+       arb_sched_queue_work(sched, &sched->stop_work);
+
+       wake_up_all(&sched->waitqueue);
+}
+
+static void arb_sched_request_stop(struct panthor_arbitration_sched *sched)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       arb_sched_request_stop_locked(sched);
+}
+
+static void arb_sched_force_close_locked(struct panthor_arbitration_sched 
*sched)
+{
+       lockdep_assert_held(&sched->lock);
+
+       if (sched->phase == ARB_SCHED_PHASE_IDLE ||
+           sched->phase == ARB_SCHED_PHASE_LOST)
+               return;
+
+       if (sched->active_aw_id < 0)
+               return;
+
+       sched->phase = ARB_SCHED_PHASE_LOST;
+
+       arb_sched_queue_work(sched, &sched->close_work);
+
+       wake_up_all(&sched->waitqueue);
+}
+
+static void arb_sched_force_close(struct panthor_arbitration_sched *sched)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       arb_sched_force_close_locked(sched);
+}
+
+static int arb_sched_next_locked(struct panthor_arbitration_sched *sched)
+{
+       u8 aw_id;
+
+       lockdep_assert_held(&sched->lock);
+
+       if (sched->disabled)
+               return 0;
+
+       /* already have a running aw */
+       if (sched->active_aw_id >= 0)
+               return 0;
+
+       /* nothing to schedule */
+       if (kfifo_is_empty(&sched->queue))
+               return 0;
+
+       if (!kfifo_get(&sched->queue, &aw_id)) {
+               dev_warn(sched->dev, "%s: queue unexpectedly empty",
+                               sched->name);
+               return -EINVAL;
+       }
+
+       clear_bit(aw_id, sched->queued_mask);
+
+       sched->active_aw_id = aw_id;
+       sched->epoch++;
+       sched->phase = ARB_SCHED_PHASE_GRANTING;
+
+       arb_sched_queue_work(sched, &sched->grant_work);
+
+       wake_up_all(&sched->waitqueue);
+
+       return 0;
+}
+
+static int arb_sched_next(struct panthor_arbitration_sched *sched)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       return arb_sched_next_locked(sched);
+}
+
+static void arb_sched_reset(struct panthor_arbitration_sched *sched,
+                                   u32 epoch, u8 aw_id)
+{
+       scoped_guard(spinlock_irqsave, &sched->lock) {
+               if (sched->epoch != epoch)
+                       return;
+
+               if (sched->active_aw_id != aw_id)
+                       return;
+
+               dev_warn(sched->dev, "%s: scheduler reset. active_aw=%u",
+                        sched->name, sched->active_aw_id);
+
+               sched->epoch++;
+               sched->active_aw_id = -1;
+               sched->phase = ARB_SCHED_PHASE_IDLE;
+               sched->grant_previously_expired = false;
+
+               wake_up_all(&sched->waitqueue);
+       }
+
+       hrtimer_cancel(&sched->grant_timer.timer);
+       hrtimer_cancel(&sched->stop_timer.timer);
+
+       arb_sched_next(sched);
+}
+
+static void arb_sched_grant_work(struct work_struct *work)
+{
+       struct panthor_arbitration_work *w = to_work(work);
+       struct panthor_arbitration_sched *sched =
+               container_of(w, struct panthor_arbitration_sched, grant_work);
+       int ret;
+       u32 epoch;
+       u8 aw_id;
+
+       scoped_guard(spinlock_irqsave, &sched->lock) {
+               if (sched->epoch != w->epoch)
+                       return;
+
+               if (sched->active_aw_id != w->aw_id)
+                       return;
+
+               epoch = sched->epoch;
+               aw_id = sched->active_aw_id;
+       }
+
+       ret = panthor_arbitration_on_grant(to_adev(sched), aw_id);
+       if (!ret) {
+               guard(spinlock_irqsave)(&sched->lock);
+
+               if (sched->phase != ARB_SCHED_PHASE_GRANTING) {
+                       dev_warn(sched->dev, "%s: Grant ACKED when not 
granting",
+                                sched->name);
+                       return;
+               }
+
+               if (sched->active_aw_id != aw_id)
+                       return;
+
+               if (sched->disabled) {
+                       sched->phase = ARB_SCHED_PHASE_STOPPING;
+                       arb_sched_queue_work(sched, &sched->stop_work);
+               } else {
+                       sched->phase = ARB_SCHED_PHASE_GRANTED;
+
+                       sched->grant_timer.epoch = sched->epoch;
+                       hrtimer_start(&sched->grant_timer.timer,
+                                     us_to_ktime(grant_timeout_us),
+                                     HRTIMER_MODE_REL);
+               }
+
+               wake_up_all(&sched->waitqueue);
+       } else {
+               dev_err(sched->dev, "%s: Failed to grant access to AW%u",
+                       sched->name, aw_id);
+
+               arb_sched_reset(sched, epoch, aw_id);
+       }
+}
+
+static void arb_sched_stop_work(struct work_struct *work)
+{
+       struct panthor_arbitration_work *w = to_work(work);
+       struct panthor_arbitration_sched *sched =
+               container_of(w, struct panthor_arbitration_sched, stop_work);
+       int ret;
+       u32 epoch;
+       u8 aw_id;
+
+       scoped_guard(spinlock_irqsave, &sched->lock) {
+               if (sched->epoch != w->epoch)
+                       return;
+
+               if (sched->active_aw_id != w->aw_id)
+                       return;
+
+               epoch = sched->epoch;
+               aw_id = sched->active_aw_id;
+       }
+
+       ret = panthor_arbitration_on_stop(to_adev(sched), aw_id);
+       if (!ret) {
+               guard(spinlock_irqsave)(&sched->lock);
+
+               if (sched->phase != ARB_SCHED_PHASE_STOPPING) {
+                       dev_warn(sched->dev, "%s: Stop ACKED when not yielding",
+                                sched->name);
+                       return;
+               }
+
+               if (sched->active_aw_id != aw_id)
+                       return;
+
+               sched->stop_timer.epoch = sched->epoch;
+               hrtimer_start(&sched->stop_timer.timer,
+                             us_to_ktime(yield_timeout_us), HRTIMER_MODE_REL);
+       } else {
+               dev_err(sched->dev, "%s: Failed to send yield to AW%u",
+                       sched->name, aw_id);
+
+               arb_sched_reset(sched, epoch, aw_id);
+       }
+}
+
+static void arb_sched_close_work(struct work_struct *work)
+{
+       struct panthor_arbitration_work *w = to_work(work);
+       struct panthor_arbitration_sched *sched =
+               container_of(w, struct panthor_arbitration_sched, close_work);
+       int ret;
+       u32 epoch;
+       u8 aw_id;
+
+       scoped_guard(spinlock_irqsave, &sched->lock) {
+               if (sched->epoch != w->epoch)
+                       return;
+
+               if (sched->active_aw_id != w->aw_id)
+                       return;
+
+               epoch = sched->epoch;
+               aw_id = sched->active_aw_id;
+       }
+
+       ret = panthor_arbitration_on_close(to_adev(sched), w->aw_id);
+       if (ret) {
+               dev_err(sched->dev, "%s: Failed to close AW%u",
+                       sched->name, aw_id);
+               arb_sched_reset(sched, epoch, aw_id);
+       } else {
+               panthor_arbitration_sched_on_stopped(sched, w->aw_id);
+       }
+}
+
+static enum hrtimer_restart
+panthor_arbitration_sched_on_stop_timeout(struct hrtimer *timer)
+{
+       struct panthor_arbitration_timer *atmr = container_of(
+               timer, struct panthor_arbitration_timer, timer);
+       struct panthor_arbitration_sched *sched = container_of(
+               atmr, struct panthor_arbitration_sched, stop_timer);
+
+       guard(spinlock_irqsave)(&sched->lock);
+
+       if (sched->epoch != atmr->epoch)
+               return HRTIMER_NORESTART;
+
+       dev_warn(sched->dev,
+                "%s: AW%d failed to stop in time. Force closing...",
+                sched->name, sched->active_aw_id);
+       arb_sched_force_close_locked(sched);
+
+       return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart
+panthor_arbitration_sched_on_grant_timeout(struct hrtimer *timer)
+{
+       struct panthor_arbitration_timer *atmr = container_of(
+               timer, struct panthor_arbitration_timer, timer);
+       struct panthor_arbitration_sched *sched = container_of(
+               atmr, struct panthor_arbitration_sched, grant_timer);
+
+       guard(spinlock_irqsave)(&sched->lock);
+
+       if (sched->epoch != atmr->epoch)
+               return HRTIMER_NORESTART;
+
+       arb_sched_request_stop_locked(sched);
+
+       return HRTIMER_NORESTART;
+}
+
+int panthor_arbitration_sched_on_request(struct panthor_arbitration_sched 
*sched, u8 aw_id)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       if (aw_id >= AM_ARB_MAX_AW_COUNT)
+               return -EINVAL;
+
+       if (test_bit(aw_id, sched->queued_mask) ||
+           (sched->active_aw_id == aw_id &&
+            sched->phase != ARB_SCHED_PHASE_STOPPING)) {
+               dev_warn(sched->dev, "%s: AW%u already in request queue.",
+                        sched->name, aw_id);
+               return -EEXIST;
+       }
+
+       /* Always put onto queue */
+       kfifo_put(&sched->queue, aw_id);
+       set_bit(aw_id, sched->queued_mask);
+
+       /* Already have running or pending, exit. */
+       if (sched->active_aw_id >= 0) {
+               /*
+                * Grant timer for the current AW has previously expired,
+                * request it to yield the GPU.
+                */
+               if (sched->grant_previously_expired)
+                       arb_sched_request_stop_locked(sched);
+
+               return 0;
+       }
+
+       return arb_sched_next_locked(sched);
+}
+
+int panthor_arbitration_sched_on_idle(struct panthor_arbitration_sched *sched, 
u8 aw_id)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       if (aw_id >= AM_ARB_MAX_AW_COUNT)
+               return -EINVAL;
+
+       if (sched->active_aw_id != aw_id)
+               return 0;
+
+       if (sched->phase != ARB_SCHED_PHASE_GRANTED)
+               return 0;
+
+       sched->phase = ARB_SCHED_PHASE_STOPPING;
+       arb_sched_queue_work(sched, &sched->close_work);
+
+       return 0;
+}
+
+int panthor_arbitration_sched_on_stopped(struct panthor_arbitration_sched 
*sched, u8 aw_id)
+{
+       guard(spinlock_irqsave)(&sched->lock);
+
+       if (aw_id >= AM_ARB_MAX_AW_COUNT)
+               return -EINVAL;
+
+       if (sched->phase == ARB_SCHED_PHASE_IDLE)
+               return 0;
+
+       if (sched->active_aw_id != aw_id)
+               return 0;
+
+       /* retire old lease */
+       sched->epoch++;
+
+       sched->grant_previously_expired = false;
+       sched->active_aw_id = -1;
+       sched->phase = ARB_SCHED_PHASE_IDLE;
+
+       wake_up_all(&sched->waitqueue);
+
+       return arb_sched_next_locked(sched);
+}
+
+static void arbitration_sched_term(struct panthor_arbitration_sched *sched)
+{
+       WARN_ON(panthor_arbitration_sched_stop(sched));
+
+       disable_work_sync(&sched->grant_work.work);
+       disable_work_sync(&sched->stop_work.work);
+       disable_work_sync(&sched->close_work.work);
+}
+
+static int arbitration_sched_init(struct panthor_arbitration *adev, int i)
+{
+       struct device *dev = adev->dev;
+       struct panthor_arbitration_sched *sched;
+
+       sched = devm_kzalloc(dev, sizeof(*sched), GFP_KERNEL);
+       if (!sched)
+               return -ENOMEM;
+
+       sched->name = devm_kasprintf(dev, GFP_KERNEL, "sched%d", i);
+       sched->dev = dev;
+
+       sched->wq = devm_alloc_ordered_workqueue(dev, "%s-wq", 0, sched->name);
+       if (!sched->wq)
+               return -ENOMEM;
+
+       INIT_WORK(&sched->grant_work.work, arb_sched_grant_work);
+       INIT_WORK(&sched->stop_work.work, arb_sched_stop_work);
+       INIT_WORK(&sched->close_work.work, arb_sched_close_work);
+
+       init_waitqueue_head(&sched->waitqueue);
+
+       spin_lock_init(&sched->lock);
+       INIT_KFIFO(sched->queue);
+       sched->active_aw_id = -1;
+       sched->phase = ARB_SCHED_PHASE_IDLE;
+
+       hrtimer_setup(&sched->grant_timer.timer,
+                     panthor_arbitration_sched_on_grant_timeout,
+                     CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+       hrtimer_setup(&sched->stop_timer.timer,
+                     panthor_arbitration_sched_on_stop_timeout,
+                     CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+       adev->sched[i] = sched;
+
+       return 0;
+}
+
+void panthor_arbitration_sched_term(struct panthor_arbitration *adev)
+{
+       for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+               struct panthor_arbitration_sched *sched = adev->sched[i];
+
+               if (!sched)
+                       continue;
+
+               arbitration_sched_term(sched);
+       }
+}
+
+int panthor_arbitration_sched_init(struct panthor_arbitration *adev)
+{
+
+       for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+               int ret = arbitration_sched_init(adev, i);
+
+               if (ret == -ENODEV)
+                       continue;
+
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+int panthor_arbitration_sched_suspend(struct panthor_arbitration *adev)
+{
+       for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+               struct panthor_arbitration_sched *sched = adev->sched[i];
+               int ret;
+
+               if (!sched)
+                       continue;
+
+               ret = panthor_arbitration_sched_stop(sched);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+int panthor_arbitration_sched_resume(struct panthor_arbitration *adev)
+{
+       for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+               struct panthor_arbitration_sched *sched = adev->sched[i];
+               int ret;
+
+               if (!sched)
+                       continue;
+
+               ret = panthor_arbitration_sched_start(sched);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * panthor_arbitration_sched_stop - Stop active aw and pause scheduler
+ *
+ * This method is not IRQ safe as it waits for on_stopped() to be called.
+ */
+int panthor_arbitration_sched_stop(struct panthor_arbitration_sched *sched)
+{
+       int ret;
+
+       arb_sched_disable(sched);
+
+       arb_sched_request_stop(sched);
+
+       /*
+        * Provisionally waiting for up to 2x yield_timeout, but path may
+        * include time taken for panthor_arbitration_on_grant() to complete
+        * if sched_request_stop() is called while in the GRANTING state.
+        *
+        * Realistically, if granting takes more than yield_timeout, HW is in
+        * bad state either way. Best continue and stop the scheduler.
+        */
+       ret = arb_sched_wait_phase(sched, ARB_SCHED_PHASE_IDLE,
+                                  grant_timeout_us + yield_timeout_us);
+       if (ret)
+               arb_sched_force_close(sched);
+
+       /* Should no longer have any further activity. Cancel timer. */
+       hrtimer_cancel(&sched->stop_timer.timer);
+       flush_workqueue(sched->wq);
+
+       scoped_guard(spinlock_irqsave, &sched->lock)
+               if (sched->phase == ARB_SCHED_PHASE_IDLE)
+                       return 0;
+
+       return ret;
+}
+
+int panthor_arbitration_sched_start(struct panthor_arbitration_sched *sched)
+{
+       arb_sched_enable(sched);
+
+       return arb_sched_next(sched);
+}
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h 
b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h
new file mode 100644
index 000000000000..4625b36f4ce6
--- /dev/null
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 or MIT */
+/* Copyright 2026 ARM Limited. All rights reserved. */
+
+#ifndef __PANTHOR_ARBITRATION_SCHED_H__
+#define __PANTHOR_ARBITRATION_SCHED_H__
+
+#include <linux/types.h>
+
+struct panthor_arbitration;
+struct panthor_arbitration_sched;
+
+int panthor_arbitration_sched_init(struct panthor_arbitration *adev);
+void panthor_arbitration_sched_term(struct panthor_arbitration *adev);
+
+int panthor_arbitration_sched_suspend(struct panthor_arbitration *adev);
+int panthor_arbitration_sched_resume(struct panthor_arbitration *adev);
+
+int panthor_arbitration_sched_stop(struct panthor_arbitration_sched *sched);
+int panthor_arbitration_sched_start(struct panthor_arbitration_sched *sched);
+
+int panthor_arbitration_sched_on_request(struct panthor_arbitration_sched 
*sched, u8 aw_id);
+int panthor_arbitration_sched_on_idle(struct panthor_arbitration_sched *sched, 
u8 aw_id);
+int panthor_arbitration_sched_on_stopped(struct panthor_arbitration_sched 
*sched, u8 aw_id);
+
+#endif
-- 
2.43.0

Reply via email to