blob: e4a103cf84558ff84d79114e2737ae7475d63d63 [file]
// 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");