[PATCH v2 19/28] drm/panthor: Add AW assignment tracking

From: Karunika Choo

Date: Tue Nov 10 2026 - 10:51:50 EST


Track the scheduler assigned to each access window. Initialize
assignments from the access-window masks in partition child DT nodes.

Use the AW mapping to route request, idle and stopped events to the
assigned scheduler. Use the scheduler ID to select the partition for
grant, stop and close commands.

Record requests from unassigned access windows and forward them after
assignment. Keep schedulers disabled while they have no assigned access
windows. Assign all AWs to partition 0 if DT property is not set or is
zero.

Signed-off-by: Karunika Choo <karunika.choo@xxxxxxx>
---
v2:
- Assign AWs to pc0 by default if no arm,access-window-mask is 0 or not
set.
- Introduce assignment_lock to protect assignments allowing serialized
lookup without the need for READ_ONCE or WRITE_ONCE.
- Add pending_aw_request_mask to track and replay pending requests
arriving before AW assignments are initialized.

.../panthor/arbitration/panthor_arbitration.h | 23 ++
.../arbitration/panthor_arbitration_drv.c | 244 +++++++++++++++++-
.../arbitration/panthor_arbitration_sched.c | 2 +
.../arbitration/panthor_arbitration_sched.h | 3 +
4 files changed, 260 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
index 1350a5982a93b..9a6c251d962de 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h
@@ -4,9 +4,11 @@
#ifndef __PANTHOR_ARBITRATION_H__
#define __PANTHOR_ARBITRATION_H__

+#include <linux/bits.h>
#include <linux/ioport.h>
#include <linux/fwnode.h>
#include <linux/types.h>
+#include <linux/spinlock.h>

struct device;
struct panthor_arbitration_sched;
@@ -16,6 +18,12 @@ struct panthor_resource_group;
#define AM_ARB_MAX_PC_COUNT 1
#define AM_ARB_MAX_RG_COUNT 1
#define AM_ARB_MAX_AW_COUNT 16
+#define AM_ARB_MAX_AW_MASK GENMASK(AM_ARB_MAX_AW_COUNT - 1, 0)
+
+struct panthor_arbitration_dt_info {
+ /** @default_aw_masks: Default per-partition AW assignments */
+ u32 default_aw_masks[AM_ARB_MAX_PC_COUNT];
+};

/**
* struct panthor_arbitration - Arbitration device
@@ -32,6 +40,21 @@ struct panthor_arbitration {

/** @sched: Pointer to scheduler data. 1 sched per partition */
struct panthor_arbitration_sched *sched[AM_ARB_MAX_PC_COUNT];
+
+ /** @dt_info: Properties parsed from the device tree */
+ struct panthor_arbitration_dt_info dt_info;
+
+ /** @assignment_lock: Protects assignments */
+ spinlock_t assignment_lock;
+
+ /** @aw_sched_map: Tracks assignment of AW to sched instance */
+ s8 aw_sched_map[AM_ARB_MAX_AW_COUNT];
+
+ /**
+ * @pending_aw_request_mask: Bitmap of unassigned AWs with pending
+ * requests
+ */
+ DECLARE_BITMAP(pending_aw_request_mask, AM_ARB_MAX_AW_COUNT);
};

/* AW to Arbiter events */
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
index 48016d0d88557..6479a7602868c 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2026 ARM Limited. All rights reserved. */

+#include <linux/bits.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/property.h>
#include <linux/types.h>
#include <linux/of_address.h>
#include <linux/of.h>
@@ -17,6 +19,28 @@

#define PANTHOR_PM_AUTOSUSPEND_DELAY_MS 100

+#define DT_ASSIGNED_AW "arm,access-window-mask"
+
+static int to_assigned_sched_idx(struct panthor_arbitration *adev, u8 aw_id)
+{
+ int idx = -1;
+
+ lockdep_assert_held(&adev->assignment_lock);
+
+ if (aw_id >= AM_ARB_MAX_AW_COUNT) {
+ dev_err(adev->dev, "Invalid AW ID (%u)", aw_id);
+ return -EINVAL;
+ }
+
+ idx = adev->aw_sched_map[aw_id];
+ if (idx < 0 || idx >= AM_ARB_MAX_PC_COUNT || !adev->sched[idx]) {
+ dev_warn_ratelimited(adev->dev, "AW%u not assigned to any sched", aw_id);
+ return -ENOENT;
+ }
+
+ return idx;
+};
+
static int panthor_arbitration_runtime_suspend(struct device *dev)
{
struct panthor_arbitration *adev = dev_get_drvdata(dev);
@@ -57,42 +81,226 @@ static int panthor_arbitration_runtime_resume(struct device *dev)
return 0;
}

+static u32 arbitration_get_sched_aw_mask(struct panthor_arbitration *adev,
+ u8 sched_id)
+{
+ u32 aw_mask = 0;
+
+ lockdep_assert_held(&adev->assignment_lock);
+
+ for (int i = 0; i < AM_ARB_MAX_AW_COUNT; i++) {
+ if (adev->aw_sched_map[i] == sched_id)
+ aw_mask |= BIT(i);
+ }
+
+ return aw_mask;
+}
+
+static int arbitration_dt_parse_assigned_aw(struct panthor_arbitration *adev)
+{
+ struct panthor_arbitration_dt_info *dt_info = &adev->dt_info;
+ struct device *dev = adev->dev;
+ struct fwnode_handle *partitions __free(fwnode_handle) = NULL;
+ u32 seen_mask = 0;
+ u8 seen_partitions = 0;
+ int ret;
+
+ partitions = device_get_named_child_node(dev, "partitions");
+ if (!partitions)
+ return 0;
+
+ fwnode_for_each_child_node_scoped(partitions, child) {
+ u32 pc_id;
+
+ if (!fwnode_name_eq(child, "partition"))
+ continue;
+
+ ret = fwnode_property_read_u32(child, "reg", &pc_id);
+ if (ret) {
+ dev_warn(dev, "Missing or invalid 'reg'");
+ continue;
+ }
+
+ if (pc_id >= AM_ARB_MAX_PC_COUNT) {
+ dev_warn(dev,
+ "partition %u >= %u not supported",
+ pc_id, AM_ARB_MAX_PC_COUNT);
+ continue;
+ }
+
+ if (BIT(pc_id) & seen_partitions) {
+ dev_warn(dev, "partition %u already present",
+ pc_id);
+ continue;
+ }
+
+ seen_partitions |= BIT(pc_id);
+
+ if (!fwnode_property_present(child, DT_ASSIGNED_AW))
+ continue;
+
+ ret = fwnode_property_read_u32(child, DT_ASSIGNED_AW,
+ &dt_info->default_aw_masks[pc_id]);
+ if (ret)
+ continue;
+ }
+
+ for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+ u32 aw_mask = dt_info->default_aw_masks[i];
+
+ if (aw_mask & ~GENMASK(AM_ARB_MAX_AW_COUNT - 1, 0))
+ return dev_err_probe(dev, -EINVAL, "%s[%d] entry exceeds max AW count",
+ DT_ASSIGNED_AW, i);
+
+ if (aw_mask & seen_mask)
+ return dev_err_probe(dev, -EINVAL, "%s entries overlap",
+ DT_ASSIGNED_AW);
+
+ seen_mask |= aw_mask;
+ }
+
+ return 0;
+}
+
+static int arbitration_dt_parse_properties(struct panthor_arbitration *adev)
+{
+ return arbitration_dt_parse_assigned_aw(adev);
+}
+
+static int arbitration_sched_try_start(struct panthor_arbitration *adev, u8 sched_id)
+{
+ struct panthor_arbitration_sched *sched;
+ u32 aw_mask;
+
+ if (!adev->sched[sched_id])
+ return -EINVAL;
+
+ sched = adev->sched[sched_id];
+
+ scoped_guard(spinlock_irqsave, &adev->assignment_lock)
+ aw_mask = arbitration_get_sched_aw_mask(adev, sched_id);
+
+ if (aw_mask)
+ panthor_arbitration_sched_disable_clear(sched, ARB_SCHED_DISABLE_RESOURCE);
+ else
+ panthor_arbitration_sched_disable_set(sched, ARB_SCHED_DISABLE_RESOURCE);
+
+ return panthor_arbitration_sched_start(sched);
+}
+
+static void arbitration_retry_pending_requests(struct panthor_arbitration *adev, u32 mask)
+{
+ unsigned long aw_mask = mask;
+ u8 aw_id;
+
+ for_each_set_bit(aw_id, &aw_mask, AM_ARB_MAX_AW_COUNT) {
+ int ret;
+
+ if (!test_and_clear_bit(aw_id, adev->pending_aw_request_mask))
+ continue;
+
+ ret = panthor_arbitration_on_request(adev, aw_id);
+ if (ret && ret != -EEXIST)
+ set_bit(aw_id, adev->pending_aw_request_mask);
+ }
+}
+
+static int arbitration_resource_init(struct panthor_arbitration *adev)
+{
+ struct panthor_arbitration_dt_info *dt_info = &adev->dt_info;
+
+ /*
+ * If arm,access-window-mask is not defined or set to 0, assign AWs to
+ * PC0 by default.
+ */
+ if (!dt_info->default_aw_masks[0])
+ dt_info->default_aw_masks[0] = AM_ARB_MAX_AW_MASK;
+
+ for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) {
+ unsigned long pc_aw_mask = dt_info->default_aw_masks[i];
+ u8 aw_id;
+ int ret;
+
+ if (!adev->pc[i] || !adev->sched[i])
+ continue;
+
+ scoped_guard(spinlock_irqsave, &adev->assignment_lock) {
+ for_each_set_bit(aw_id, &pc_aw_mask, AM_ARB_MAX_AW_COUNT)
+ adev->aw_sched_map[aw_id] = i;
+ }
+
+ ret = arbitration_sched_try_start(adev, i);
+ if (ret)
+ return ret;
+ }
+
+ arbitration_retry_pending_requests(adev, AM_ARB_MAX_AW_MASK);
+
+ return 0;
+}
+
/* AW to Arbiter events */
int panthor_arbitration_on_request(struct panthor_arbitration *adev, u8 aw_id)
{
- /* TODO: AW to PC assignment */
- return panthor_arbitration_sched_on_request(adev->sched[0], aw_id);
+ int idx;
+
+ guard(spinlock_irqsave)(&adev->assignment_lock);
+
+ idx = to_assigned_sched_idx(adev, aw_id);
+
+ if (idx == -ENOENT) {
+ set_bit(aw_id, adev->pending_aw_request_mask);
+
+ return 0;
+ } else if (idx < 0) {
+ return idx;
+ }
+
+ return panthor_arbitration_sched_on_request(adev->sched[idx], aw_id);
}

int panthor_arbitration_on_idle(struct panthor_arbitration *adev, u8 aw_id)
{
- /* TODO: AW to PC assignment */
- return panthor_arbitration_sched_on_idle(adev->sched[0], aw_id);
+ int idx;
+
+ guard(spinlock_irqsave)(&adev->assignment_lock);
+
+ idx = to_assigned_sched_idx(adev, aw_id);
+
+ if (idx < 0)
+ return idx;
+
+ return panthor_arbitration_sched_on_idle(adev->sched[idx], aw_id);
}

int panthor_arbitration_on_stopped(struct panthor_arbitration *adev, u8 aw_id)
{
- /* TODO: AW to PC assignment */
- return panthor_arbitration_sched_on_stopped(adev->sched[0], aw_id);
+ int idx;
+
+ guard(spinlock_irqsave)(&adev->assignment_lock);
+
+ idx = to_assigned_sched_idx(adev, aw_id);
+
+ if (idx < 0)
+ return idx;
+
+ return panthor_arbitration_sched_on_stopped(adev->sched[idx], aw_id);
}

/* Arbiter to AW events */
int panthor_arbitration_on_grant(struct panthor_arbitration *adev, u8 sched_id, u8 aw_id)
{
- /* TODO: AW to PC assignment */
- return panthor_partition_control_open_window(adev->pc[0], aw_id);
+ return panthor_partition_control_open_window(adev->pc[sched_id], aw_id);
}

int panthor_arbitration_on_stop(struct panthor_arbitration *adev, u8 sched_id)
{
- /* TODO: AW to PC assignment */
- return panthor_partition_control_yield_now(adev->pc[0]);
+ return panthor_partition_control_yield_now(adev->pc[sched_id]);
}

int panthor_arbitration_on_close(struct panthor_arbitration *adev, u8 sched_id)
{
- /* TODO: AW to PC assignment */
- return panthor_partition_control_close_window(adev->pc[0]);
+ return panthor_partition_control_close_window(adev->pc[sched_id]);
}

static int panthor_arbitration_probe(struct platform_device *pdev)
@@ -110,8 +318,16 @@ static int panthor_arbitration_probe(struct platform_device *pdev)

adev->dev = dev;

+ spin_lock_init(&adev->assignment_lock);
+ for (int i = 0; i < AM_ARB_MAX_AW_COUNT; i++)
+ adev->aw_sched_map[i] = -1;
+
dev_set_drvdata(dev, adev);

+ ret = arbitration_dt_parse_properties(adev);
+ if (ret)
+ return ret;
+
pm_runtime_enable(dev);

ret = pm_runtime_resume_and_get(dev);
@@ -130,6 +346,10 @@ static int panthor_arbitration_probe(struct platform_device *pdev)
if (ret)
goto err_term;

+ ret = arbitration_resource_init(adev);
+ if (ret)
+ goto err_term;
+
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret)
goto err_depopulate;
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
index 23768739e9ef5..95a7685a5b3d6 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.c
@@ -662,6 +662,8 @@ static int arbitration_sched_init(struct panthor_arbitration *adev, int i)
INIT_KFIFO(sched->queue);
sched->active_aw_id = -1;
sched->phase = ARB_SCHED_PHASE_IDLE;
+ /* Scheduler is disabled until its resources are configured */
+ sched->disabled = BIT(ARB_SCHED_DISABLE_RESOURCE);

hrtimer_setup(&sched->grant_timer.timer,
panthor_arbitration_sched_on_grant_timeout,
diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h
index 4d0f72838d218..187124ac39a5e 100644
--- a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h
+++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_sched.h
@@ -15,6 +15,9 @@ enum panthor_arb_sched_disable_reason {

/** @ARB_SCHED_DISABLE_TERM: scheduler disabled for termination */
ARB_SCHED_DISABLE_TERM,
+
+ /** @ARB_SCHED_DISABLE_RESOURCE: No resource assigned. scheduler disabled. */
+ ARB_SCHED_DISABLE_RESOURCE,
};

int panthor_arbitration_sched_init(struct panthor_arbitration *adev);
--
2.43.0