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