1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Freescale data path resource container (DPRC) driver |
4 | * |
5 | * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. |
6 | * Copyright 2019-2020 NXP |
7 | * Author: German Rivera <German.Rivera@freescale.com> |
8 | * |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/fsl/mc.h> |
15 | |
16 | #include "fsl-mc-private.h" |
17 | |
18 | #define FSL_MC_DPRC_DRIVER_NAME "fsl_mc_dprc" |
19 | |
20 | struct fsl_mc_child_objs { |
21 | int child_count; |
22 | struct fsl_mc_obj_desc *child_array; |
23 | }; |
24 | |
25 | static bool fsl_mc_device_match(struct fsl_mc_device *mc_dev, |
26 | struct fsl_mc_obj_desc *obj_desc) |
27 | { |
28 | return mc_dev->obj_desc.id == obj_desc->id && |
29 | strcmp(mc_dev->obj_desc.type, obj_desc->type) == 0; |
30 | } |
31 | |
32 | static bool fsl_mc_obj_desc_is_allocatable(struct fsl_mc_obj_desc *obj) |
33 | { |
34 | if (strcmp(obj->type, "dpmcp" ) == 0 || |
35 | strcmp(obj->type, "dpcon" ) == 0 || |
36 | strcmp(obj->type, "dpbp" ) == 0) |
37 | return true; |
38 | else |
39 | return false; |
40 | } |
41 | |
42 | static int __fsl_mc_device_remove_if_not_in_mc(struct device *dev, void *data) |
43 | { |
44 | int i; |
45 | struct fsl_mc_child_objs *objs; |
46 | struct fsl_mc_device *mc_dev; |
47 | |
48 | if (!dev_is_fsl_mc(dev)) |
49 | return 0; |
50 | |
51 | mc_dev = to_fsl_mc_device(dev); |
52 | objs = data; |
53 | |
54 | for (i = 0; i < objs->child_count; i++) { |
55 | struct fsl_mc_obj_desc *obj_desc = &objs->child_array[i]; |
56 | |
57 | if (strlen(obj_desc->type) != 0 && |
58 | fsl_mc_device_match(mc_dev, obj_desc)) |
59 | break; |
60 | } |
61 | |
62 | if (i == objs->child_count) |
63 | fsl_mc_device_remove(mc_dev); |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static int __fsl_mc_device_remove(struct device *dev, void *data) |
69 | { |
70 | if (!dev_is_fsl_mc(dev)) |
71 | return 0; |
72 | |
73 | fsl_mc_device_remove(to_fsl_mc_device(dev)); |
74 | return 0; |
75 | } |
76 | |
77 | /** |
78 | * dprc_remove_devices - Removes devices for objects removed from a DPRC |
79 | * |
80 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object |
81 | * @obj_desc_array: array of object descriptors for child objects currently |
82 | * present in the DPRC in the MC. |
83 | * @num_child_objects_in_mc: number of entries in obj_desc_array |
84 | * |
85 | * Synchronizes the state of the Linux bus driver with the actual state of |
86 | * the MC by removing devices that represent MC objects that have |
87 | * been dynamically removed in the physical DPRC. |
88 | */ |
89 | void dprc_remove_devices(struct fsl_mc_device *mc_bus_dev, |
90 | struct fsl_mc_obj_desc *obj_desc_array, |
91 | int num_child_objects_in_mc) |
92 | { |
93 | if (num_child_objects_in_mc != 0) { |
94 | /* |
95 | * Remove child objects that are in the DPRC in Linux, |
96 | * but not in the MC: |
97 | */ |
98 | struct fsl_mc_child_objs objs; |
99 | |
100 | objs.child_count = num_child_objects_in_mc; |
101 | objs.child_array = obj_desc_array; |
102 | device_for_each_child(dev: &mc_bus_dev->dev, data: &objs, |
103 | fn: __fsl_mc_device_remove_if_not_in_mc); |
104 | } else { |
105 | /* |
106 | * There are no child objects for this DPRC in the MC. |
107 | * So, remove all the child devices from Linux: |
108 | */ |
109 | device_for_each_child(dev: &mc_bus_dev->dev, NULL, |
110 | fn: __fsl_mc_device_remove); |
111 | } |
112 | } |
113 | EXPORT_SYMBOL_GPL(dprc_remove_devices); |
114 | |
115 | static int __fsl_mc_device_match(struct device *dev, void *data) |
116 | { |
117 | struct fsl_mc_obj_desc *obj_desc = data; |
118 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); |
119 | |
120 | return fsl_mc_device_match(mc_dev, obj_desc); |
121 | } |
122 | |
123 | struct fsl_mc_device *fsl_mc_device_lookup(struct fsl_mc_obj_desc *obj_desc, |
124 | struct fsl_mc_device *mc_bus_dev) |
125 | { |
126 | struct device *dev; |
127 | |
128 | dev = device_find_child(dev: &mc_bus_dev->dev, data: obj_desc, |
129 | match: __fsl_mc_device_match); |
130 | |
131 | return dev ? to_fsl_mc_device(dev) : NULL; |
132 | } |
133 | |
134 | /** |
135 | * check_plugged_state_change - Check change in an MC object's plugged state |
136 | * |
137 | * @mc_dev: pointer to the fsl-mc device for a given MC object |
138 | * @obj_desc: pointer to the MC object's descriptor in the MC |
139 | * |
140 | * If the plugged state has changed from unplugged to plugged, the fsl-mc |
141 | * device is bound to the corresponding device driver. |
142 | * If the plugged state has changed from plugged to unplugged, the fsl-mc |
143 | * device is unbound from the corresponding device driver. |
144 | */ |
145 | static void check_plugged_state_change(struct fsl_mc_device *mc_dev, |
146 | struct fsl_mc_obj_desc *obj_desc) |
147 | { |
148 | int error; |
149 | u32 plugged_flag_at_mc = |
150 | obj_desc->state & FSL_MC_OBJ_STATE_PLUGGED; |
151 | |
152 | if (plugged_flag_at_mc != |
153 | (mc_dev->obj_desc.state & FSL_MC_OBJ_STATE_PLUGGED)) { |
154 | if (plugged_flag_at_mc) { |
155 | mc_dev->obj_desc.state |= FSL_MC_OBJ_STATE_PLUGGED; |
156 | error = device_attach(dev: &mc_dev->dev); |
157 | if (error < 0) { |
158 | dev_err(&mc_dev->dev, |
159 | "device_attach() failed: %d\n" , |
160 | error); |
161 | } |
162 | } else { |
163 | mc_dev->obj_desc.state &= ~FSL_MC_OBJ_STATE_PLUGGED; |
164 | device_release_driver(dev: &mc_dev->dev); |
165 | } |
166 | } |
167 | } |
168 | |
169 | static void fsl_mc_obj_device_add(struct fsl_mc_device *mc_bus_dev, |
170 | struct fsl_mc_obj_desc *obj_desc) |
171 | { |
172 | int error; |
173 | struct fsl_mc_device *child_dev; |
174 | |
175 | /* |
176 | * Check if device is already known to Linux: |
177 | */ |
178 | child_dev = fsl_mc_device_lookup(obj_desc, mc_bus_dev); |
179 | if (child_dev) { |
180 | check_plugged_state_change(mc_dev: child_dev, obj_desc); |
181 | put_device(dev: &child_dev->dev); |
182 | } else { |
183 | error = fsl_mc_device_add(obj_desc, NULL, parent_dev: &mc_bus_dev->dev, |
184 | new_mc_dev: &child_dev); |
185 | if (error < 0) |
186 | return; |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * dprc_add_new_devices - Adds devices to the logical bus for a DPRC |
192 | * |
193 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object |
194 | * @obj_desc_array: array of device descriptors for child devices currently |
195 | * present in the physical DPRC. |
196 | * @num_child_objects_in_mc: number of entries in obj_desc_array |
197 | * |
198 | * Synchronizes the state of the Linux bus driver with the actual |
199 | * state of the MC by adding objects that have been newly discovered |
200 | * in the physical DPRC. |
201 | */ |
202 | static void dprc_add_new_devices(struct fsl_mc_device *mc_bus_dev, |
203 | struct fsl_mc_obj_desc *obj_desc_array, |
204 | int num_child_objects_in_mc) |
205 | { |
206 | int i; |
207 | |
208 | /* probe the allocable objects first */ |
209 | for (i = 0; i < num_child_objects_in_mc; i++) { |
210 | struct fsl_mc_obj_desc *obj_desc = &obj_desc_array[i]; |
211 | |
212 | if (strlen(obj_desc->type) > 0 && |
213 | fsl_mc_obj_desc_is_allocatable(obj: obj_desc)) |
214 | fsl_mc_obj_device_add(mc_bus_dev, obj_desc); |
215 | } |
216 | |
217 | for (i = 0; i < num_child_objects_in_mc; i++) { |
218 | struct fsl_mc_obj_desc *obj_desc = &obj_desc_array[i]; |
219 | |
220 | if (strlen(obj_desc->type) > 0 && |
221 | !fsl_mc_obj_desc_is_allocatable(obj: obj_desc)) |
222 | fsl_mc_obj_device_add(mc_bus_dev, obj_desc); |
223 | } |
224 | } |
225 | |
226 | /** |
227 | * dprc_scan_objects - Discover objects in a DPRC |
228 | * |
229 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object |
230 | * @alloc_interrupts: if true the function allocates the interrupt pool, |
231 | * otherwise the interrupt allocation is delayed |
232 | * |
233 | * Detects objects added and removed from a DPRC and synchronizes the |
234 | * state of the Linux bus driver, MC by adding and removing |
235 | * devices accordingly. |
236 | * Two types of devices can be found in a DPRC: allocatable objects (e.g., |
237 | * dpbp, dpmcp) and non-allocatable devices (e.g., dprc, dpni). |
238 | * All allocatable devices needed to be probed before all non-allocatable |
239 | * devices, to ensure that device drivers for non-allocatable |
240 | * devices can allocate any type of allocatable devices. |
241 | * That is, we need to ensure that the corresponding resource pools are |
242 | * populated before they can get allocation requests from probe callbacks |
243 | * of the device drivers for the non-allocatable devices. |
244 | */ |
245 | int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev, |
246 | bool alloc_interrupts) |
247 | { |
248 | int num_child_objects; |
249 | int dprc_get_obj_failures; |
250 | int error; |
251 | unsigned int irq_count = mc_bus_dev->obj_desc.irq_count; |
252 | struct fsl_mc_obj_desc *child_obj_desc_array = NULL; |
253 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); |
254 | |
255 | error = dprc_get_obj_count(mc_io: mc_bus_dev->mc_io, |
256 | cmd_flags: 0, |
257 | token: mc_bus_dev->mc_handle, |
258 | obj_count: &num_child_objects); |
259 | if (error < 0) { |
260 | dev_err(&mc_bus_dev->dev, "dprc_get_obj_count() failed: %d\n" , |
261 | error); |
262 | return error; |
263 | } |
264 | |
265 | if (num_child_objects != 0) { |
266 | int i; |
267 | |
268 | child_obj_desc_array = |
269 | devm_kmalloc_array(dev: &mc_bus_dev->dev, n: num_child_objects, |
270 | size: sizeof(*child_obj_desc_array), |
271 | GFP_KERNEL); |
272 | if (!child_obj_desc_array) |
273 | return -ENOMEM; |
274 | |
275 | /* |
276 | * Discover objects currently present in the physical DPRC: |
277 | */ |
278 | dprc_get_obj_failures = 0; |
279 | for (i = 0; i < num_child_objects; i++) { |
280 | struct fsl_mc_obj_desc *obj_desc = |
281 | &child_obj_desc_array[i]; |
282 | |
283 | error = dprc_get_obj(mc_io: mc_bus_dev->mc_io, |
284 | cmd_flags: 0, |
285 | token: mc_bus_dev->mc_handle, |
286 | obj_index: i, obj_desc); |
287 | if (error < 0) { |
288 | dev_err(&mc_bus_dev->dev, |
289 | "dprc_get_obj(i=%d) failed: %d\n" , |
290 | i, error); |
291 | /* |
292 | * Mark the obj entry as "invalid", by using the |
293 | * empty string as obj type: |
294 | */ |
295 | obj_desc->type[0] = '\0'; |
296 | obj_desc->id = error; |
297 | dprc_get_obj_failures++; |
298 | continue; |
299 | } |
300 | |
301 | /* |
302 | * add a quirk for all versions of dpsec < 4.0...none |
303 | * are coherent regardless of what the MC reports. |
304 | */ |
305 | if ((strcmp(obj_desc->type, "dpseci" ) == 0) && |
306 | (obj_desc->ver_major < 4)) |
307 | obj_desc->flags |= |
308 | FSL_MC_OBJ_FLAG_NO_MEM_SHAREABILITY; |
309 | |
310 | irq_count += obj_desc->irq_count; |
311 | dev_dbg(&mc_bus_dev->dev, |
312 | "Discovered object: type %s, id %d\n" , |
313 | obj_desc->type, obj_desc->id); |
314 | } |
315 | |
316 | if (dprc_get_obj_failures != 0) { |
317 | dev_err(&mc_bus_dev->dev, |
318 | "%d out of %d devices could not be retrieved\n" , |
319 | dprc_get_obj_failures, num_child_objects); |
320 | } |
321 | } |
322 | |
323 | /* |
324 | * Allocate IRQ's before binding the scanned devices with their |
325 | * respective drivers. |
326 | */ |
327 | if (dev_get_msi_domain(dev: &mc_bus_dev->dev)) { |
328 | if (irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS) { |
329 | dev_warn(&mc_bus_dev->dev, |
330 | "IRQs needed (%u) exceed IRQs preallocated (%u)\n" , |
331 | irq_count, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); |
332 | } |
333 | |
334 | if (alloc_interrupts && !mc_bus->irq_resources) { |
335 | error = fsl_mc_populate_irq_pool(mc_bus_dev, |
336 | FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); |
337 | if (error < 0) |
338 | return error; |
339 | } |
340 | } |
341 | |
342 | dprc_remove_devices(mc_bus_dev, child_obj_desc_array, |
343 | num_child_objects); |
344 | |
345 | dprc_add_new_devices(mc_bus_dev, obj_desc_array: child_obj_desc_array, |
346 | num_child_objects_in_mc: num_child_objects); |
347 | |
348 | if (child_obj_desc_array) |
349 | devm_kfree(dev: &mc_bus_dev->dev, p: child_obj_desc_array); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | /** |
355 | * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state |
356 | * |
357 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object |
358 | * @alloc_interrupts: if true the function allocates the interrupt pool, |
359 | * otherwise the interrupt allocation is delayed |
360 | * Scans the physical DPRC and synchronizes the state of the Linux |
361 | * bus driver with the actual state of the MC by adding and removing |
362 | * devices as appropriate. |
363 | */ |
364 | int dprc_scan_container(struct fsl_mc_device *mc_bus_dev, |
365 | bool alloc_interrupts) |
366 | { |
367 | int error = 0; |
368 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); |
369 | |
370 | fsl_mc_init_all_resource_pools(mc_bus_dev); |
371 | |
372 | /* |
373 | * Discover objects in the DPRC: |
374 | */ |
375 | mutex_lock(&mc_bus->scan_mutex); |
376 | error = dprc_scan_objects(mc_bus_dev, alloc_interrupts); |
377 | mutex_unlock(lock: &mc_bus->scan_mutex); |
378 | |
379 | return error; |
380 | } |
381 | EXPORT_SYMBOL_GPL(dprc_scan_container); |
382 | |
383 | /** |
384 | * dprc_irq0_handler - Regular ISR for DPRC interrupt 0 |
385 | * |
386 | * @irq_num: IRQ number of the interrupt being handled |
387 | * @arg: Pointer to device structure |
388 | */ |
389 | static irqreturn_t dprc_irq0_handler(int irq_num, void *arg) |
390 | { |
391 | return IRQ_WAKE_THREAD; |
392 | } |
393 | |
394 | /** |
395 | * dprc_irq0_handler_thread - Handler thread function for DPRC interrupt 0 |
396 | * |
397 | * @irq_num: IRQ number of the interrupt being handled |
398 | * @arg: Pointer to device structure |
399 | */ |
400 | static irqreturn_t dprc_irq0_handler_thread(int irq_num, void *arg) |
401 | { |
402 | int error; |
403 | u32 status; |
404 | struct device *dev = arg; |
405 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); |
406 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
407 | struct fsl_mc_io *mc_io = mc_dev->mc_io; |
408 | int irq = mc_dev->irqs[0]->virq; |
409 | |
410 | dev_dbg(dev, "DPRC IRQ %d triggered on CPU %u\n" , |
411 | irq_num, smp_processor_id()); |
412 | |
413 | if (!(mc_dev->flags & FSL_MC_IS_DPRC)) |
414 | return IRQ_HANDLED; |
415 | |
416 | mutex_lock(&mc_bus->scan_mutex); |
417 | if (irq != (u32)irq_num) |
418 | goto out; |
419 | |
420 | status = 0; |
421 | error = dprc_get_irq_status(mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, |
422 | status: &status); |
423 | if (error < 0) { |
424 | dev_err(dev, |
425 | "dprc_get_irq_status() failed: %d\n" , error); |
426 | goto out; |
427 | } |
428 | |
429 | error = dprc_clear_irq_status(mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, |
430 | status); |
431 | if (error < 0) { |
432 | dev_err(dev, |
433 | "dprc_clear_irq_status() failed: %d\n" , error); |
434 | goto out; |
435 | } |
436 | |
437 | if (status & (DPRC_IRQ_EVENT_OBJ_ADDED | |
438 | DPRC_IRQ_EVENT_OBJ_REMOVED | |
439 | DPRC_IRQ_EVENT_CONTAINER_DESTROYED | |
440 | DPRC_IRQ_EVENT_OBJ_DESTROYED | |
441 | DPRC_IRQ_EVENT_OBJ_CREATED)) { |
442 | |
443 | error = dprc_scan_objects(mc_bus_dev: mc_dev, alloc_interrupts: true); |
444 | if (error < 0) { |
445 | /* |
446 | * If the error is -ENXIO, we ignore it, as it indicates |
447 | * that the object scan was aborted, as we detected that |
448 | * an object was removed from the DPRC in the MC, while |
449 | * we were scanning the DPRC. |
450 | */ |
451 | if (error != -ENXIO) { |
452 | dev_err(dev, "dprc_scan_objects() failed: %d\n" , |
453 | error); |
454 | } |
455 | |
456 | goto out; |
457 | } |
458 | } |
459 | |
460 | out: |
461 | mutex_unlock(lock: &mc_bus->scan_mutex); |
462 | return IRQ_HANDLED; |
463 | } |
464 | |
465 | /* |
466 | * Disable and clear interrupt for a given DPRC object |
467 | */ |
468 | int disable_dprc_irq(struct fsl_mc_device *mc_dev) |
469 | { |
470 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
471 | int error; |
472 | struct fsl_mc_io *mc_io = mc_dev->mc_io; |
473 | |
474 | /* |
475 | * Disable generation of interrupt, while we configure it: |
476 | */ |
477 | error = dprc_set_irq_enable(mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, en: 0); |
478 | if (error < 0) { |
479 | dev_err(&mc_dev->dev, |
480 | "Disabling DPRC IRQ failed: dprc_set_irq_enable() failed: %d\n" , |
481 | error); |
482 | return error; |
483 | } |
484 | |
485 | /* |
486 | * Disable all interrupt causes for the interrupt: |
487 | */ |
488 | error = dprc_set_irq_mask(mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, mask: 0x0); |
489 | if (error < 0) { |
490 | dev_err(&mc_dev->dev, |
491 | "Disabling DPRC IRQ failed: dprc_set_irq_mask() failed: %d\n" , |
492 | error); |
493 | return error; |
494 | } |
495 | |
496 | /* |
497 | * Clear any leftover interrupts: |
498 | */ |
499 | error = dprc_clear_irq_status(mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, status: ~0x0U); |
500 | if (error < 0) { |
501 | dev_err(&mc_dev->dev, |
502 | "Disabling DPRC IRQ failed: dprc_clear_irq_status() failed: %d\n" , |
503 | error); |
504 | return error; |
505 | } |
506 | |
507 | mc_bus->irq_enabled = 0; |
508 | |
509 | return 0; |
510 | } |
511 | |
512 | int get_dprc_irq_state(struct fsl_mc_device *mc_dev) |
513 | { |
514 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
515 | |
516 | return mc_bus->irq_enabled; |
517 | } |
518 | |
519 | static int register_dprc_irq_handler(struct fsl_mc_device *mc_dev) |
520 | { |
521 | int error; |
522 | struct fsl_mc_device_irq *irq = mc_dev->irqs[0]; |
523 | |
524 | /* |
525 | * NOTE: devm_request_threaded_irq() invokes the device-specific |
526 | * function that programs the MSI physically in the device |
527 | */ |
528 | error = devm_request_threaded_irq(dev: &mc_dev->dev, |
529 | irq: irq->virq, |
530 | handler: dprc_irq0_handler, |
531 | thread_fn: dprc_irq0_handler_thread, |
532 | IRQF_NO_SUSPEND | IRQF_ONESHOT, |
533 | devname: dev_name(dev: &mc_dev->dev), |
534 | dev_id: &mc_dev->dev); |
535 | if (error < 0) { |
536 | dev_err(&mc_dev->dev, |
537 | "devm_request_threaded_irq() failed: %d\n" , |
538 | error); |
539 | return error; |
540 | } |
541 | |
542 | return 0; |
543 | } |
544 | |
545 | int enable_dprc_irq(struct fsl_mc_device *mc_dev) |
546 | { |
547 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
548 | int error; |
549 | |
550 | /* |
551 | * Enable all interrupt causes for the interrupt: |
552 | */ |
553 | error = dprc_set_irq_mask(mc_io: mc_dev->mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, |
554 | mask: ~0x0u); |
555 | if (error < 0) { |
556 | dev_err(&mc_dev->dev, |
557 | "Enabling DPRC IRQ failed: dprc_set_irq_mask() failed: %d\n" , |
558 | error); |
559 | |
560 | return error; |
561 | } |
562 | |
563 | /* |
564 | * Enable generation of the interrupt: |
565 | */ |
566 | error = dprc_set_irq_enable(mc_io: mc_dev->mc_io, cmd_flags: 0, token: mc_dev->mc_handle, irq_index: 0, en: 1); |
567 | if (error < 0) { |
568 | dev_err(&mc_dev->dev, |
569 | "Enabling DPRC IRQ failed: dprc_set_irq_enable() failed: %d\n" , |
570 | error); |
571 | |
572 | return error; |
573 | } |
574 | |
575 | mc_bus->irq_enabled = 1; |
576 | |
577 | return 0; |
578 | } |
579 | |
580 | /* |
581 | * Setup interrupt for a given DPRC device |
582 | */ |
583 | static int dprc_setup_irq(struct fsl_mc_device *mc_dev) |
584 | { |
585 | int error; |
586 | |
587 | error = fsl_mc_allocate_irqs(mc_dev); |
588 | if (error < 0) |
589 | return error; |
590 | |
591 | error = disable_dprc_irq(mc_dev); |
592 | if (error < 0) |
593 | goto error_free_irqs; |
594 | |
595 | error = register_dprc_irq_handler(mc_dev); |
596 | if (error < 0) |
597 | goto error_free_irqs; |
598 | |
599 | error = enable_dprc_irq(mc_dev); |
600 | if (error < 0) |
601 | goto error_free_irqs; |
602 | |
603 | return 0; |
604 | |
605 | error_free_irqs: |
606 | fsl_mc_free_irqs(mc_dev); |
607 | return error; |
608 | } |
609 | |
610 | /** |
611 | * dprc_setup - opens and creates a mc_io for DPRC |
612 | * |
613 | * @mc_dev: Pointer to fsl-mc device representing a DPRC |
614 | * |
615 | * It opens the physical DPRC in the MC. |
616 | * It configures the DPRC portal used to communicate with MC |
617 | */ |
618 | |
619 | int dprc_setup(struct fsl_mc_device *mc_dev) |
620 | { |
621 | struct device *parent_dev = mc_dev->dev.parent; |
622 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
623 | struct irq_domain *mc_msi_domain; |
624 | bool mc_io_created = false; |
625 | bool msi_domain_set = false; |
626 | bool uapi_created = false; |
627 | u16 major_ver, minor_ver; |
628 | size_t region_size; |
629 | int error; |
630 | |
631 | if (!is_fsl_mc_bus_dprc(mc_dev)) |
632 | return -EINVAL; |
633 | |
634 | if (dev_get_msi_domain(dev: &mc_dev->dev)) |
635 | return -EINVAL; |
636 | |
637 | if (!mc_dev->mc_io) { |
638 | /* |
639 | * This is a child DPRC: |
640 | */ |
641 | if (!dev_is_fsl_mc(parent_dev)) |
642 | return -EINVAL; |
643 | |
644 | if (mc_dev->obj_desc.region_count == 0) |
645 | return -EINVAL; |
646 | |
647 | region_size = resource_size(res: mc_dev->regions); |
648 | |
649 | error = fsl_create_mc_io(dev: &mc_dev->dev, |
650 | mc_portal_phys_addr: mc_dev->regions[0].start, |
651 | mc_portal_size: region_size, |
652 | NULL, |
653 | FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, |
654 | new_mc_io: &mc_dev->mc_io); |
655 | if (error < 0) |
656 | return error; |
657 | |
658 | mc_io_created = true; |
659 | } else { |
660 | error = fsl_mc_uapi_create_device_file(mc_bus); |
661 | if (error < 0) |
662 | return -EPROBE_DEFER; |
663 | uapi_created = true; |
664 | } |
665 | |
666 | mc_msi_domain = fsl_mc_find_msi_domain(dev: &mc_dev->dev); |
667 | if (!mc_msi_domain) { |
668 | dev_warn(&mc_dev->dev, |
669 | "WARNING: MC bus without interrupt support\n" ); |
670 | } else { |
671 | dev_set_msi_domain(dev: &mc_dev->dev, d: mc_msi_domain); |
672 | msi_domain_set = true; |
673 | } |
674 | |
675 | error = dprc_open(mc_io: mc_dev->mc_io, cmd_flags: 0, container_id: mc_dev->obj_desc.id, |
676 | token: &mc_dev->mc_handle); |
677 | if (error < 0) { |
678 | dev_err(&mc_dev->dev, "dprc_open() failed: %d\n" , error); |
679 | goto error_cleanup_msi_domain; |
680 | } |
681 | |
682 | error = dprc_get_attributes(mc_io: mc_dev->mc_io, cmd_flags: 0, token: mc_dev->mc_handle, |
683 | attributes: &mc_bus->dprc_attr); |
684 | if (error < 0) { |
685 | dev_err(&mc_dev->dev, "dprc_get_attributes() failed: %d\n" , |
686 | error); |
687 | goto error_cleanup_open; |
688 | } |
689 | |
690 | error = dprc_get_api_version(mc_io: mc_dev->mc_io, cmd_flags: 0, |
691 | major_ver: &major_ver, |
692 | minor_ver: &minor_ver); |
693 | if (error < 0) { |
694 | dev_err(&mc_dev->dev, "dprc_get_api_version() failed: %d\n" , |
695 | error); |
696 | goto error_cleanup_open; |
697 | } |
698 | |
699 | if (major_ver < DPRC_MIN_VER_MAJOR) { |
700 | dev_err(&mc_dev->dev, |
701 | "ERROR: DPRC version %d.%d not supported\n" , |
702 | major_ver, minor_ver); |
703 | error = -ENOTSUPP; |
704 | goto error_cleanup_open; |
705 | } |
706 | |
707 | return 0; |
708 | |
709 | error_cleanup_open: |
710 | (void)dprc_close(mc_io: mc_dev->mc_io, cmd_flags: 0, token: mc_dev->mc_handle); |
711 | |
712 | error_cleanup_msi_domain: |
713 | if (msi_domain_set) |
714 | dev_set_msi_domain(dev: &mc_dev->dev, NULL); |
715 | |
716 | if (mc_io_created) { |
717 | fsl_destroy_mc_io(mc_io: mc_dev->mc_io); |
718 | mc_dev->mc_io = NULL; |
719 | } |
720 | |
721 | if (uapi_created) |
722 | fsl_mc_uapi_remove_device_file(mc_bus); |
723 | |
724 | return error; |
725 | } |
726 | EXPORT_SYMBOL_GPL(dprc_setup); |
727 | |
728 | /** |
729 | * dprc_probe - callback invoked when a DPRC is being bound to this driver |
730 | * |
731 | * @mc_dev: Pointer to fsl-mc device representing a DPRC |
732 | * |
733 | * It opens the physical DPRC in the MC. |
734 | * It scans the DPRC to discover the MC objects contained in it. |
735 | * It creates the interrupt pool for the MC bus associated with the DPRC. |
736 | * It configures the interrupts for the DPRC device itself. |
737 | */ |
738 | static int dprc_probe(struct fsl_mc_device *mc_dev) |
739 | { |
740 | int error; |
741 | |
742 | error = dprc_setup(mc_dev); |
743 | if (error < 0) |
744 | return error; |
745 | |
746 | /* |
747 | * Discover MC objects in DPRC object: |
748 | */ |
749 | error = dprc_scan_container(mc_dev, true); |
750 | if (error < 0) |
751 | goto dprc_cleanup; |
752 | |
753 | /* |
754 | * Configure interrupt for the DPRC object associated with this MC bus: |
755 | */ |
756 | error = dprc_setup_irq(mc_dev); |
757 | if (error < 0) |
758 | goto scan_cleanup; |
759 | |
760 | dev_info(&mc_dev->dev, "DPRC device bound to driver" ); |
761 | return 0; |
762 | |
763 | scan_cleanup: |
764 | device_for_each_child(dev: &mc_dev->dev, NULL, fn: __fsl_mc_device_remove); |
765 | dprc_cleanup: |
766 | dprc_cleanup(mc_dev); |
767 | return error; |
768 | } |
769 | |
770 | /* |
771 | * Tear down interrupt for a given DPRC object |
772 | */ |
773 | static void dprc_teardown_irq(struct fsl_mc_device *mc_dev) |
774 | { |
775 | struct fsl_mc_device_irq *irq = mc_dev->irqs[0]; |
776 | |
777 | (void)disable_dprc_irq(mc_dev); |
778 | |
779 | devm_free_irq(dev: &mc_dev->dev, irq: irq->virq, dev_id: &mc_dev->dev); |
780 | |
781 | fsl_mc_free_irqs(mc_dev); |
782 | } |
783 | |
784 | /** |
785 | * dprc_cleanup - function that cleanups a DPRC |
786 | * |
787 | * @mc_dev: Pointer to fsl-mc device representing the DPRC |
788 | * |
789 | * It closes the DPRC device in the MC. |
790 | * It destroys the interrupt pool associated with this MC bus. |
791 | */ |
792 | |
793 | int dprc_cleanup(struct fsl_mc_device *mc_dev) |
794 | { |
795 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
796 | int error; |
797 | |
798 | /* this function should be called only for DPRCs, it |
799 | * is an error to call it for regular objects |
800 | */ |
801 | if (!is_fsl_mc_bus_dprc(mc_dev)) |
802 | return -EINVAL; |
803 | |
804 | if (dev_get_msi_domain(dev: &mc_dev->dev)) { |
805 | fsl_mc_cleanup_irq_pool(mc_bus_dev: mc_dev); |
806 | dev_set_msi_domain(dev: &mc_dev->dev, NULL); |
807 | } |
808 | |
809 | fsl_mc_cleanup_all_resource_pools(mc_bus_dev: mc_dev); |
810 | |
811 | /* if this step fails we cannot go further with cleanup as there is no way of |
812 | * communicating with the firmware |
813 | */ |
814 | if (!mc_dev->mc_io) { |
815 | dev_err(&mc_dev->dev, "mc_io is NULL, tear down cannot be performed in firmware\n" ); |
816 | return -EINVAL; |
817 | } |
818 | |
819 | error = dprc_close(mc_io: mc_dev->mc_io, cmd_flags: 0, token: mc_dev->mc_handle); |
820 | if (error < 0) |
821 | dev_err(&mc_dev->dev, "dprc_close() failed: %d\n" , error); |
822 | |
823 | if (!fsl_mc_is_root_dprc(dev: &mc_dev->dev)) { |
824 | fsl_destroy_mc_io(mc_io: mc_dev->mc_io); |
825 | mc_dev->mc_io = NULL; |
826 | } else { |
827 | fsl_mc_uapi_remove_device_file(mc_bus); |
828 | } |
829 | |
830 | return 0; |
831 | } |
832 | EXPORT_SYMBOL_GPL(dprc_cleanup); |
833 | |
834 | /** |
835 | * dprc_remove - callback invoked when a DPRC is being unbound from this driver |
836 | * |
837 | * @mc_dev: Pointer to fsl-mc device representing the DPRC |
838 | * |
839 | * It removes the DPRC's child objects from Linux (not from the MC) and |
840 | * closes the DPRC device in the MC. |
841 | * It tears down the interrupts that were configured for the DPRC device. |
842 | * It destroys the interrupt pool associated with this MC bus. |
843 | */ |
844 | static void dprc_remove(struct fsl_mc_device *mc_dev) |
845 | { |
846 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
847 | |
848 | if (!mc_bus->irq_resources) { |
849 | dev_err(&mc_dev->dev, "No irq resources, so unbinding the device failed\n" ); |
850 | return; |
851 | } |
852 | |
853 | if (dev_get_msi_domain(dev: &mc_dev->dev)) |
854 | dprc_teardown_irq(mc_dev); |
855 | |
856 | device_for_each_child(dev: &mc_dev->dev, NULL, fn: __fsl_mc_device_remove); |
857 | |
858 | dprc_cleanup(mc_dev); |
859 | |
860 | dev_info(&mc_dev->dev, "DPRC device unbound from driver" ); |
861 | } |
862 | |
863 | static const struct fsl_mc_device_id match_id_table[] = { |
864 | { |
865 | .vendor = FSL_MC_VENDOR_FREESCALE, |
866 | .obj_type = "dprc" }, |
867 | {.vendor = 0x0}, |
868 | }; |
869 | |
870 | static struct fsl_mc_driver dprc_driver = { |
871 | .driver = { |
872 | .name = FSL_MC_DPRC_DRIVER_NAME, |
873 | .owner = THIS_MODULE, |
874 | .pm = NULL, |
875 | }, |
876 | .match_id_table = match_id_table, |
877 | .probe = dprc_probe, |
878 | .remove = dprc_remove, |
879 | }; |
880 | |
881 | int __init dprc_driver_init(void) |
882 | { |
883 | return fsl_mc_driver_register(&dprc_driver); |
884 | } |
885 | |
886 | void dprc_driver_exit(void) |
887 | { |
888 | fsl_mc_driver_unregister(driver: &dprc_driver); |
889 | } |
890 | |