1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * AMD MicroBlaze/V BRAM-based Remote Processor driver 4 * 5 * Copyright (C) 2026 Advanced Micro Devices, Inc. 6 * 7 * This driver supports soft-core processors (MicroBlaze, MicroBlaze-V, or 8 * similar) instantiated in AMD programmable logic, using dual-port BRAM 9 * for firmware storage and execution. 10 * 11 * The firmware memory (BRAM) is described in the processor-local address 12 * space and translated to the Linux-visible system physical address with 13 * standard devicetree address translation. 14 * 15 * Reset is controlled via GPIO connected to Processor System Reset IP. 16 */ 17 18 #include <linux/clk.h> 19 #include <linux/dma-mapping.h> 20 #include <linux/gpio/consumer.h> 21 #include <linux/module.h> 22 #include <linux/of.h> 23 #include <linux/of_address.h> 24 #include <linux/platform_device.h> 25 #include <linux/remoteproc.h> 26 27 #include "remoteproc_internal.h" 28 29 /** 30 * struct amd_bram_rproc - AMD MicroBlaze/V BRAM-based remoteproc private data 31 * @dev: device pointer 32 * @reset: GPIO descriptor for reset control (active-low) 33 * @clk: processor clock 34 */ 35 struct amd_bram_rproc { 36 struct device *dev; 37 struct gpio_desc *reset; 38 struct clk *clk; 39 }; 40 41 static int amd_bram_rproc_prepare(struct rproc *rproc) 42 { 43 struct amd_bram_rproc *priv = rproc->priv; 44 struct rproc_mem_entry *mem; 45 struct resource res; 46 u64 da, size; 47 int ret; 48 49 ret = of_property_read_reg(priv->dev->of_node, 0, &da, &size); 50 if (ret) { 51 dev_err(priv->dev, "failed to parse executable memory reg\n"); 52 return ret; 53 } 54 55 if (!size || size > U32_MAX) { 56 dev_err(priv->dev, "invalid executable memory size\n"); 57 return -EINVAL; 58 } 59 60 if (da > U32_MAX) { 61 dev_err(priv->dev, "invalid executable memory address\n"); 62 return -EINVAL; 63 } 64 65 ret = of_address_to_resource(priv->dev->of_node, 0, &res); 66 if (ret) { 67 dev_err(priv->dev, "failed to translate executable memory reg\n"); 68 return ret; 69 } 70 71 mem = rproc_mem_entry_init(priv->dev, NULL, (dma_addr_t)res.start, 72 resource_size(&res), da, 73 rproc_mem_entry_ioremap_wc, 74 rproc_mem_entry_iounmap, 75 dev_name(priv->dev)); 76 if (!mem) 77 return -ENOMEM; 78 79 rproc_add_carveout(rproc, mem); 80 rproc_coredump_add_segment(rproc, da, resource_size(&res)); 81 82 return 0; 83 } 84 85 static int amd_bram_rproc_start(struct rproc *rproc) 86 { 87 struct amd_bram_rproc *priv = rproc->priv; 88 int ret; 89 90 /* Enable clock before releasing reset */ 91 ret = clk_prepare_enable(priv->clk); 92 if (ret) { 93 dev_err(priv->dev, "failed to enable clock: %d\n", ret); 94 return ret; 95 } 96 97 /* Deassert reset and let the processor run. */ 98 ret = gpiod_set_value_cansleep(priv->reset, 0); 99 if (ret) { 100 dev_err(priv->dev, "failed to deassert reset: %d\n", ret); 101 clk_disable_unprepare(priv->clk); 102 return ret; 103 } 104 105 return 0; 106 } 107 108 static int amd_bram_rproc_stop(struct rproc *rproc) 109 { 110 struct amd_bram_rproc *priv = rproc->priv; 111 int ret; 112 113 /* Assert reset before disabling the processor clock. */ 114 ret = gpiod_set_value_cansleep(priv->reset, 1); 115 if (ret) { 116 dev_err(priv->dev, "failed to assert reset: %d\n", ret); 117 return ret; 118 } 119 120 /* Disable clock after asserting reset */ 121 clk_disable_unprepare(priv->clk); 122 123 return 0; 124 } 125 126 static int amd_bram_rproc_parse_fw(struct rproc *rproc, 127 const struct firmware *fw) 128 { 129 rproc_elf_load_rsc_table_optional(rproc, fw, dev_dbg, 130 "no resource table found\n"); 131 return 0; 132 } 133 134 static const struct rproc_ops amd_bram_rproc_ops = { 135 .prepare = amd_bram_rproc_prepare, 136 .start = amd_bram_rproc_start, 137 .stop = amd_bram_rproc_stop, 138 .load = rproc_elf_load_segments, 139 .sanity_check = rproc_elf_sanity_check, 140 .get_boot_addr = rproc_elf_get_boot_addr, 141 .parse_fw = amd_bram_rproc_parse_fw, 142 }; 143 144 static int amd_bram_rproc_probe(struct platform_device *pdev) 145 { 146 struct device *dev = &pdev->dev; 147 struct amd_bram_rproc *priv; 148 const char *fw_name = NULL; 149 struct rproc *rproc; 150 int ret; 151 152 ret = rproc_of_parse_firmware(dev, 0, &fw_name); 153 if (ret < 0 && ret != -EINVAL) 154 return dev_err_probe(dev, ret, 155 "failed to parse firmware-name property\n"); 156 157 rproc = devm_rproc_alloc(dev, dev_name(dev), &amd_bram_rproc_ops, 158 fw_name, sizeof(*priv)); 159 if (!rproc) 160 return -ENOMEM; 161 162 priv = rproc->priv; 163 priv->dev = dev; 164 165 /* Get the processor clock */ 166 priv->clk = devm_clk_get(dev, NULL); 167 if (IS_ERR(priv->clk)) 168 return dev_err_probe(dev, PTR_ERR(priv->clk), 169 "failed to get clock\n"); 170 171 /* 172 * Keep the processor in reset until remoteproc has finished loading 173 * firmware into the executable memory window described by reg and 174 * translated through the parent bus ranges property. 175 */ 176 priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 177 if (IS_ERR(priv->reset)) 178 return dev_err_probe(dev, PTR_ERR(priv->reset), 179 "failed to get reset gpio\n"); 180 181 rproc->auto_boot = false; 182 183 ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); 184 if (ret) 185 return dev_err_probe(dev, ret, "failed to set DMA mask\n"); 186 187 platform_set_drvdata(pdev, rproc); 188 189 ret = devm_rproc_add(dev, rproc); 190 if (ret) 191 return dev_err_probe(dev, ret, "failed to register rproc\n"); 192 193 return 0; 194 } 195 196 static const struct of_device_id amd_bram_rproc_of_match[] = { 197 { .compatible = "xlnx,zynqmp-bram-rproc" }, 198 { /* sentinel */ }, 199 }; 200 MODULE_DEVICE_TABLE(of, amd_bram_rproc_of_match); 201 202 static struct platform_driver amd_bram_rproc_driver = { 203 .probe = amd_bram_rproc_probe, 204 .driver = { 205 .name = "amd-bram-rproc", 206 .of_match_table = amd_bram_rproc_of_match, 207 }, 208 }; 209 module_platform_driver(amd_bram_rproc_driver); 210 211 MODULE_DESCRIPTION("AMD MicroBlaze/V BRAM-based Remote Processor driver"); 212 MODULE_AUTHOR("Ben Levinsky <ben.levinsky@amd.com>"); 213 MODULE_LICENSE("GPL"); 214