mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
Add a remoteproc driver for AMD MicroBlaze/V soft-core processor subsystems instantiated in programmable logic and using dual-port BRAM for firmware storage and execution. The driver parses the firmware memory window from the remoteproc device node's reg property, interprets that address and size in the processor-local address space, and then uses standard devicetree address translation through the parent bus ranges property to obtain the corresponding Linux-visible system physical address. The resulting translated region is registered as the executable remoteproc carveout and coredump segment. The processor is controlled through an active-low reset GPIO and a subsystem clock. The clock is enabled before reset is released, and the processor is kept in reset until firmware loading completes. The firmware-name property is optional, allowing firmware to be assigned later through the remoteproc framework. Firmware images without a resource table are also accepted. Signed-off-by: Ben Levinsky <ben.levinsky@amd.com> Link: https://lore.kernel.org/r/20260714202441.554065-3-ben.levinsky@amd.com Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
214 lines
5.5 KiB
C
214 lines
5.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* AMD MicroBlaze/V BRAM-based Remote Processor driver
|
|
*
|
|
* Copyright (C) 2026 Advanced Micro Devices, Inc.
|
|
*
|
|
* This driver supports soft-core processors (MicroBlaze, MicroBlaze-V, or
|
|
* similar) instantiated in AMD programmable logic, using dual-port BRAM
|
|
* for firmware storage and execution.
|
|
*
|
|
* The firmware memory (BRAM) is described in the processor-local address
|
|
* space and translated to the Linux-visible system physical address with
|
|
* standard devicetree address translation.
|
|
*
|
|
* Reset is controlled via GPIO connected to Processor System Reset IP.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/remoteproc.h>
|
|
|
|
#include "remoteproc_internal.h"
|
|
|
|
/**
|
|
* struct amd_bram_rproc - AMD MicroBlaze/V BRAM-based remoteproc private data
|
|
* @dev: device pointer
|
|
* @reset: GPIO descriptor for reset control (active-low)
|
|
* @clk: processor clock
|
|
*/
|
|
struct amd_bram_rproc {
|
|
struct device *dev;
|
|
struct gpio_desc *reset;
|
|
struct clk *clk;
|
|
};
|
|
|
|
static int amd_bram_rproc_prepare(struct rproc *rproc)
|
|
{
|
|
struct amd_bram_rproc *priv = rproc->priv;
|
|
struct rproc_mem_entry *mem;
|
|
struct resource res;
|
|
u64 da, size;
|
|
int ret;
|
|
|
|
ret = of_property_read_reg(priv->dev->of_node, 0, &da, &size);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to parse executable memory reg\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!size || size > U32_MAX) {
|
|
dev_err(priv->dev, "invalid executable memory size\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (da > U32_MAX) {
|
|
dev_err(priv->dev, "invalid executable memory address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_address_to_resource(priv->dev->of_node, 0, &res);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to translate executable memory reg\n");
|
|
return ret;
|
|
}
|
|
|
|
mem = rproc_mem_entry_init(priv->dev, NULL, (dma_addr_t)res.start,
|
|
resource_size(&res), da,
|
|
rproc_mem_entry_ioremap_wc,
|
|
rproc_mem_entry_iounmap,
|
|
dev_name(priv->dev));
|
|
if (!mem)
|
|
return -ENOMEM;
|
|
|
|
rproc_add_carveout(rproc, mem);
|
|
rproc_coredump_add_segment(rproc, da, resource_size(&res));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amd_bram_rproc_start(struct rproc *rproc)
|
|
{
|
|
struct amd_bram_rproc *priv = rproc->priv;
|
|
int ret;
|
|
|
|
/* Enable clock before releasing reset */
|
|
ret = clk_prepare_enable(priv->clk);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to enable clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Deassert reset and let the processor run. */
|
|
ret = gpiod_set_value_cansleep(priv->reset, 0);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to deassert reset: %d\n", ret);
|
|
clk_disable_unprepare(priv->clk);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amd_bram_rproc_stop(struct rproc *rproc)
|
|
{
|
|
struct amd_bram_rproc *priv = rproc->priv;
|
|
int ret;
|
|
|
|
/* Assert reset before disabling the processor clock. */
|
|
ret = gpiod_set_value_cansleep(priv->reset, 1);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to assert reset: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Disable clock after asserting reset */
|
|
clk_disable_unprepare(priv->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amd_bram_rproc_parse_fw(struct rproc *rproc,
|
|
const struct firmware *fw)
|
|
{
|
|
rproc_elf_load_rsc_table_optional(rproc, fw, dev_dbg,
|
|
"no resource table found\n");
|
|
return 0;
|
|
}
|
|
|
|
static const struct rproc_ops amd_bram_rproc_ops = {
|
|
.prepare = amd_bram_rproc_prepare,
|
|
.start = amd_bram_rproc_start,
|
|
.stop = amd_bram_rproc_stop,
|
|
.load = rproc_elf_load_segments,
|
|
.sanity_check = rproc_elf_sanity_check,
|
|
.get_boot_addr = rproc_elf_get_boot_addr,
|
|
.parse_fw = amd_bram_rproc_parse_fw,
|
|
};
|
|
|
|
static int amd_bram_rproc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct amd_bram_rproc *priv;
|
|
const char *fw_name = NULL;
|
|
struct rproc *rproc;
|
|
int ret;
|
|
|
|
ret = rproc_of_parse_firmware(dev, 0, &fw_name);
|
|
if (ret < 0 && ret != -EINVAL)
|
|
return dev_err_probe(dev, ret,
|
|
"failed to parse firmware-name property\n");
|
|
|
|
rproc = devm_rproc_alloc(dev, dev_name(dev), &amd_bram_rproc_ops,
|
|
fw_name, sizeof(*priv));
|
|
if (!rproc)
|
|
return -ENOMEM;
|
|
|
|
priv = rproc->priv;
|
|
priv->dev = dev;
|
|
|
|
/* Get the processor clock */
|
|
priv->clk = devm_clk_get(dev, NULL);
|
|
if (IS_ERR(priv->clk))
|
|
return dev_err_probe(dev, PTR_ERR(priv->clk),
|
|
"failed to get clock\n");
|
|
|
|
/*
|
|
* Keep the processor in reset until remoteproc has finished loading
|
|
* firmware into the executable memory window described by reg and
|
|
* translated through the parent bus ranges property.
|
|
*/
|
|
priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
|
|
if (IS_ERR(priv->reset))
|
|
return dev_err_probe(dev, PTR_ERR(priv->reset),
|
|
"failed to get reset gpio\n");
|
|
|
|
rproc->auto_boot = false;
|
|
|
|
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to set DMA mask\n");
|
|
|
|
platform_set_drvdata(pdev, rproc);
|
|
|
|
ret = devm_rproc_add(dev, rproc);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to register rproc\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id amd_bram_rproc_of_match[] = {
|
|
{ .compatible = "xlnx,zynqmp-bram-rproc" },
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, amd_bram_rproc_of_match);
|
|
|
|
static struct platform_driver amd_bram_rproc_driver = {
|
|
.probe = amd_bram_rproc_probe,
|
|
.driver = {
|
|
.name = "amd-bram-rproc",
|
|
.of_match_table = amd_bram_rproc_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(amd_bram_rproc_driver);
|
|
|
|
MODULE_DESCRIPTION("AMD MicroBlaze/V BRAM-based Remote Processor driver");
|
|
MODULE_AUTHOR("Ben Levinsky <ben.levinsky@amd.com>");
|
|
MODULE_LICENSE("GPL");
|