Commit | Line | Data |
---|---|---|
6b291e80 TS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * ZynqMP R5 Remote Processor driver | |
4 | * | |
5 | */ | |
6 | ||
7 | #include <dt-bindings/power/xlnx-zynqmp-power.h> | |
8 | #include <linux/dma-mapping.h> | |
9 | #include <linux/firmware/xlnx-zynqmp.h> | |
10 | #include <linux/kernel.h> | |
5dfb28c2 TS |
11 | #include <linux/mailbox_client.h> |
12 | #include <linux/mailbox/zynqmp-ipi-message.h> | |
6b291e80 TS |
13 | #include <linux/module.h> |
14 | #include <linux/of_address.h> | |
15 | #include <linux/of_platform.h> | |
16 | #include <linux/of_reserved_mem.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/remoteproc.h> | |
6b291e80 TS |
19 | |
20 | #include "remoteproc_internal.h" | |
21 | ||
5dfb28c2 TS |
22 | /* IPI buffer MAX length */ |
23 | #define IPI_BUF_LEN_MAX 32U | |
24 | ||
25 | /* RX mailbox client buffer max length */ | |
26 | #define MBOX_CLIENT_BUF_MAX (IPI_BUF_LEN_MAX + \ | |
27 | sizeof(struct zynqmp_ipi_message)) | |
6b291e80 TS |
28 | /* |
29 | * settings for RPU cluster mode which | |
30 | * reflects possible values of xlnx,cluster-mode dt-property | |
31 | */ | |
32 | enum zynqmp_r5_cluster_mode { | |
33 | SPLIT_MODE = 0, /* When cores run as separate processor */ | |
34 | LOCKSTEP_MODE = 1, /* cores execute same code in lockstep,clk-for-clk */ | |
35 | SINGLE_CPU_MODE = 2, /* core0 is held in reset and only core1 runs */ | |
36 | }; | |
37 | ||
38 | /** | |
39 | * struct mem_bank_data - Memory Bank description | |
40 | * | |
41 | * @addr: Start address of memory bank | |
42 | * @size: Size of Memory bank | |
43 | * @pm_domain_id: Power-domains id of memory bank for firmware to turn on/off | |
44 | * @bank_name: name of the bank for remoteproc framework | |
45 | */ | |
46 | struct mem_bank_data { | |
47 | phys_addr_t addr; | |
48 | size_t size; | |
49 | u32 pm_domain_id; | |
50 | char *bank_name; | |
51 | }; | |
52 | ||
5dfb28c2 TS |
53 | /** |
54 | * struct mbox_info | |
55 | * | |
56 | * @rx_mc_buf: to copy data from mailbox rx channel | |
57 | * @tx_mc_buf: to copy data to mailbox tx channel | |
58 | * @r5_core: this mailbox's corresponding r5_core pointer | |
59 | * @mbox_work: schedule work after receiving data from mailbox | |
60 | * @mbox_cl: mailbox client | |
61 | * @tx_chan: mailbox tx channel | |
62 | * @rx_chan: mailbox rx channel | |
63 | */ | |
64 | struct mbox_info { | |
65 | unsigned char rx_mc_buf[MBOX_CLIENT_BUF_MAX]; | |
66 | unsigned char tx_mc_buf[MBOX_CLIENT_BUF_MAX]; | |
67 | struct zynqmp_r5_core *r5_core; | |
68 | struct work_struct mbox_work; | |
69 | struct mbox_client mbox_cl; | |
70 | struct mbox_chan *tx_chan; | |
71 | struct mbox_chan *rx_chan; | |
72 | }; | |
73 | ||
6b291e80 TS |
74 | /* |
75 | * Hardcoded TCM bank values. This will be removed once TCM bindings are | |
76 | * accepted for system-dt specifications and upstreamed in linux kernel | |
77 | */ | |
78 | static const struct mem_bank_data zynqmp_tcm_banks[] = { | |
79 | {0xffe00000UL, 0x10000UL, PD_R5_0_ATCM, "atcm0"}, /* TCM 64KB each */ | |
80 | {0xffe20000UL, 0x10000UL, PD_R5_0_BTCM, "btcm0"}, | |
81 | {0xffe90000UL, 0x10000UL, PD_R5_1_ATCM, "atcm1"}, | |
82 | {0xffeb0000UL, 0x10000UL, PD_R5_1_BTCM, "btcm1"}, | |
83 | }; | |
84 | ||
85 | /** | |
86 | * struct zynqmp_r5_core | |
87 | * | |
88 | * @dev: device of RPU instance | |
89 | * @np: device node of RPU instance | |
90 | * @tcm_bank_count: number TCM banks accessible to this RPU | |
91 | * @tcm_banks: array of each TCM bank data | |
6b291e80 TS |
92 | * @rproc: rproc handle |
93 | * @pm_domain_id: RPU CPU power domain id | |
5dfb28c2 | 94 | * @ipi: pointer to mailbox information |
6b291e80 TS |
95 | */ |
96 | struct zynqmp_r5_core { | |
97 | struct device *dev; | |
98 | struct device_node *np; | |
99 | int tcm_bank_count; | |
100 | struct mem_bank_data **tcm_banks; | |
6b291e80 TS |
101 | struct rproc *rproc; |
102 | u32 pm_domain_id; | |
5dfb28c2 | 103 | struct mbox_info *ipi; |
6b291e80 TS |
104 | }; |
105 | ||
106 | /** | |
107 | * struct zynqmp_r5_cluster | |
108 | * | |
109 | * @dev: r5f subsystem cluster device node | |
110 | * @mode: cluster mode of type zynqmp_r5_cluster_mode | |
111 | * @core_count: number of r5 cores used for this cluster mode | |
112 | * @r5_cores: Array of pointers pointing to r5 core | |
113 | */ | |
114 | struct zynqmp_r5_cluster { | |
115 | struct device *dev; | |
116 | enum zynqmp_r5_cluster_mode mode; | |
117 | int core_count; | |
118 | struct zynqmp_r5_core **r5_cores; | |
119 | }; | |
120 | ||
5dfb28c2 TS |
121 | /** |
122 | * event_notified_idr_cb() - callback for vq_interrupt per notifyid | |
123 | * @id: rproc->notify id | |
124 | * @ptr: pointer to idr private data | |
125 | * @data: data passed to idr_for_each callback | |
126 | * | |
127 | * Pass notification to remoteproc virtio | |
128 | * | |
129 | * Return: 0. having return is to satisfy the idr_for_each() function | |
130 | * pointer input argument requirement. | |
131 | **/ | |
132 | static int event_notified_idr_cb(int id, void *ptr, void *data) | |
133 | { | |
134 | struct rproc *rproc = data; | |
135 | ||
136 | if (rproc_vq_interrupt(rproc, id) == IRQ_NONE) | |
137 | dev_dbg(&rproc->dev, "data not found for vqid=%d\n", id); | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | /** | |
143 | * handle_event_notified() - remoteproc notification work function | |
144 | * @work: pointer to the work structure | |
145 | * | |
146 | * It checks each registered remoteproc notify IDs. | |
147 | */ | |
148 | static void handle_event_notified(struct work_struct *work) | |
149 | { | |
150 | struct mbox_info *ipi; | |
151 | struct rproc *rproc; | |
152 | ||
153 | ipi = container_of(work, struct mbox_info, mbox_work); | |
154 | rproc = ipi->r5_core->rproc; | |
155 | ||
156 | /* | |
157 | * We only use IPI for interrupt. The RPU firmware side may or may | |
158 | * not write the notifyid when it trigger IPI. | |
159 | * And thus, we scan through all the registered notifyids and | |
160 | * find which one is valid to get the message. | |
161 | * Even if message from firmware is NULL, we attempt to get vqid | |
162 | */ | |
163 | idr_for_each(&rproc->notifyids, event_notified_idr_cb, rproc); | |
164 | } | |
165 | ||
166 | /** | |
167 | * zynqmp_r5_mb_rx_cb() - receive channel mailbox callback | |
168 | * @cl: mailbox client | |
169 | * @msg: message pointer | |
170 | * | |
171 | * Receive data from ipi buffer, ack interrupt and then | |
172 | * it will schedule the R5 notification work. | |
173 | */ | |
174 | static void zynqmp_r5_mb_rx_cb(struct mbox_client *cl, void *msg) | |
175 | { | |
176 | struct zynqmp_ipi_message *ipi_msg, *buf_msg; | |
177 | struct mbox_info *ipi; | |
178 | size_t len; | |
179 | ||
180 | ipi = container_of(cl, struct mbox_info, mbox_cl); | |
181 | ||
182 | /* copy data from ipi buffer to r5_core */ | |
183 | ipi_msg = (struct zynqmp_ipi_message *)msg; | |
184 | buf_msg = (struct zynqmp_ipi_message *)ipi->rx_mc_buf; | |
185 | len = ipi_msg->len; | |
186 | if (len > IPI_BUF_LEN_MAX) { | |
187 | dev_warn(cl->dev, "msg size exceeded than %d\n", | |
188 | IPI_BUF_LEN_MAX); | |
189 | len = IPI_BUF_LEN_MAX; | |
190 | } | |
191 | buf_msg->len = len; | |
192 | memcpy(buf_msg->data, ipi_msg->data, len); | |
193 | ||
194 | /* received and processed interrupt ack */ | |
195 | if (mbox_send_message(ipi->rx_chan, NULL) < 0) | |
196 | dev_err(cl->dev, "ack failed to mbox rx_chan\n"); | |
197 | ||
198 | schedule_work(&ipi->mbox_work); | |
199 | } | |
200 | ||
201 | /** | |
202 | * zynqmp_r5_setup_mbox() - Setup mailboxes related properties | |
203 | * this is used for each individual R5 core | |
204 | * | |
205 | * @cdev: child node device | |
206 | * | |
207 | * Function to setup mailboxes related properties | |
208 | * return : NULL if failed else pointer to mbox_info | |
209 | */ | |
210 | static struct mbox_info *zynqmp_r5_setup_mbox(struct device *cdev) | |
211 | { | |
212 | struct mbox_client *mbox_cl; | |
213 | struct mbox_info *ipi; | |
214 | ||
215 | ipi = kzalloc(sizeof(*ipi), GFP_KERNEL); | |
216 | if (!ipi) | |
217 | return NULL; | |
218 | ||
219 | mbox_cl = &ipi->mbox_cl; | |
220 | mbox_cl->rx_callback = zynqmp_r5_mb_rx_cb; | |
221 | mbox_cl->tx_block = false; | |
222 | mbox_cl->knows_txdone = false; | |
223 | mbox_cl->tx_done = NULL; | |
224 | mbox_cl->dev = cdev; | |
225 | ||
226 | /* Request TX and RX channels */ | |
227 | ipi->tx_chan = mbox_request_channel_byname(mbox_cl, "tx"); | |
228 | if (IS_ERR(ipi->tx_chan)) { | |
229 | ipi->tx_chan = NULL; | |
230 | kfree(ipi); | |
231 | dev_warn(cdev, "mbox tx channel request failed\n"); | |
232 | return NULL; | |
233 | } | |
234 | ||
235 | ipi->rx_chan = mbox_request_channel_byname(mbox_cl, "rx"); | |
236 | if (IS_ERR(ipi->rx_chan)) { | |
237 | mbox_free_channel(ipi->tx_chan); | |
238 | ipi->rx_chan = NULL; | |
239 | ipi->tx_chan = NULL; | |
240 | kfree(ipi); | |
241 | dev_warn(cdev, "mbox rx channel request failed\n"); | |
242 | return NULL; | |
243 | } | |
244 | ||
245 | INIT_WORK(&ipi->mbox_work, handle_event_notified); | |
246 | ||
247 | return ipi; | |
248 | } | |
249 | ||
250 | static void zynqmp_r5_free_mbox(struct mbox_info *ipi) | |
251 | { | |
252 | if (!ipi) | |
253 | return; | |
254 | ||
255 | if (ipi->tx_chan) { | |
256 | mbox_free_channel(ipi->tx_chan); | |
257 | ipi->tx_chan = NULL; | |
258 | } | |
259 | ||
260 | if (ipi->rx_chan) { | |
261 | mbox_free_channel(ipi->rx_chan); | |
262 | ipi->rx_chan = NULL; | |
263 | } | |
264 | ||
265 | kfree(ipi); | |
266 | } | |
267 | ||
268 | /* | |
269 | * zynqmp_r5_core_kick() - kick a firmware if mbox is provided | |
270 | * @rproc: r5 core's corresponding rproc structure | |
271 | * @vqid: virtqueue ID | |
272 | */ | |
273 | static void zynqmp_r5_rproc_kick(struct rproc *rproc, int vqid) | |
274 | { | |
275 | struct zynqmp_r5_core *r5_core = rproc->priv; | |
276 | struct device *dev = r5_core->dev; | |
277 | struct zynqmp_ipi_message *mb_msg; | |
278 | struct mbox_info *ipi; | |
279 | int ret; | |
280 | ||
281 | ipi = r5_core->ipi; | |
282 | if (!ipi) | |
283 | return; | |
284 | ||
285 | mb_msg = (struct zynqmp_ipi_message *)ipi->tx_mc_buf; | |
286 | memcpy(mb_msg->data, &vqid, sizeof(vqid)); | |
287 | mb_msg->len = sizeof(vqid); | |
288 | ret = mbox_send_message(ipi->tx_chan, mb_msg); | |
289 | if (ret < 0) | |
290 | dev_warn(dev, "failed to send message\n"); | |
291 | } | |
292 | ||
6b291e80 TS |
293 | /* |
294 | * zynqmp_r5_set_mode() | |
295 | * | |
296 | * set RPU cluster and TCM operation mode | |
297 | * | |
298 | * @r5_core: pointer to zynqmp_r5_core type object | |
299 | * @fw_reg_val: value expected by firmware to configure RPU cluster mode | |
300 | * @tcm_mode: value expected by fw to configure TCM mode (lockstep or split) | |
301 | * | |
302 | * Return: 0 for success and < 0 for failure | |
303 | */ | |
304 | static int zynqmp_r5_set_mode(struct zynqmp_r5_core *r5_core, | |
305 | enum rpu_oper_mode fw_reg_val, | |
306 | enum rpu_tcm_comb tcm_mode) | |
307 | { | |
308 | int ret; | |
309 | ||
310 | ret = zynqmp_pm_set_rpu_mode(r5_core->pm_domain_id, fw_reg_val); | |
311 | if (ret < 0) { | |
312 | dev_err(r5_core->dev, "failed to set RPU mode\n"); | |
313 | return ret; | |
314 | } | |
315 | ||
316 | ret = zynqmp_pm_set_tcm_config(r5_core->pm_domain_id, tcm_mode); | |
317 | if (ret < 0) | |
318 | dev_err(r5_core->dev, "failed to configure TCM\n"); | |
319 | ||
320 | return ret; | |
321 | } | |
322 | ||
323 | /* | |
324 | * zynqmp_r5_rproc_start() | |
325 | * @rproc: single R5 core's corresponding rproc instance | |
326 | * | |
327 | * Start R5 Core from designated boot address. | |
328 | * | |
329 | * return 0 on success, otherwise non-zero value on failure | |
330 | */ | |
331 | static int zynqmp_r5_rproc_start(struct rproc *rproc) | |
332 | { | |
333 | struct zynqmp_r5_core *r5_core = rproc->priv; | |
334 | enum rpu_boot_mem bootmem; | |
335 | int ret; | |
336 | ||
337 | /* | |
338 | * The exception vector pointers (EVP) refer to the base-address of | |
339 | * exception vectors (for reset, IRQ, FIQ, etc). The reset-vector | |
340 | * starts at the base-address and subsequent vectors are on 4-byte | |
341 | * boundaries. | |
342 | * | |
343 | * Exception vectors can start either from 0x0000_0000 (LOVEC) or | |
344 | * from 0xFFFF_0000 (HIVEC) which is mapped in the OCM (On-Chip Memory) | |
345 | * | |
346 | * Usually firmware will put Exception vectors at LOVEC. | |
347 | * | |
348 | * It is not recommend that you change the exception vector. | |
349 | * Changing the EVP to HIVEC will result in increased interrupt latency | |
350 | * and jitter. Also, if the OCM is secured and the Cortex-R5F processor | |
351 | * is non-secured, then the Cortex-R5F processor cannot access the | |
352 | * HIVEC exception vectors in the OCM. | |
353 | */ | |
354 | bootmem = (rproc->bootaddr >= 0xFFFC0000) ? | |
355 | PM_RPU_BOOTMEM_HIVEC : PM_RPU_BOOTMEM_LOVEC; | |
356 | ||
357 | dev_dbg(r5_core->dev, "RPU boot addr 0x%llx from %s.", rproc->bootaddr, | |
358 | bootmem == PM_RPU_BOOTMEM_HIVEC ? "OCM" : "TCM"); | |
359 | ||
360 | ret = zynqmp_pm_request_wake(r5_core->pm_domain_id, 1, | |
361 | bootmem, ZYNQMP_PM_REQUEST_ACK_NO); | |
362 | if (ret) | |
363 | dev_err(r5_core->dev, | |
364 | "failed to start RPU = 0x%x\n", r5_core->pm_domain_id); | |
365 | return ret; | |
366 | } | |
367 | ||
368 | /* | |
369 | * zynqmp_r5_rproc_stop() | |
370 | * @rproc: single R5 core's corresponding rproc instance | |
371 | * | |
372 | * Power down R5 Core. | |
373 | * | |
374 | * return 0 on success, otherwise non-zero value on failure | |
375 | */ | |
376 | static int zynqmp_r5_rproc_stop(struct rproc *rproc) | |
377 | { | |
378 | struct zynqmp_r5_core *r5_core = rproc->priv; | |
379 | int ret; | |
380 | ||
381 | ret = zynqmp_pm_force_pwrdwn(r5_core->pm_domain_id, | |
382 | ZYNQMP_PM_REQUEST_ACK_BLOCKING); | |
383 | if (ret) | |
384 | dev_err(r5_core->dev, "failed to stop remoteproc RPU %d\n", ret); | |
385 | ||
386 | return ret; | |
387 | } | |
388 | ||
389 | /* | |
390 | * zynqmp_r5_mem_region_map() | |
391 | * @rproc: single R5 core's corresponding rproc instance | |
392 | * @mem: mem descriptor to map reserved memory-regions | |
393 | * | |
394 | * Callback to map va for memory-region's carveout. | |
395 | * | |
396 | * return 0 on success, otherwise non-zero value on failure | |
397 | */ | |
398 | static int zynqmp_r5_mem_region_map(struct rproc *rproc, | |
399 | struct rproc_mem_entry *mem) | |
400 | { | |
401 | void __iomem *va; | |
402 | ||
403 | va = ioremap_wc(mem->dma, mem->len); | |
404 | if (IS_ERR_OR_NULL(va)) | |
405 | return -ENOMEM; | |
406 | ||
407 | mem->va = (void *)va; | |
408 | ||
409 | return 0; | |
410 | } | |
411 | ||
412 | /* | |
413 | * zynqmp_r5_rproc_mem_unmap | |
414 | * @rproc: single R5 core's corresponding rproc instance | |
415 | * @mem: mem entry to unmap | |
416 | * | |
417 | * Unmap memory-region carveout | |
418 | * | |
419 | * return: always returns 0 | |
420 | */ | |
421 | static int zynqmp_r5_mem_region_unmap(struct rproc *rproc, | |
422 | struct rproc_mem_entry *mem) | |
423 | { | |
424 | iounmap((void __iomem *)mem->va); | |
425 | return 0; | |
426 | } | |
427 | ||
428 | /* | |
429 | * add_mem_regions_carveout() | |
430 | * @rproc: single R5 core's corresponding rproc instance | |
431 | * | |
432 | * Construct rproc mem carveouts from memory-region property nodes | |
433 | * | |
434 | * return 0 on success, otherwise non-zero value on failure | |
435 | */ | |
436 | static int add_mem_regions_carveout(struct rproc *rproc) | |
437 | { | |
438 | struct rproc_mem_entry *rproc_mem; | |
439 | struct zynqmp_r5_core *r5_core; | |
81c18e08 | 440 | struct of_phandle_iterator it; |
6b291e80 | 441 | struct reserved_mem *rmem; |
81c18e08 | 442 | int i = 0; |
6b291e80 | 443 | |
86660713 | 444 | r5_core = rproc->priv; |
6b291e80 | 445 | |
81c18e08 TS |
446 | /* Register associated reserved memory regions */ |
447 | of_phandle_iterator_init(&it, r5_core->np, "memory-region", NULL, 0); | |
6b291e80 | 448 | |
81c18e08 TS |
449 | while (of_phandle_iterator_next(&it) == 0) { |
450 | rmem = of_reserved_mem_lookup(it.node); | |
451 | if (!rmem) { | |
452 | of_node_put(it.node); | |
453 | dev_err(&rproc->dev, "unable to acquire memory-region\n"); | |
454 | return -EINVAL; | |
455 | } | |
456 | ||
457 | if (!strcmp(it.node->name, "vdev0buffer")) { | |
6b291e80 TS |
458 | /* Init reserved memory for vdev buffer */ |
459 | rproc_mem = rproc_of_resm_mem_entry_init(&rproc->dev, i, | |
460 | rmem->size, | |
461 | rmem->base, | |
81c18e08 | 462 | it.node->name); |
6b291e80 TS |
463 | } else { |
464 | /* Register associated reserved memory regions */ | |
465 | rproc_mem = rproc_mem_entry_init(&rproc->dev, NULL, | |
466 | (dma_addr_t)rmem->base, | |
467 | rmem->size, rmem->base, | |
468 | zynqmp_r5_mem_region_map, | |
469 | zynqmp_r5_mem_region_unmap, | |
81c18e08 | 470 | it.node->name); |
6b291e80 TS |
471 | } |
472 | ||
81c18e08 TS |
473 | if (!rproc_mem) { |
474 | of_node_put(it.node); | |
6b291e80 | 475 | return -ENOMEM; |
81c18e08 | 476 | } |
6b291e80 TS |
477 | |
478 | rproc_add_carveout(rproc, rproc_mem); | |
479 | ||
480 | dev_dbg(&rproc->dev, "reserved mem carveout %s addr=%llx, size=0x%llx", | |
81c18e08 TS |
481 | it.node->name, rmem->base, rmem->size); |
482 | i++; | |
6b291e80 TS |
483 | } |
484 | ||
485 | return 0; | |
486 | } | |
487 | ||
488 | /* | |
489 | * tcm_mem_unmap() | |
490 | * @rproc: single R5 core's corresponding rproc instance | |
491 | * @mem: tcm mem entry to unmap | |
492 | * | |
493 | * Unmap TCM banks when powering down R5 core. | |
494 | * | |
495 | * return always 0 | |
496 | */ | |
497 | static int tcm_mem_unmap(struct rproc *rproc, struct rproc_mem_entry *mem) | |
498 | { | |
499 | iounmap((void __iomem *)mem->va); | |
500 | ||
501 | return 0; | |
502 | } | |
503 | ||
504 | /* | |
505 | * tcm_mem_map() | |
506 | * @rproc: single R5 core's corresponding rproc instance | |
507 | * @mem: tcm memory entry descriptor | |
508 | * | |
509 | * Given TCM bank entry, this func setup virtual address for TCM bank | |
510 | * remoteproc carveout. It also takes care of va to da address translation | |
511 | * | |
512 | * return 0 on success, otherwise non-zero value on failure | |
513 | */ | |
514 | static int tcm_mem_map(struct rproc *rproc, | |
515 | struct rproc_mem_entry *mem) | |
516 | { | |
517 | void __iomem *va; | |
518 | ||
519 | va = ioremap_wc(mem->dma, mem->len); | |
520 | if (IS_ERR_OR_NULL(va)) | |
521 | return -ENOMEM; | |
522 | ||
523 | /* Update memory entry va */ | |
524 | mem->va = (void *)va; | |
525 | ||
526 | /* clear TCMs */ | |
527 | memset_io(va, 0, mem->len); | |
528 | ||
529 | /* | |
530 | * The R5s expect their TCM banks to be at address 0x0 and 0x2000, | |
531 | * while on the Linux side they are at 0xffexxxxx. | |
532 | * | |
533 | * Zero out the high 12 bits of the address. This will give | |
534 | * expected values for TCM Banks 0A and 0B (0x0 and 0x20000). | |
535 | */ | |
536 | mem->da &= 0x000fffff; | |
537 | ||
538 | /* | |
539 | * TCM Banks 1A and 1B still have to be translated. | |
540 | * | |
541 | * Below handle these two banks' absolute addresses (0xffe90000 and | |
542 | * 0xffeb0000) and convert to the expected relative addresses | |
543 | * (0x0 and 0x20000). | |
544 | */ | |
545 | if (mem->da == 0x90000 || mem->da == 0xB0000) | |
546 | mem->da -= 0x90000; | |
547 | ||
548 | /* if translated TCM bank address is not valid report error */ | |
549 | if (mem->da != 0x0 && mem->da != 0x20000) { | |
550 | dev_err(&rproc->dev, "invalid TCM address: %x\n", mem->da); | |
551 | return -EINVAL; | |
552 | } | |
553 | return 0; | |
554 | } | |
555 | ||
556 | /* | |
557 | * add_tcm_carveout_split_mode() | |
558 | * @rproc: single R5 core's corresponding rproc instance | |
559 | * | |
560 | * allocate and add remoteproc carveout for TCM memory in split mode | |
561 | * | |
562 | * return 0 on success, otherwise non-zero value on failure | |
563 | */ | |
564 | static int add_tcm_carveout_split_mode(struct rproc *rproc) | |
565 | { | |
566 | struct rproc_mem_entry *rproc_mem; | |
567 | struct zynqmp_r5_core *r5_core; | |
568 | int i, num_banks, ret; | |
569 | phys_addr_t bank_addr; | |
570 | struct device *dev; | |
571 | u32 pm_domain_id; | |
572 | size_t bank_size; | |
573 | char *bank_name; | |
574 | ||
86660713 | 575 | r5_core = rproc->priv; |
6b291e80 TS |
576 | dev = r5_core->dev; |
577 | num_banks = r5_core->tcm_bank_count; | |
578 | ||
579 | /* | |
580 | * Power-on Each 64KB TCM, | |
581 | * register its address space, map and unmap functions | |
582 | * and add carveouts accordingly | |
583 | */ | |
584 | for (i = 0; i < num_banks; i++) { | |
585 | bank_addr = r5_core->tcm_banks[i]->addr; | |
586 | bank_name = r5_core->tcm_banks[i]->bank_name; | |
587 | bank_size = r5_core->tcm_banks[i]->size; | |
588 | pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; | |
589 | ||
590 | ret = zynqmp_pm_request_node(pm_domain_id, | |
591 | ZYNQMP_PM_CAPABILITY_ACCESS, 0, | |
592 | ZYNQMP_PM_REQUEST_ACK_BLOCKING); | |
593 | if (ret < 0) { | |
594 | dev_err(dev, "failed to turn on TCM 0x%x", pm_domain_id); | |
595 | goto release_tcm_split; | |
596 | } | |
597 | ||
598 | dev_dbg(dev, "TCM carveout split mode %s addr=%llx, size=0x%lx", | |
599 | bank_name, bank_addr, bank_size); | |
600 | ||
601 | rproc_mem = rproc_mem_entry_init(dev, NULL, bank_addr, | |
602 | bank_size, bank_addr, | |
603 | tcm_mem_map, tcm_mem_unmap, | |
604 | bank_name); | |
605 | if (!rproc_mem) { | |
606 | ret = -ENOMEM; | |
607 | zynqmp_pm_release_node(pm_domain_id); | |
608 | goto release_tcm_split; | |
609 | } | |
610 | ||
611 | rproc_add_carveout(rproc, rproc_mem); | |
612 | } | |
613 | ||
614 | return 0; | |
615 | ||
616 | release_tcm_split: | |
617 | /* If failed, Turn off all TCM banks turned on before */ | |
618 | for (i--; i >= 0; i--) { | |
619 | pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; | |
620 | zynqmp_pm_release_node(pm_domain_id); | |
621 | } | |
622 | return ret; | |
623 | } | |
624 | ||
625 | /* | |
626 | * add_tcm_carveout_lockstep_mode() | |
627 | * @rproc: single R5 core's corresponding rproc instance | |
628 | * | |
629 | * allocate and add remoteproc carveout for TCM memory in lockstep mode | |
630 | * | |
631 | * return 0 on success, otherwise non-zero value on failure | |
632 | */ | |
633 | static int add_tcm_carveout_lockstep_mode(struct rproc *rproc) | |
634 | { | |
635 | struct rproc_mem_entry *rproc_mem; | |
636 | struct zynqmp_r5_core *r5_core; | |
637 | int i, num_banks, ret; | |
638 | phys_addr_t bank_addr; | |
639 | size_t bank_size = 0; | |
640 | struct device *dev; | |
641 | u32 pm_domain_id; | |
642 | char *bank_name; | |
643 | ||
86660713 | 644 | r5_core = rproc->priv; |
6b291e80 TS |
645 | dev = r5_core->dev; |
646 | ||
647 | /* Go through zynqmp banks for r5 node */ | |
648 | num_banks = r5_core->tcm_bank_count; | |
649 | ||
650 | /* | |
651 | * In lockstep mode, TCM is contiguous memory block | |
652 | * However, each TCM block still needs to be enabled individually. | |
653 | * So, Enable each TCM block individually, but add their size | |
654 | * to create contiguous memory region. | |
655 | */ | |
656 | bank_addr = r5_core->tcm_banks[0]->addr; | |
657 | bank_name = r5_core->tcm_banks[0]->bank_name; | |
658 | ||
659 | for (i = 0; i < num_banks; i++) { | |
660 | bank_size += r5_core->tcm_banks[i]->size; | |
661 | pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; | |
662 | ||
663 | /* Turn on each TCM bank individually */ | |
664 | ret = zynqmp_pm_request_node(pm_domain_id, | |
665 | ZYNQMP_PM_CAPABILITY_ACCESS, 0, | |
666 | ZYNQMP_PM_REQUEST_ACK_BLOCKING); | |
667 | if (ret < 0) { | |
668 | dev_err(dev, "failed to turn on TCM 0x%x", pm_domain_id); | |
669 | goto release_tcm_lockstep; | |
670 | } | |
671 | } | |
672 | ||
673 | dev_dbg(dev, "TCM add carveout lockstep mode %s addr=0x%llx, size=0x%lx", | |
674 | bank_name, bank_addr, bank_size); | |
675 | ||
676 | /* Register TCM address range, TCM map and unmap functions */ | |
677 | rproc_mem = rproc_mem_entry_init(dev, NULL, bank_addr, | |
678 | bank_size, bank_addr, | |
679 | tcm_mem_map, tcm_mem_unmap, | |
680 | bank_name); | |
681 | if (!rproc_mem) { | |
682 | ret = -ENOMEM; | |
683 | goto release_tcm_lockstep; | |
684 | } | |
685 | ||
686 | /* If registration is success, add carveouts */ | |
687 | rproc_add_carveout(rproc, rproc_mem); | |
688 | ||
689 | return 0; | |
690 | ||
691 | release_tcm_lockstep: | |
692 | /* If failed, Turn off all TCM banks turned on before */ | |
693 | for (i--; i >= 0; i--) { | |
694 | pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; | |
695 | zynqmp_pm_release_node(pm_domain_id); | |
696 | } | |
697 | return ret; | |
698 | } | |
699 | ||
700 | /* | |
701 | * add_tcm_banks() | |
702 | * @rproc: single R5 core's corresponding rproc instance | |
703 | * | |
704 | * allocate and add remoteproc carveouts for TCM memory based on cluster mode | |
705 | * | |
706 | * return 0 on success, otherwise non-zero value on failure | |
707 | */ | |
708 | static int add_tcm_banks(struct rproc *rproc) | |
709 | { | |
710 | struct zynqmp_r5_cluster *cluster; | |
711 | struct zynqmp_r5_core *r5_core; | |
712 | struct device *dev; | |
713 | ||
86660713 | 714 | r5_core = rproc->priv; |
6b291e80 TS |
715 | if (!r5_core) |
716 | return -EINVAL; | |
717 | ||
718 | dev = r5_core->dev; | |
719 | ||
720 | cluster = dev_get_drvdata(dev->parent); | |
721 | if (!cluster) { | |
722 | dev_err(dev->parent, "Invalid driver data\n"); | |
723 | return -EINVAL; | |
724 | } | |
725 | ||
726 | /* | |
727 | * In lockstep mode TCM banks are one contiguous memory region of 256Kb | |
728 | * In split mode, each TCM bank is 64Kb and not contiguous. | |
729 | * We add memory carveouts accordingly. | |
730 | */ | |
731 | if (cluster->mode == SPLIT_MODE) | |
732 | return add_tcm_carveout_split_mode(rproc); | |
733 | else if (cluster->mode == LOCKSTEP_MODE) | |
734 | return add_tcm_carveout_lockstep_mode(rproc); | |
735 | ||
736 | return -EINVAL; | |
737 | } | |
738 | ||
739 | /* | |
740 | * zynqmp_r5_parse_fw() | |
741 | * @rproc: single R5 core's corresponding rproc instance | |
742 | * @fw: ptr to firmware to be loaded onto r5 core | |
743 | * | |
744 | * get resource table if available | |
745 | * | |
746 | * return 0 on success, otherwise non-zero value on failure | |
747 | */ | |
748 | static int zynqmp_r5_parse_fw(struct rproc *rproc, const struct firmware *fw) | |
749 | { | |
750 | int ret; | |
751 | ||
752 | ret = rproc_elf_load_rsc_table(rproc, fw); | |
753 | if (ret == -EINVAL) { | |
754 | /* | |
755 | * resource table only required for IPC. | |
756 | * if not present, this is not necessarily an error; | |
757 | * for example, loading r5 hello world application | |
758 | * so simply inform user and keep going. | |
759 | */ | |
760 | dev_info(&rproc->dev, "no resource table found.\n"); | |
761 | ret = 0; | |
762 | } | |
763 | return ret; | |
764 | } | |
765 | ||
766 | /** | |
767 | * zynqmp_r5_rproc_prepare() | |
768 | * adds carveouts for TCM bank and reserved memory regions | |
769 | * | |
770 | * @rproc: Device node of each rproc | |
771 | * | |
772 | * Return: 0 for success else < 0 error code | |
773 | */ | |
774 | static int zynqmp_r5_rproc_prepare(struct rproc *rproc) | |
775 | { | |
776 | int ret; | |
777 | ||
778 | ret = add_tcm_banks(rproc); | |
779 | if (ret) { | |
780 | dev_err(&rproc->dev, "failed to get TCM banks, err %d\n", ret); | |
781 | return ret; | |
782 | } | |
783 | ||
784 | ret = add_mem_regions_carveout(rproc); | |
785 | if (ret) { | |
786 | dev_err(&rproc->dev, "failed to get reserve mem regions %d\n", ret); | |
787 | return ret; | |
788 | } | |
789 | ||
790 | return 0; | |
791 | } | |
792 | ||
793 | /** | |
794 | * zynqmp_r5_rproc_unprepare() | |
795 | * Turns off TCM banks using power-domain id | |
796 | * | |
797 | * @rproc: Device node of each rproc | |
798 | * | |
799 | * Return: always 0 | |
800 | */ | |
801 | static int zynqmp_r5_rproc_unprepare(struct rproc *rproc) | |
802 | { | |
803 | struct zynqmp_r5_core *r5_core; | |
804 | u32 pm_domain_id; | |
805 | int i; | |
806 | ||
86660713 | 807 | r5_core = rproc->priv; |
6b291e80 TS |
808 | |
809 | for (i = 0; i < r5_core->tcm_bank_count; i++) { | |
810 | pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; | |
811 | if (zynqmp_pm_release_node(pm_domain_id)) | |
812 | dev_warn(r5_core->dev, | |
813 | "can't turn off TCM bank 0x%x", pm_domain_id); | |
814 | } | |
815 | ||
816 | return 0; | |
817 | } | |
818 | ||
819 | static const struct rproc_ops zynqmp_r5_rproc_ops = { | |
820 | .prepare = zynqmp_r5_rproc_prepare, | |
821 | .unprepare = zynqmp_r5_rproc_unprepare, | |
822 | .start = zynqmp_r5_rproc_start, | |
823 | .stop = zynqmp_r5_rproc_stop, | |
824 | .load = rproc_elf_load_segments, | |
825 | .parse_fw = zynqmp_r5_parse_fw, | |
826 | .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, | |
827 | .sanity_check = rproc_elf_sanity_check, | |
828 | .get_boot_addr = rproc_elf_get_boot_addr, | |
5dfb28c2 | 829 | .kick = zynqmp_r5_rproc_kick, |
6b291e80 TS |
830 | }; |
831 | ||
832 | /** | |
833 | * zynqmp_r5_add_rproc_core() | |
834 | * Allocate and add struct rproc object for each r5f core | |
835 | * This is called for each individual r5f core | |
836 | * | |
837 | * @cdev: Device node of each r5 core | |
838 | * | |
839 | * Return: zynqmp_r5_core object for success else error code pointer | |
840 | */ | |
841 | static struct zynqmp_r5_core *zynqmp_r5_add_rproc_core(struct device *cdev) | |
842 | { | |
843 | struct zynqmp_r5_core *r5_core; | |
844 | struct rproc *r5_rproc; | |
845 | int ret; | |
846 | ||
847 | /* Set up DMA mask */ | |
848 | ret = dma_set_coherent_mask(cdev, DMA_BIT_MASK(32)); | |
849 | if (ret) | |
850 | return ERR_PTR(ret); | |
851 | ||
852 | /* Allocate remoteproc instance */ | |
853 | r5_rproc = rproc_alloc(cdev, dev_name(cdev), | |
854 | &zynqmp_r5_rproc_ops, | |
855 | NULL, sizeof(struct zynqmp_r5_core)); | |
856 | if (!r5_rproc) { | |
857 | dev_err(cdev, "failed to allocate memory for rproc instance\n"); | |
858 | return ERR_PTR(-ENOMEM); | |
859 | } | |
860 | ||
861 | r5_rproc->auto_boot = false; | |
86660713 | 862 | r5_core = r5_rproc->priv; |
6b291e80 TS |
863 | r5_core->dev = cdev; |
864 | r5_core->np = dev_of_node(cdev); | |
865 | if (!r5_core->np) { | |
866 | dev_err(cdev, "can't get device node for r5 core\n"); | |
867 | ret = -EINVAL; | |
868 | goto free_rproc; | |
869 | } | |
870 | ||
871 | /* Add R5 remoteproc core */ | |
872 | ret = rproc_add(r5_rproc); | |
873 | if (ret) { | |
874 | dev_err(cdev, "failed to add r5 remoteproc\n"); | |
875 | goto free_rproc; | |
876 | } | |
877 | ||
878 | r5_core->rproc = r5_rproc; | |
879 | return r5_core; | |
880 | ||
881 | free_rproc: | |
882 | rproc_free(r5_rproc); | |
883 | return ERR_PTR(ret); | |
884 | } | |
885 | ||
886 | /** | |
887 | * zynqmp_r5_get_tcm_node() | |
888 | * Ideally this function should parse tcm node and store information | |
889 | * in r5_core instance. For now, Hardcoded TCM information is used. | |
890 | * This approach is used as TCM bindings for system-dt is being developed | |
891 | * | |
892 | * @cluster: pointer to zynqmp_r5_cluster type object | |
893 | * | |
894 | * Return: 0 for success and < 0 error code for failure. | |
895 | */ | |
896 | static int zynqmp_r5_get_tcm_node(struct zynqmp_r5_cluster *cluster) | |
897 | { | |
898 | struct device *dev = cluster->dev; | |
899 | struct zynqmp_r5_core *r5_core; | |
900 | int tcm_bank_count, tcm_node; | |
901 | int i, j; | |
902 | ||
903 | tcm_bank_count = ARRAY_SIZE(zynqmp_tcm_banks); | |
904 | ||
905 | /* count per core tcm banks */ | |
906 | tcm_bank_count = tcm_bank_count / cluster->core_count; | |
907 | ||
908 | /* | |
909 | * r5 core 0 will use all of TCM banks in lockstep mode. | |
910 | * In split mode, r5 core0 will use 128k and r5 core1 will use another | |
911 | * 128k. Assign TCM banks to each core accordingly | |
912 | */ | |
913 | tcm_node = 0; | |
914 | for (i = 0; i < cluster->core_count; i++) { | |
915 | r5_core = cluster->r5_cores[i]; | |
916 | r5_core->tcm_banks = devm_kcalloc(dev, tcm_bank_count, | |
917 | sizeof(struct mem_bank_data *), | |
918 | GFP_KERNEL); | |
919 | if (!r5_core->tcm_banks) | |
920 | return -ENOMEM; | |
921 | ||
922 | for (j = 0; j < tcm_bank_count; j++) { | |
923 | /* | |
924 | * Use pre-defined TCM reg values. | |
925 | * Eventually this should be replaced by values | |
926 | * parsed from dts. | |
927 | */ | |
928 | r5_core->tcm_banks[j] = | |
929 | (struct mem_bank_data *)&zynqmp_tcm_banks[tcm_node]; | |
930 | tcm_node++; | |
931 | } | |
932 | ||
933 | r5_core->tcm_bank_count = tcm_bank_count; | |
934 | } | |
935 | ||
936 | return 0; | |
937 | } | |
938 | ||
6b291e80 TS |
939 | /* |
940 | * zynqmp_r5_core_init() | |
941 | * Create and initialize zynqmp_r5_core type object | |
942 | * | |
943 | * @cluster: pointer to zynqmp_r5_cluster type object | |
944 | * @fw_reg_val: value expected by firmware to configure RPU cluster mode | |
945 | * @tcm_mode: value expected by fw to configure TCM mode (lockstep or split) | |
946 | * | |
947 | * Return: 0 for success and error code for failure. | |
948 | */ | |
949 | static int zynqmp_r5_core_init(struct zynqmp_r5_cluster *cluster, | |
950 | enum rpu_oper_mode fw_reg_val, | |
951 | enum rpu_tcm_comb tcm_mode) | |
952 | { | |
953 | struct device *dev = cluster->dev; | |
954 | struct zynqmp_r5_core *r5_core; | |
955 | int ret, i; | |
956 | ||
957 | ret = zynqmp_r5_get_tcm_node(cluster); | |
958 | if (ret < 0) { | |
959 | dev_err(dev, "can't get tcm node, err %d\n", ret); | |
960 | return ret; | |
961 | } | |
962 | ||
963 | for (i = 0; i < cluster->core_count; i++) { | |
964 | r5_core = cluster->r5_cores[i]; | |
965 | ||
6b291e80 TS |
966 | /* Initialize r5 cores with power-domains parsed from dts */ |
967 | ret = of_property_read_u32_index(r5_core->np, "power-domains", | |
968 | 1, &r5_core->pm_domain_id); | |
969 | if (ret) { | |
970 | dev_err(dev, "failed to get power-domains property\n"); | |
971 | return ret; | |
972 | } | |
973 | ||
974 | ret = zynqmp_r5_set_mode(r5_core, fw_reg_val, tcm_mode); | |
975 | if (ret) { | |
976 | dev_err(dev, "failed to set r5 cluster mode %d, err %d\n", | |
977 | cluster->mode, ret); | |
978 | return ret; | |
979 | } | |
980 | } | |
981 | ||
982 | return 0; | |
983 | } | |
984 | ||
985 | /* | |
986 | * zynqmp_r5_cluster_init() | |
987 | * Create and initialize zynqmp_r5_cluster type object | |
988 | * | |
989 | * @cluster: pointer to zynqmp_r5_cluster type object | |
990 | * | |
991 | * Return: 0 for success and error code for failure. | |
992 | */ | |
993 | static int zynqmp_r5_cluster_init(struct zynqmp_r5_cluster *cluster) | |
994 | { | |
995 | enum zynqmp_r5_cluster_mode cluster_mode = LOCKSTEP_MODE; | |
996 | struct device *dev = cluster->dev; | |
997 | struct device_node *dev_node = dev_of_node(dev); | |
998 | struct platform_device *child_pdev; | |
999 | struct zynqmp_r5_core **r5_cores; | |
1000 | enum rpu_oper_mode fw_reg_val; | |
1001 | struct device **child_devs; | |
1002 | struct device_node *child; | |
1003 | enum rpu_tcm_comb tcm_mode; | |
1004 | int core_count, ret, i; | |
5dfb28c2 | 1005 | struct mbox_info *ipi; |
6b291e80 TS |
1006 | |
1007 | ret = of_property_read_u32(dev_node, "xlnx,cluster-mode", &cluster_mode); | |
1008 | ||
1009 | /* | |
1010 | * on success returns 0, if not defined then returns -EINVAL, | |
1011 | * In that case, default is LOCKSTEP mode. Other than that | |
1012 | * returns relative error code < 0. | |
1013 | */ | |
1014 | if (ret != -EINVAL && ret != 0) { | |
1015 | dev_err(dev, "Invalid xlnx,cluster-mode property\n"); | |
1016 | return ret; | |
1017 | } | |
1018 | ||
1019 | /* | |
1020 | * For now driver only supports split mode and lockstep mode. | |
1021 | * fail driver probe if either of that is not set in dts. | |
1022 | */ | |
1023 | if (cluster_mode == LOCKSTEP_MODE) { | |
1024 | tcm_mode = PM_RPU_TCM_COMB; | |
1025 | fw_reg_val = PM_RPU_MODE_LOCKSTEP; | |
1026 | } else if (cluster_mode == SPLIT_MODE) { | |
1027 | tcm_mode = PM_RPU_TCM_SPLIT; | |
1028 | fw_reg_val = PM_RPU_MODE_SPLIT; | |
1029 | } else { | |
1030 | dev_err(dev, "driver does not support cluster mode %d\n", cluster_mode); | |
1031 | return -EINVAL; | |
1032 | } | |
1033 | ||
1034 | /* | |
1035 | * Number of cores is decided by number of child nodes of | |
1036 | * r5f subsystem node in dts. If Split mode is used in dts | |
1037 | * 2 child nodes are expected. | |
1038 | * In lockstep mode if two child nodes are available, | |
1039 | * only use first child node and consider it as core0 | |
1040 | * and ignore core1 dt node. | |
1041 | */ | |
1042 | core_count = of_get_available_child_count(dev_node); | |
1043 | if (core_count == 0) { | |
1044 | dev_err(dev, "Invalid number of r5 cores %d", core_count); | |
1045 | return -EINVAL; | |
1046 | } else if (cluster_mode == SPLIT_MODE && core_count != 2) { | |
1047 | dev_err(dev, "Invalid number of r5 cores for split mode\n"); | |
1048 | return -EINVAL; | |
1049 | } else if (cluster_mode == LOCKSTEP_MODE && core_count == 2) { | |
1050 | dev_warn(dev, "Only r5 core0 will be used\n"); | |
1051 | core_count = 1; | |
1052 | } | |
1053 | ||
1054 | child_devs = kcalloc(core_count, sizeof(struct device *), GFP_KERNEL); | |
1055 | if (!child_devs) | |
1056 | return -ENOMEM; | |
1057 | ||
1058 | r5_cores = kcalloc(core_count, | |
1059 | sizeof(struct zynqmp_r5_core *), GFP_KERNEL); | |
1060 | if (!r5_cores) { | |
1061 | kfree(child_devs); | |
1062 | return -ENOMEM; | |
1063 | } | |
1064 | ||
1065 | i = 0; | |
1066 | for_each_available_child_of_node(dev_node, child) { | |
1067 | child_pdev = of_find_device_by_node(child); | |
1068 | if (!child_pdev) { | |
1069 | of_node_put(child); | |
1070 | ret = -ENODEV; | |
1071 | goto release_r5_cores; | |
1072 | } | |
1073 | ||
1074 | child_devs[i] = &child_pdev->dev; | |
1075 | ||
1076 | /* create and add remoteproc instance of type struct rproc */ | |
1077 | r5_cores[i] = zynqmp_r5_add_rproc_core(&child_pdev->dev); | |
1078 | if (IS_ERR(r5_cores[i])) { | |
1079 | of_node_put(child); | |
1080 | ret = PTR_ERR(r5_cores[i]); | |
1081 | r5_cores[i] = NULL; | |
1082 | goto release_r5_cores; | |
1083 | } | |
1084 | ||
5dfb28c2 TS |
1085 | /* |
1086 | * If mailbox nodes are disabled using "status" property then | |
1087 | * setting up mailbox channels will fail. | |
1088 | */ | |
1089 | ipi = zynqmp_r5_setup_mbox(&child_pdev->dev); | |
1090 | if (ipi) { | |
1091 | r5_cores[i]->ipi = ipi; | |
1092 | ipi->r5_core = r5_cores[i]; | |
1093 | } | |
1094 | ||
6b291e80 TS |
1095 | /* |
1096 | * If two child nodes are available in dts in lockstep mode, | |
1097 | * then ignore second child node. | |
1098 | */ | |
1099 | if (cluster_mode == LOCKSTEP_MODE) { | |
1100 | of_node_put(child); | |
1101 | break; | |
1102 | } | |
1103 | ||
1104 | i++; | |
1105 | } | |
1106 | ||
1107 | cluster->mode = cluster_mode; | |
1108 | cluster->core_count = core_count; | |
1109 | cluster->r5_cores = r5_cores; | |
1110 | ||
1111 | ret = zynqmp_r5_core_init(cluster, fw_reg_val, tcm_mode); | |
1112 | if (ret < 0) { | |
1113 | dev_err(dev, "failed to init r5 core err %d\n", ret); | |
1114 | cluster->core_count = 0; | |
1115 | cluster->r5_cores = NULL; | |
1116 | ||
1117 | /* | |
1118 | * at this point rproc resources for each core are allocated. | |
1119 | * adjust index to free resources in reverse order | |
1120 | */ | |
1121 | i = core_count - 1; | |
1122 | goto release_r5_cores; | |
1123 | } | |
1124 | ||
1125 | kfree(child_devs); | |
1126 | return 0; | |
1127 | ||
1128 | release_r5_cores: | |
1129 | while (i >= 0) { | |
1130 | put_device(child_devs[i]); | |
1131 | if (r5_cores[i]) { | |
5dfb28c2 | 1132 | zynqmp_r5_free_mbox(r5_cores[i]->ipi); |
6b291e80 TS |
1133 | of_reserved_mem_device_release(r5_cores[i]->dev); |
1134 | rproc_del(r5_cores[i]->rproc); | |
1135 | rproc_free(r5_cores[i]->rproc); | |
1136 | } | |
1137 | i--; | |
1138 | } | |
1139 | kfree(r5_cores); | |
1140 | kfree(child_devs); | |
1141 | return ret; | |
1142 | } | |
1143 | ||
1144 | static void zynqmp_r5_cluster_exit(void *data) | |
1145 | { | |
86660713 | 1146 | struct platform_device *pdev = data; |
6b291e80 TS |
1147 | struct zynqmp_r5_cluster *cluster; |
1148 | struct zynqmp_r5_core *r5_core; | |
1149 | int i; | |
1150 | ||
86660713 | 1151 | cluster = platform_get_drvdata(pdev); |
6b291e80 TS |
1152 | if (!cluster) |
1153 | return; | |
1154 | ||
1155 | for (i = 0; i < cluster->core_count; i++) { | |
1156 | r5_core = cluster->r5_cores[i]; | |
5dfb28c2 | 1157 | zynqmp_r5_free_mbox(r5_core->ipi); |
6b291e80 TS |
1158 | of_reserved_mem_device_release(r5_core->dev); |
1159 | put_device(r5_core->dev); | |
1160 | rproc_del(r5_core->rproc); | |
1161 | rproc_free(r5_core->rproc); | |
1162 | } | |
1163 | ||
1164 | kfree(cluster->r5_cores); | |
1165 | kfree(cluster); | |
1166 | platform_set_drvdata(pdev, NULL); | |
1167 | } | |
1168 | ||
1169 | /* | |
1170 | * zynqmp_r5_remoteproc_probe() | |
1171 | * parse device-tree, initialize hardware and allocate required resources | |
1172 | * and remoteproc ops | |
1173 | * | |
1174 | * @pdev: domain platform device for R5 cluster | |
1175 | * | |
1176 | * Return: 0 for success and < 0 for failure. | |
1177 | */ | |
1178 | static int zynqmp_r5_remoteproc_probe(struct platform_device *pdev) | |
1179 | { | |
1180 | struct zynqmp_r5_cluster *cluster; | |
1181 | struct device *dev = &pdev->dev; | |
1182 | int ret; | |
1183 | ||
1184 | cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); | |
1185 | if (!cluster) | |
1186 | return -ENOMEM; | |
1187 | ||
1188 | cluster->dev = dev; | |
1189 | ||
1190 | ret = devm_of_platform_populate(dev); | |
1191 | if (ret) { | |
1192 | dev_err_probe(dev, ret, "failed to populate platform dev\n"); | |
1193 | kfree(cluster); | |
1194 | return ret; | |
1195 | } | |
1196 | ||
1197 | /* wire in so each core can be cleaned up at driver remove */ | |
1198 | platform_set_drvdata(pdev, cluster); | |
1199 | ||
1200 | ret = zynqmp_r5_cluster_init(cluster); | |
1201 | if (ret) { | |
1202 | kfree(cluster); | |
1203 | platform_set_drvdata(pdev, NULL); | |
1204 | dev_err_probe(dev, ret, "Invalid r5f subsystem device tree\n"); | |
1205 | return ret; | |
1206 | } | |
1207 | ||
1208 | ret = devm_add_action_or_reset(dev, zynqmp_r5_cluster_exit, pdev); | |
1209 | if (ret) | |
1210 | return ret; | |
1211 | ||
1212 | return 0; | |
1213 | } | |
1214 | ||
1215 | /* Match table for OF platform binding */ | |
1216 | static const struct of_device_id zynqmp_r5_remoteproc_match[] = { | |
1217 | { .compatible = "xlnx,zynqmp-r5fss", }, | |
1218 | { /* end of list */ }, | |
1219 | }; | |
1220 | MODULE_DEVICE_TABLE(of, zynqmp_r5_remoteproc_match); | |
1221 | ||
1222 | static struct platform_driver zynqmp_r5_remoteproc_driver = { | |
1223 | .probe = zynqmp_r5_remoteproc_probe, | |
1224 | .driver = { | |
1225 | .name = "zynqmp_r5_remoteproc", | |
1226 | .of_match_table = zynqmp_r5_remoteproc_match, | |
1227 | }, | |
1228 | }; | |
1229 | module_platform_driver(zynqmp_r5_remoteproc_driver); | |
1230 | ||
1231 | MODULE_DESCRIPTION("Xilinx R5F remote processor driver"); | |
1232 | MODULE_AUTHOR("Xilinx Inc."); | |
1233 | MODULE_LICENSE("GPL"); |