| // 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"); |