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