1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
// 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");
|