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 partition-control registers and interrupts described by DT child nodes. It also provides helpers to open, close, and yield partition access windows. Signed-off-by: Karunika Choo <[email protected]> --- v1: - Updated DT structure and accompanying parsing to handle child nodes instead of named platform resources. - Add child device population/depopulation. - Remove blocking waits on partition open, close and yield operations. - Adds YIELD_IDLE helper. drivers/gpu/drm/panthor/Kconfig | 10 + drivers/gpu/drm/panthor/Makefile | 1 + drivers/gpu/drm/panthor/arbitration/Makefile | 13 + .../panthor/arbitration/panthor_arbitration.h | 40 +++ .../arbitration/panthor_arbitration_drv.c | 174 +++++++++ .../arbitration/panthor_partition_control.c | 333 ++++++++++++++++++ .../arbitration/panthor_partition_control.h | 29 ++ 7 files changed, 600 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 e17c93035e8b7..04b11b933290e 100644 --- a/drivers/gpu/drm/panthor/Kconfig +++ b/drivers/gpu/drm/panthor/Kconfig @@ -30,3 +30,13 @@ config DRM_PANTHOR_SYSTEM AM GPUs. This block provides GPU-wide control outside the main GPU register window, including reset handling and management of shared clock and regulator resources. + +config DRM_PANTHOR_ARBITRATION + tristate "HW-assisted virtualization for Mali 5th Gen AM GPUs" + depends on DRM_PANTHOR + depends on OF + help + Enable support for the arbitration blocks found on Arm Mali 5th Gen + AM GPUs. These blocks manage partition controls, resource groups and + access-window scheduling, allowing multiple access windows to share + GPU resources under hardware-assisted arbitration. diff --git a/drivers/gpu/drm/panthor/Makefile b/drivers/gpu/drm/panthor/Makefile index 488a5e03efbc4..5d4d0ae64952e 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 0000000000000..b0b6b2e3edf8d --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/Makefile @@ -0,0 +1,13 @@ +# 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 0000000000000..26950e47852dd --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 or MIT */ +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#ifndef __PANTHOR_ARBITRATION_H__ +#define __PANTHOR_ARBITRATION_H__ + +#include <linux/ioport.h> +#include <linux/fwnode.h> + +struct device; +struct panthor_partition_control; + +#define AM_ARB_MAX_PC_COUNT 1 +#define AM_ARB_MAX_AW_COUNT 16 + +/** struct panthor_arbitration_resource - Arbitration resource */ +struct panthor_arbitration_resource { + /** @res: resource parsed from child 'reg' property */ + struct resource res; + + /** @id: id parsed from child 'arm,id' property */ + u32 id; +}; + +/** + * 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]; +}; + +int panthor_arbitration_parse_resource(struct device *dev, + const struct fwnode_handle *child, + struct panthor_arbitration_resource *res); + +#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 0000000000000..09342245a47d1 --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_arbitration_drv.c @@ -0,0 +1,174 @@ +// 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 <linux/of_address.h> +#include <linux/of.h> + +#include "panthor_arbitration.h" +#include "panthor_partition_control.h" + +#define PANTHOR_PM_AUTOSUSPEND_DELAY_MS 100 + +#define DT_ID "arm,id" + +int panthor_arbitration_parse_resource(struct device *dev, + const struct fwnode_handle *child, + struct panthor_arbitration_resource *res) +{ + const char *name; + int ret; + + if (!child || !res) + return -EINVAL; + + name = fwnode_get_name(child); + + if (of_address_to_resource(to_of_node(child), 0, &res->res)) { + dev_warn(dev, "Missing 'reg' property in %s", name); + return -ENODEV; + } + + if (!fwnode_property_present(child, DT_ID)) { + dev_warn(dev, "Missing '%s' property in %s@%llx", DT_ID, name, + res->res.start); + return -ENODEV; + } + + ret = fwnode_property_read_u32(child, DT_ID, &res->id); + if (ret) { + dev_warn(dev, "Malformed '%s' property in %s@%llx", DT_ID, name, + res->res.start); + return -ENODEV; + } + + return 0; +} + +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_term_pc; + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) + goto err_depopulate; + + pm_runtime_set_autosuspend_delay(dev, PANTHOR_PM_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_depopulate: + of_platform_depopulate(dev); + +err_term_pc: + panthor_partition_control_term(adev); + 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) + dev_warn(adev->dev, "failed to resume during remove: %d", ret); + + of_platform_depopulate(adev->dev); + + if (ret >= 0) { + panthor_partition_control_term(adev); + pm_runtime_put_noidle(adev->dev); + } + + 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 0000000000000..e6fbf5e58b51a --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT +/* Copyright 2026 ARM Limited. All rights reserved. */ + +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.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; +}; + +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 yield_now(struct panthor_partition_control *pc) +{ + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_YIELD_NOW)); + + return 0; +} + +static int yield_idle(struct panthor_partition_control *pc) +{ + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_YIELD_IDLE)); + + return 0; +} + +static int window_close(struct panthor_partition_control *pc) +{ + /* Partition already closed. */ + if (partition_state_get(pc) == PART_STATE_RESET) + return 1; + + guard(spinlock_irqsave)(&pc->lock); + + pc->closing = true; + + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_CLOSE_WINDOW)); + + return 0; +} + +static int window_open(struct panthor_partition_control *pc, u8 aw_id) +{ + if (aw_id >= AM_ARB_MAX_AW_COUNT) + return -EINVAL; + + if (partition_state_get(pc) == PART_STATE_WINDOW_OPEN) { + u8 cur_aw = partition_aw_get(pc); + + if (cur_aw == aw_id) + return 0; + + dev_warn(pc->dev, + "%s: OPEN_WINDOW(%u): Already opened for aw=%u, state=%u. Closing...", + pc->name, aw_id, cur_aw, partition_state_get(pc)); + + window_close(pc); + } + + guard(spinlock_irqsave)(&pc->lock); + + gpu_write(pc->iomem, AM_PART_COMMAND, + AM_PART_SET_COMMAND(PART_CMD_OPEN_WINDOW) | AM_PART_SET_WINDOW(aw_id)); + + 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; + } +} + +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) < 0)) + WARN_ON(partition_state_wait(pc, PART_STATE_RESET)); + + partition_irq_suspend(pc); + } +} + +static int partition_control_init(struct panthor_arbitration *adev, + const struct fwnode_handle *child) +{ + struct panthor_arbitration_resource res; + struct panthor_partition_control *pc; + struct device *dev = adev->dev; + struct irq_data *irqdata; + void __iomem *iomem; + u32 irq_flags = 0; + char *name; + int irq; + int ret; + + ret = panthor_arbitration_parse_resource(dev, child, &res); + if (ret) + return ret; + + if (res.id >= AM_ARB_MAX_PC_COUNT) + return -ENODEV; + + if (adev->pc[res.id]) + return dev_warn_probe(dev, -ENODEV, "partition@%llx: id (%u) already present", + res.res.start, res.id); + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + name = devm_kasprintf(dev, GFP_KERNEL, "pc%d", res.id); + + iomem = devm_ioremap_resource(dev, &res.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; + + spin_lock_init(&pc->lock); + pc->current_aw = -1; + + irq = fwnode_irq_get(child, 0); + if (irq < 0) + return dev_err_probe(dev, irq, "%s: Failed to get IRQ", name); + + irqdata = irq_get_irq_data(irq); + if (irqdata) + irq_flags = irqd_get_trigger_type(irqdata); + + ret = devm_request_irq( + dev, irq, partition_irq_raw_handler, irq_flags | IRQF_SHARED, + devm_kasprintf(dev, GFP_KERNEL, "panthor-%s-irq", name), pc); + if (ret) + return ret; + + adev->pc[res.id] = pc; + + partition_irq_resume(pc); + + return 0; +} + +int panthor_partition_control_init(struct panthor_arbitration *adev) +{ + device_for_each_named_child_node_scoped(adev->dev, child, "partition") { + int ret = partition_control_init(adev, child); + + 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); +} + +int panthor_partition_control_yield_idle(struct panthor_partition_control *pc) +{ + return yield_idle(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 0000000000000..9a688d7bbdb1f --- /dev/null +++ b/drivers/gpu/drm/panthor/arbitration/panthor_partition_control.h @@ -0,0 +1,29 @@ +/* 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); + +int panthor_partition_control_yield_idle(struct panthor_partition_control *pc); + +#endif -- 2.43.0
