Mali v15 AM GPUs expose AM_PARTITION_CONTROL blocks to manage partition access windows.
Add a panthor arbitration platform driver and wire it into Kconfig and the panthor build. The driver maps pcN partition-control resources, handles RESET_DONE and INVALID_COMMAND interrupts, and provides helpers to open, close and yield partition access windows. Signed-off-by: Karunika Choo <[email protected]> --- drivers/gpu/drm/panthor/Kconfig | 7 + drivers/gpu/drm/panthor/Makefile | 1 + drivers/gpu/drm/panthor/arbitration/Makefile | 15 + .../panthor/arbitration/panthor_arbitration.h | 24 ++ .../arbitration/panthor_arbitration_drv.c | 127 ++++++ .../arbitration/panthor_partition_control.c | 381 ++++++++++++++++++ .../arbitration/panthor_partition_control.h | 27 ++ 7 files changed, 582 insertions(+) create mode 100644 drivers/gpu/drm/panthor/arbitration/Makefile create mode 100644 drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h create mode 100644 drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c create mode 100644 drivers/gpu/drm/panthor/arbitration/panthor_partition_control.c create mode 100644 drivers/gpu/drm/panthor/arbitration/panthor_partition_control.h diff --git a/drivers/gpu/drm/panthor/Kconfig b/drivers/gpu/drm/panthor/Kconfig index da6b9bc1c67b..55b9b80f45d1 100644 --- a/drivers/gpu/drm/panthor/Kconfig +++ b/drivers/gpu/drm/panthor/Kconfig @@ -27,3 +27,10 @@ config DRM_PANTHOR_SYSTEM depends on OF help Driver for GPU-wide configuration of Arm Mali 5th Gen AM GPUs. + +config DRM_PANTHOR_ARBITRATION + tristate "HW-assisted virtualization for Mali 5th Gen AM GPUs" + depends on DRM_PANTHOR + depends on OF + help + HW-assisted virtualization driver for Mali 5th Gen AM GPUs. diff --git a/drivers/gpu/drm/panthor/Makefile b/drivers/gpu/drm/panthor/Makefile index 488a5e03efbc..5d4d0ae64952 100644 --- a/drivers/gpu/drm/panthor/Makefile +++ b/drivers/gpu/drm/panthor/Makefile @@ -15,5 +15,6 @@ panthor-y := \ obj-$(CONFIG_DRM_PANTHOR) += panthor.o obj-$(CONFIG_DRM_PANTHOR_SYSTEM) += system/ +obj-$(CONFIG_DRM_PANTHOR_ARBITRATION) += arbitration/ CFLAGS_panthor_gpu.o := -I$(src) diff --git a/drivers/gpu/drm/panthor/arbitration/Makefile b/drivers/gpu/drm/panthor/arbitration/Makefile new file mode 100644 index 000000000000..5d5b2b8d84bc --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 OR MIT +# Copyright 2026 ARM Limited. All rights reserved. + +obj-$(CONFIG_DRM_PANTHOR_ARBITRATION) += panthor_arbitration.o +panthor_arbitration-y := \ + panthor_arbitration_drv.o \ + panthor_partition_control.o + +INCLUDES = \ + -I$(src)/.. + +ccflags-y += $(INCLUDES) +subdir-ccflags-y += $(INCLUDES) + + diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h new file mode 100644 index 000000000000..5e35ae8463c4 --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 or MIT */ +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#ifndef __PANTHOR_ARBITRATION_H__ +#define __PANTHOR_ARBITRATION_H__ + +struct device; +struct panthor_partition_control; + +#define AM_ARB_MAX_PC_COUNT 1 +#define AM_ARB_MAX_AW_COUNT 16 + +/** + * struct panthor_arbitration - Arbitration device + */ +struct panthor_arbitration { + /** @dev: Device pointer */ + struct device *dev; + + /** @pc: Pointer array to partition control data */ + struct panthor_partition_control *pc[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 new file mode 100644 index 000000000000..07a566168e6a --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#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/types.h> + +#include "panthor_arbitration.h" +#include "panthor_partition_control.h" + +#define PANTHOR_PM_AUTOSUSPEND_DELAY_MS 100 + +static int panthor_arbitration_runtime_suspend(struct device *dev) +{ + struct panthor_arbitration *adev = dev_get_drvdata(dev); + int ret = 0; + + ret = panthor_partition_control_suspend(adev); + if (ret) + return ret; + + return 0; +} + +static int panthor_arbitration_runtime_resume(struct device *dev) +{ + struct panthor_arbitration *adev = dev_get_drvdata(dev); + int ret = 0; + + ret = panthor_partition_control_resume(adev); + if (ret) + return ret; + + return 0; +} + +static int panthor_arbitration_probe(struct platform_device *pdev) +{ + struct panthor_arbitration *adev; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev) + return -EINVAL; + + adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + + adev->dev = dev; + + dev_set_drvdata(dev, adev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = panthor_partition_control_init(adev); + if (ret) + goto err_out; + + pm_runtime_set_autosuspend_delay(dev, PANTHOR_PM_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_out: + pm_runtime_put_noidle(dev); + return ret; +} + +static void panthor_arbitration_remove(struct platform_device *pdev) +{ + struct panthor_arbitration *adev = platform_get_drvdata(pdev); + int ret; + + if (!adev) + return; + + ret = pm_runtime_resume_and_get(adev->dev); + if (ret < 0) + goto out_suspended; + + panthor_partition_control_term(adev); + + pm_runtime_put_noidle(adev->dev); + +out_suspended: + pm_runtime_set_suspended(adev->dev); +} + +static const struct dev_pm_ops panthor_arbitration_pm_ops = { + .resume = pm_runtime_force_resume, + .suspend = pm_runtime_force_suspend, + .runtime_resume = panthor_arbitration_runtime_resume, + .runtime_suspend = panthor_arbitration_runtime_suspend, +}; + +static const struct of_device_id panthor_arbitration_of_match[] = { + { .compatible = "arm,mali-gen5-am-arbitration" }, + { /* Sentinel */ }, +}; + +static struct platform_driver panthor_arbitration_driver = { + .probe = panthor_arbitration_probe, + .remove = panthor_arbitration_remove, + .driver = { + .name = "panthor-arbitration", + .of_match_table = panthor_arbitration_of_match, + .pm = pm_ptr(&panthor_arbitration_pm_ops), + }, +}; + +module_platform_driver(panthor_arbitration_driver); + +MODULE_AUTHOR("ARM Ltd."); +MODULE_DESCRIPTION("Panthor HW-assisted virtualization Driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.c b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.c new file mode 100644 index 000000000000..f4bad839610d --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#include <linux/iopoll.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include "panthor_arbitration.h" +#include "panthor_device_io.h" +#include "panthor_partition_control.h" + +#define AM_PART_IRQ_RAWSTAT 0x40 +#define AM_PART_IRQ_CLEAR 0x44 +#define AM_PART_IRQ_MASK 0x48 +#define AM_PART_IRQ_STATUS 0x4C +#define PART_RESET_DONE BIT(3) +#define PART_INVALID_COMMAND BIT(6) + +#define AM_PART_STATE 0x60 +#define AM_PART_STATE_GET(x) FIELD_GET(GENMASK(11, 8), x) +#define PART_STATE_RESET 0 +#define PART_STATE_WINDOW_OPENING 1 +#define PART_STATE_WINDOW_OPEN 7 +#define PART_STATE_WINDOW_CLOSED 8 +#define AM_PART_STATE_WINDOW_GET(x) FIELD_GET(GENMASK(15, 12), x) + +#define AM_PART_COMMAND 0x100 +#define AM_PART_SET_COMMAND(x) FIELD_PREP(GENMASK(7, 0), x) +#define PART_CMD_YIELD_IDLE 0x10 +#define PART_CMD_YIELD_NOW 0x11 +#define PART_CMD_CLOSE_WINDOW 0x20 +#define PART_CMD_OPEN_WINDOW 0x21 +#define AM_PART_SET_WINDOW(x) FIELD_PREP(GENMASK(11, 8), x) + +#define PART_CONTROL_IRQ_MASK \ + (PART_RESET_DONE | PART_INVALID_COMMAND) + +#define PART_REG_POLL_SLEEP_US 10 +#define PART_STATE_TRANSITION_TIMEOUT_US 5000000 + +/** + * struct panthor_partition_control - Partition control data + */ +struct panthor_partition_control { + /** @dev: Device pointer */ + struct device *dev; + + /** @name: Resource name */ + char *name; + + /** @iomem: CPU mapping of the partition control IOMEM region */ + void __iomem *iomem; + + /** @irq: IRQ number */ + int irq; + + /** @lock: Proctects partition control state data */ + spinlock_t lock; + + /** @current_aw: currently open access window */ + int current_aw; + + /** @closing: synchronous closing of the partition */ + bool closing; + + /** @waitqueue: Event wait queue. Not IRQ safe */ + wait_queue_head_t waitqueue; +}; + +static void partition_irq_suspend(struct panthor_partition_control *pc) +{ + gpu_write(pc->iomem, AM_PART_IRQ_MASK, 0); +} + +static void partition_irq_resume(struct panthor_partition_control *pc) +{ + gpu_write(pc->iomem, AM_PART_IRQ_MASK, PART_CONTROL_IRQ_MASK); +} + +static u32 partition_state_get(struct panthor_partition_control *pc) +{ + const u32 state = gpu_read(pc->iomem, AM_PART_STATE); + + return AM_PART_STATE_GET(state); +} + +static u8 partition_aw_get(struct panthor_partition_control *pc) +{ + const u32 state = gpu_read(pc->iomem, AM_PART_STATE); + + return AM_PART_STATE_WINDOW_GET(state); +} + +static int partition_state_wait(struct panthor_partition_control *pc, u32 state) +{ + u32 partition_state; + + return read_poll_timeout_atomic(partition_state_get, partition_state, + partition_state == state, + PART_REG_POLL_SLEEP_US, + PART_STATE_TRANSITION_TIMEOUT_US, + false, pc); +} + +static int partition_state_wait_transition(struct panthor_partition_control *pc) +{ + int ret; + +again: + switch (partition_state_get(pc)) { + case PART_STATE_WINDOW_CLOSED: + ret = partition_state_wait(pc, PART_STATE_RESET); + if (ret) + return ret; + goto again; + case PART_STATE_WINDOW_OPENING: + ret = partition_state_wait(pc, PART_STATE_WINDOW_OPEN); + if (ret) + return ret; + goto again; + case PART_STATE_RESET: + case PART_STATE_WINDOW_OPEN: + return 0; + default: + dev_err(pc->dev, "%s: Unknown state %u", pc->name, + partition_state_get(pc)); + return -EINVAL; + } +} + +static int yield_now(struct panthor_partition_control *pc) +{ + int ret; + + ret = partition_state_wait_transition(pc); + if (ret) + return ret; + + /* Partition already reset, nothing to yield */ + if (partition_state_get(pc) == PART_STATE_RESET) + return 0; + + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_YIELD_NOW)); + + return 0; +} + +static int window_close(struct panthor_partition_control *pc) +{ + int ret; + + ret = partition_state_wait_transition(pc); + if (ret) + return ret; + + /* Partition already closed. */ + if (partition_state_get(pc) == PART_STATE_RESET) + return 0; + + scoped_guard(spinlock_irqsave, &pc->lock) + pc->closing = true; + + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_CLOSE_WINDOW)); + + /* + * Wait for RESET_DONE interrupt to clear pc->closing. Prevents stale + * RESET_DONE from being misinterpreted as RESET_DONE due to YIELD_DONE. + */ + if (!wait_event_timeout( + pc->waitqueue, !pc->closing, + usecs_to_jiffies(PART_STATE_TRANSITION_TIMEOUT_US))) { + if (pc->closing) { + dev_err(pc->dev, + "%s: Timed out waiting for partition to close: %d", + pc->name, ret); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int window_open(struct panthor_partition_control *pc, u8 aw_id) +{ + int ret; + + if (aw_id >= AM_ARB_MAX_AW_COUNT) + return -EINVAL; + + ret = partition_state_wait_transition(pc); + if (ret) + return ret; + + if (partition_state_get(pc) == PART_STATE_WINDOW_OPEN) { + dev_warn(pc->dev, + "%s: Window opem for %u Failed. Already opened. aw=%u, state=%u", + pc->name, aw_id, partition_aw_get(pc), + partition_state_get(pc)); + return 0; + } + + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_OPEN_WINDOW) | AM_PART_SET_WINDOW(aw_id)); + + ret = partition_state_wait(pc, PART_STATE_WINDOW_OPEN); + if (ret) { + dev_err(pc->dev, "%s: Timed out waiting for partition to open: %d", + pc->name, ret); + return ret; + } + + scoped_guard(spinlock_irqsave, &pc->lock) + pc->current_aw = aw_id; + + return 0; +} + +static void partition_handle_reset_done(struct panthor_partition_control *pc) +{ + scoped_guard(spinlock_irqsave, &pc->lock) { + pc->current_aw = -1; + + /* RESET_DONE from CLOSE_WINDOW */ + if (pc->closing) + pc->closing = false; + } + + wake_up_all(&pc->waitqueue); +} + +static irqreturn_t partition_irq_raw_handler(int irq, void *data) +{ + struct panthor_partition_control *pc = data; + u32 status; + + status = gpu_read(pc->iomem, AM_PART_IRQ_STATUS); + if (!status) + return IRQ_NONE; + + if (status & PART_RESET_DONE) + partition_handle_reset_done(pc); + + if (status & PART_INVALID_COMMAND) + dev_warn(pc->dev, "%s: Invalid command", pc->name); + + gpu_write(pc->iomem, AM_PART_IRQ_CLEAR, status); + + return IRQ_HANDLED; +} + +int panthor_partition_control_suspend(struct panthor_arbitration *adev) +{ + for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) { + struct panthor_partition_control *pc = adev->pc[i]; + + if (!pc) + continue; + + partition_irq_suspend(pc); + } + + return 0; +} + +int panthor_partition_control_resume(struct panthor_arbitration *adev) +{ + for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) { + struct panthor_partition_control *pc = adev->pc[i]; + + if (!pc) + continue; + + partition_irq_resume(pc); + } + + return 0; +} + +void panthor_partition_control_term(struct panthor_arbitration *adev) +{ + for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) { + struct panthor_partition_control *pc = adev->pc[i]; + + if (!pc) + continue; + + if (!WARN_ON(window_close(pc))) + WARN_ON(partition_state_wait(pc, PART_STATE_RESET)); + + partition_irq_suspend(pc); + } +} + +static int partition_control_init(struct panthor_arbitration *adev, int i) +{ + struct device *dev = adev->dev; + struct platform_device *pdev = to_platform_device(dev); + struct panthor_partition_control *pc; + struct resource *res; + void __iomem *iomem; + char *name; + int irq; + int ret; + + if (i >= AM_ARB_MAX_PC_COUNT) + return -EINVAL; + + name = devm_kasprintf(dev, GFP_KERNEL, "pc%d", i); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) { + devm_kfree(dev, name); + return -ENODEV; + } + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + iomem = devm_ioremap_resource(dev, res); + if (IS_ERR(iomem)) { + return dev_err_probe(dev, PTR_ERR(iomem), + "%s: Failed to ioremap", name); + } + + pc->name = name; + pc->dev = dev; + pc->iomem = iomem; + + init_waitqueue_head(&pc->waitqueue); + spin_lock_init(&pc->lock); + pc->current_aw = -1; + + adev->pc[i] = pc; + + irq = platform_get_irq_byname(pdev, name); + if (irq < 0) + return irq; + + ret = devm_request_irq( + dev, irq, partition_irq_raw_handler, IRQF_SHARED, + devm_kasprintf(dev, GFP_KERNEL, "panthor-%s-irq", name), pc); + if (ret) + return ret; + + partition_irq_resume(pc); + + return 0; +} + +int panthor_partition_control_init(struct panthor_arbitration *adev) +{ + for (int i = 0; i < AM_ARB_MAX_PC_COUNT; i++) { + int ret = partition_control_init(adev, i); + if (ret == -ENODEV) + continue; + + if (ret) + return ret; + } + + return 0; +} + +int panthor_partition_control_open_window(struct panthor_partition_control *pc, u8 aw_id) +{ + return window_open(pc, aw_id); +} + +int panthor_partition_control_close_window(struct panthor_partition_control *pc) +{ + return window_close(pc); +} + +int panthor_partition_control_yield_now(struct panthor_partition_control *pc) +{ + return yield_now(pc); +} diff --git a/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.h b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.h new file mode 100644 index 000000000000..bd935de04137 --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 or MIT */ +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#ifndef __PANTHOR_PARTITION_CONTROL_H__ +#define __PANTHOR_PARTITION_CONTROL_H__ + +#include <linux/types.h> + +struct device; +struct panthor_arbitration; +struct panthor_partition_control; + +int panthor_partition_control_init(struct panthor_arbitration *adev); + +void panthor_partition_control_term(struct panthor_arbitration *adev); + +int panthor_partition_control_suspend(struct panthor_arbitration *adev); + +int panthor_partition_control_resume(struct panthor_arbitration *adev); + +int panthor_partition_control_open_window(struct panthor_partition_control *pc, u8 aw_id); + +int panthor_partition_control_close_window(struct panthor_partition_control *pc); + +int panthor_partition_control_yield_now(struct panthor_partition_control *pc); + +#endif -- 2.43.0
