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 = |
2cdb82c7 | 132 | obj_desc->state & DPRC_OBJ_STATE_PLUGGED; |
1663e809 GR |
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 | ||
ae014c23 HG |
315 | /* |
316 | * add a quirk for all versions of dpsec < 4.0...none | |
317 | * are coherent regardless of what the MC reports. | |
318 | */ | |
319 | if ((strcmp(obj_desc->type, "dpseci") == 0) && | |
320 | (obj_desc->ver_major < 4)) | |
321 | obj_desc->flags |= | |
322 | DPRC_OBJ_FLAG_NO_MEM_SHAREABILITY; | |
323 | ||
1e4aa42a | 324 | irq_count += obj_desc->irq_count; |
f2f2726b GR |
325 | dev_dbg(&mc_bus_dev->dev, |
326 | "Discovered object: type %s, id %d\n", | |
327 | obj_desc->type, obj_desc->id); | |
328 | } | |
329 | ||
330 | if (dprc_get_obj_failures != 0) { | |
331 | dev_err(&mc_bus_dev->dev, | |
332 | "%d out of %d devices could not be retrieved\n", | |
333 | dprc_get_obj_failures, num_child_objects); | |
334 | } | |
335 | } | |
336 | ||
1e4aa42a | 337 | *total_irq_count = irq_count; |
f2f2726b GR |
338 | dprc_remove_devices(mc_bus_dev, child_obj_desc_array, |
339 | num_child_objects); | |
340 | ||
341 | dprc_add_new_devices(mc_bus_dev, child_obj_desc_array, | |
342 | num_child_objects); | |
343 | ||
344 | if (child_obj_desc_array) | |
345 | devm_kfree(&mc_bus_dev->dev, child_obj_desc_array); | |
346 | ||
347 | return 0; | |
348 | } | |
349 | EXPORT_SYMBOL_GPL(dprc_scan_objects); | |
350 | ||
351 | /** | |
352 | * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state | |
353 | * | |
354 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
355 | * | |
356 | * Scans the physical DPRC and synchronizes the state of the Linux | |
357 | * bus driver with the actual state of the MC by adding and removing | |
358 | * devices as appropriate. | |
359 | */ | |
360 | int dprc_scan_container(struct fsl_mc_device *mc_bus_dev) | |
361 | { | |
362 | int error; | |
1e4aa42a | 363 | unsigned int irq_count; |
f2f2726b GR |
364 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); |
365 | ||
197f4d6a GR |
366 | dprc_init_all_resource_pools(mc_bus_dev); |
367 | ||
f2f2726b GR |
368 | /* |
369 | * Discover objects in the DPRC: | |
370 | */ | |
371 | mutex_lock(&mc_bus->scan_mutex); | |
1e4aa42a | 372 | error = dprc_scan_objects(mc_bus_dev, &irq_count); |
f2f2726b | 373 | mutex_unlock(&mc_bus->scan_mutex); |
197f4d6a GR |
374 | if (error < 0) |
375 | goto error; | |
376 | ||
1e4aa42a GR |
377 | if (dev_get_msi_domain(&mc_bus_dev->dev) && !mc_bus->irq_resources) { |
378 | if (irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS) { | |
379 | dev_warn(&mc_bus_dev->dev, | |
380 | "IRQs needed (%u) exceed IRQs preallocated (%u)\n", | |
381 | irq_count, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); | |
382 | } | |
383 | ||
384 | error = fsl_mc_populate_irq_pool( | |
385 | mc_bus, | |
386 | FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); | |
387 | if (error < 0) | |
388 | goto error; | |
389 | } | |
390 | ||
197f4d6a GR |
391 | return 0; |
392 | error: | |
393 | dprc_cleanup_all_resource_pools(mc_bus_dev); | |
f2f2726b GR |
394 | return error; |
395 | } | |
396 | EXPORT_SYMBOL_GPL(dprc_scan_container); | |
397 | ||
f52dee5c GR |
398 | /** |
399 | * dprc_irq0_handler - Regular ISR for DPRC interrupt 0 | |
400 | * | |
401 | * @irq: IRQ number of the interrupt being handled | |
402 | * @arg: Pointer to device structure | |
403 | */ | |
404 | static irqreturn_t dprc_irq0_handler(int irq_num, void *arg) | |
405 | { | |
406 | return IRQ_WAKE_THREAD; | |
407 | } | |
408 | ||
409 | /** | |
410 | * dprc_irq0_handler_thread - Handler thread function for DPRC interrupt 0 | |
411 | * | |
412 | * @irq: IRQ number of the interrupt being handled | |
413 | * @arg: Pointer to device structure | |
414 | */ | |
415 | static irqreturn_t dprc_irq0_handler_thread(int irq_num, void *arg) | |
416 | { | |
417 | int error; | |
418 | u32 status; | |
36b64670 | 419 | struct device *dev = arg; |
f52dee5c GR |
420 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); |
421 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); | |
422 | struct fsl_mc_io *mc_io = mc_dev->mc_io; | |
423 | struct msi_desc *msi_desc = mc_dev->irqs[0]->msi_desc; | |
424 | ||
425 | dev_dbg(dev, "DPRC IRQ %d triggered on CPU %u\n", | |
426 | irq_num, smp_processor_id()); | |
427 | ||
428 | if (WARN_ON(!(mc_dev->flags & FSL_MC_IS_DPRC))) | |
429 | return IRQ_HANDLED; | |
430 | ||
431 | mutex_lock(&mc_bus->scan_mutex); | |
432 | if (WARN_ON(!msi_desc || msi_desc->irq != (u32)irq_num)) | |
433 | goto out; | |
434 | ||
ac061998 | 435 | status = 0; |
f52dee5c GR |
436 | error = dprc_get_irq_status(mc_io, 0, mc_dev->mc_handle, 0, |
437 | &status); | |
438 | if (error < 0) { | |
439 | dev_err(dev, | |
440 | "dprc_get_irq_status() failed: %d\n", error); | |
441 | goto out; | |
442 | } | |
443 | ||
444 | error = dprc_clear_irq_status(mc_io, 0, mc_dev->mc_handle, 0, | |
445 | status); | |
446 | if (error < 0) { | |
447 | dev_err(dev, | |
448 | "dprc_clear_irq_status() failed: %d\n", error); | |
449 | goto out; | |
450 | } | |
451 | ||
452 | if (status & (DPRC_IRQ_EVENT_OBJ_ADDED | | |
453 | DPRC_IRQ_EVENT_OBJ_REMOVED | | |
454 | DPRC_IRQ_EVENT_CONTAINER_DESTROYED | | |
455 | DPRC_IRQ_EVENT_OBJ_DESTROYED | | |
456 | DPRC_IRQ_EVENT_OBJ_CREATED)) { | |
457 | unsigned int irq_count; | |
458 | ||
459 | error = dprc_scan_objects(mc_dev, &irq_count); | |
460 | if (error < 0) { | |
461 | /* | |
462 | * If the error is -ENXIO, we ignore it, as it indicates | |
463 | * that the object scan was aborted, as we detected that | |
464 | * an object was removed from the DPRC in the MC, while | |
465 | * we were scanning the DPRC. | |
466 | */ | |
467 | if (error != -ENXIO) { | |
468 | dev_err(dev, "dprc_scan_objects() failed: %d\n", | |
469 | error); | |
470 | } | |
471 | ||
472 | goto out; | |
473 | } | |
474 | ||
475 | if (irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS) { | |
476 | dev_warn(dev, | |
477 | "IRQs needed (%u) exceed IRQs preallocated (%u)\n", | |
478 | irq_count, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); | |
479 | } | |
480 | } | |
481 | ||
482 | out: | |
483 | mutex_unlock(&mc_bus->scan_mutex); | |
484 | return IRQ_HANDLED; | |
485 | } | |
486 | ||
487 | /* | |
488 | * Disable and clear interrupt for a given DPRC object | |
489 | */ | |
490 | static int disable_dprc_irq(struct fsl_mc_device *mc_dev) | |
491 | { | |
492 | int error; | |
493 | struct fsl_mc_io *mc_io = mc_dev->mc_io; | |
494 | ||
495 | WARN_ON(mc_dev->obj_desc.irq_count != 1); | |
496 | ||
497 | /* | |
498 | * Disable generation of interrupt, while we configure it: | |
499 | */ | |
500 | error = dprc_set_irq_enable(mc_io, 0, mc_dev->mc_handle, 0, 0); | |
501 | if (error < 0) { | |
502 | dev_err(&mc_dev->dev, | |
503 | "Disabling DPRC IRQ failed: dprc_set_irq_enable() failed: %d\n", | |
504 | error); | |
505 | return error; | |
506 | } | |
507 | ||
508 | /* | |
509 | * Disable all interrupt causes for the interrupt: | |
510 | */ | |
511 | error = dprc_set_irq_mask(mc_io, 0, mc_dev->mc_handle, 0, 0x0); | |
512 | if (error < 0) { | |
513 | dev_err(&mc_dev->dev, | |
514 | "Disabling DPRC IRQ failed: dprc_set_irq_mask() failed: %d\n", | |
515 | error); | |
516 | return error; | |
517 | } | |
518 | ||
519 | /* | |
520 | * Clear any leftover interrupts: | |
521 | */ | |
522 | error = dprc_clear_irq_status(mc_io, 0, mc_dev->mc_handle, 0, ~0x0U); | |
523 | if (error < 0) { | |
524 | dev_err(&mc_dev->dev, | |
525 | "Disabling DPRC IRQ failed: dprc_clear_irq_status() failed: %d\n", | |
526 | error); | |
527 | return error; | |
528 | } | |
529 | ||
530 | return 0; | |
531 | } | |
532 | ||
533 | static int register_dprc_irq_handler(struct fsl_mc_device *mc_dev) | |
534 | { | |
535 | int error; | |
536 | struct fsl_mc_device_irq *irq = mc_dev->irqs[0]; | |
537 | ||
538 | WARN_ON(mc_dev->obj_desc.irq_count != 1); | |
539 | ||
540 | /* | |
541 | * NOTE: devm_request_threaded_irq() invokes the device-specific | |
542 | * function that programs the MSI physically in the device | |
543 | */ | |
544 | error = devm_request_threaded_irq(&mc_dev->dev, | |
545 | irq->msi_desc->irq, | |
546 | dprc_irq0_handler, | |
547 | dprc_irq0_handler_thread, | |
548 | IRQF_NO_SUSPEND | IRQF_ONESHOT, | |
549 | "FSL MC DPRC irq0", | |
550 | &mc_dev->dev); | |
551 | if (error < 0) { | |
552 | dev_err(&mc_dev->dev, | |
553 | "devm_request_threaded_irq() failed: %d\n", | |
554 | error); | |
555 | return error; | |
556 | } | |
557 | ||
558 | return 0; | |
559 | } | |
560 | ||
561 | static int enable_dprc_irq(struct fsl_mc_device *mc_dev) | |
562 | { | |
563 | int error; | |
564 | ||
565 | /* | |
566 | * Enable all interrupt causes for the interrupt: | |
567 | */ | |
568 | error = dprc_set_irq_mask(mc_dev->mc_io, 0, mc_dev->mc_handle, 0, | |
569 | ~0x0u); | |
570 | if (error < 0) { | |
571 | dev_err(&mc_dev->dev, | |
572 | "Enabling DPRC IRQ failed: dprc_set_irq_mask() failed: %d\n", | |
573 | error); | |
574 | ||
575 | return error; | |
576 | } | |
577 | ||
578 | /* | |
579 | * Enable generation of the interrupt: | |
580 | */ | |
581 | error = dprc_set_irq_enable(mc_dev->mc_io, 0, mc_dev->mc_handle, 0, 1); | |
582 | if (error < 0) { | |
583 | dev_err(&mc_dev->dev, | |
584 | "Enabling DPRC IRQ failed: dprc_set_irq_enable() failed: %d\n", | |
585 | error); | |
586 | ||
587 | return error; | |
588 | } | |
589 | ||
590 | return 0; | |
591 | } | |
592 | ||
593 | /* | |
594 | * Setup interrupt for a given DPRC device | |
595 | */ | |
596 | static int dprc_setup_irq(struct fsl_mc_device *mc_dev) | |
597 | { | |
598 | int error; | |
599 | ||
600 | error = fsl_mc_allocate_irqs(mc_dev); | |
601 | if (error < 0) | |
602 | return error; | |
603 | ||
604 | error = disable_dprc_irq(mc_dev); | |
605 | if (error < 0) | |
606 | goto error_free_irqs; | |
607 | ||
608 | error = register_dprc_irq_handler(mc_dev); | |
609 | if (error < 0) | |
610 | goto error_free_irqs; | |
611 | ||
612 | error = enable_dprc_irq(mc_dev); | |
613 | if (error < 0) | |
614 | goto error_free_irqs; | |
615 | ||
616 | return 0; | |
617 | ||
618 | error_free_irqs: | |
619 | fsl_mc_free_irqs(mc_dev); | |
620 | return error; | |
621 | } | |
622 | ||
f2f2726b GR |
623 | /** |
624 | * dprc_probe - callback invoked when a DPRC is being bound to this driver | |
625 | * | |
626 | * @mc_dev: Pointer to fsl-mc device representing a DPRC | |
627 | * | |
628 | * It opens the physical DPRC in the MC. | |
629 | * It scans the DPRC to discover the MC objects contained in it. | |
197f4d6a GR |
630 | * It creates the interrupt pool for the MC bus associated with the DPRC. |
631 | * It configures the interrupts for the DPRC device itself. | |
f2f2726b GR |
632 | */ |
633 | static int dprc_probe(struct fsl_mc_device *mc_dev) | |
634 | { | |
635 | int error; | |
636 | size_t region_size; | |
232ae8f2 | 637 | struct device *parent_dev = mc_dev->dev.parent; |
f2f2726b | 638 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
8804f9fc | 639 | bool mc_io_created = false; |
232ae8f2 | 640 | bool msi_domain_set = false; |
f2f2726b GR |
641 | |
642 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
643 | return -EINVAL; | |
644 | ||
232ae8f2 GR |
645 | if (WARN_ON(dev_get_msi_domain(&mc_dev->dev))) |
646 | return -EINVAL; | |
647 | ||
f2f2726b GR |
648 | if (!mc_dev->mc_io) { |
649 | /* | |
650 | * This is a child DPRC: | |
651 | */ | |
8804f9fc GR |
652 | if (WARN_ON(parent_dev->bus != &fsl_mc_bus_type)) |
653 | return -EINVAL; | |
654 | ||
f2f2726b GR |
655 | if (WARN_ON(mc_dev->obj_desc.region_count == 0)) |
656 | return -EINVAL; | |
657 | ||
658 | region_size = mc_dev->regions[0].end - | |
659 | mc_dev->regions[0].start + 1; | |
660 | ||
661 | error = fsl_create_mc_io(&mc_dev->dev, | |
662 | mc_dev->regions[0].start, | |
663 | region_size, | |
1129cde5 GR |
664 | NULL, |
665 | FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, | |
666 | &mc_dev->mc_io); | |
f2f2726b GR |
667 | if (error < 0) |
668 | return error; | |
8804f9fc GR |
669 | |
670 | mc_io_created = true; | |
671 | ||
232ae8f2 GR |
672 | /* |
673 | * Inherit parent MSI domain: | |
674 | */ | |
675 | dev_set_msi_domain(&mc_dev->dev, | |
676 | dev_get_msi_domain(parent_dev)); | |
677 | msi_domain_set = true; | |
678 | } else { | |
679 | /* | |
680 | * This is a root DPRC | |
681 | */ | |
682 | struct irq_domain *mc_msi_domain; | |
683 | ||
684 | if (WARN_ON(parent_dev->bus == &fsl_mc_bus_type)) | |
685 | return -EINVAL; | |
686 | ||
687 | error = fsl_mc_find_msi_domain(parent_dev, | |
688 | &mc_msi_domain); | |
689 | if (error < 0) { | |
690 | dev_warn(&mc_dev->dev, | |
691 | "WARNING: MC bus without interrupt support\n"); | |
692 | } else { | |
693 | dev_set_msi_domain(&mc_dev->dev, mc_msi_domain); | |
694 | msi_domain_set = true; | |
695 | } | |
f2f2726b GR |
696 | } |
697 | ||
1ee695fa | 698 | error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, |
f2f2726b GR |
699 | &mc_dev->mc_handle); |
700 | if (error < 0) { | |
701 | dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", error); | |
8804f9fc | 702 | goto error_cleanup_msi_domain; |
f2f2726b GR |
703 | } |
704 | ||
1716cb4c IK |
705 | error = dprc_get_attributes(mc_dev->mc_io, 0, mc_dev->mc_handle, |
706 | &mc_bus->dprc_attr); | |
707 | if (error < 0) { | |
708 | dev_err(&mc_dev->dev, "dprc_get_attributes() failed: %d\n", | |
709 | error); | |
710 | goto error_cleanup_open; | |
711 | } | |
712 | ||
713 | if (mc_bus->dprc_attr.version.major < DPRC_MIN_VER_MAJOR || | |
714 | (mc_bus->dprc_attr.version.major == DPRC_MIN_VER_MAJOR && | |
715 | mc_bus->dprc_attr.version.minor < DPRC_MIN_VER_MINOR)) { | |
716 | dev_err(&mc_dev->dev, | |
717 | "ERROR: DPRC version %d.%d not supported\n", | |
718 | mc_bus->dprc_attr.version.major, | |
719 | mc_bus->dprc_attr.version.minor); | |
720 | error = -ENOTSUPP; | |
721 | goto error_cleanup_open; | |
722 | } | |
723 | ||
f2f2726b GR |
724 | mutex_init(&mc_bus->scan_mutex); |
725 | ||
726 | /* | |
727 | * Discover MC objects in DPRC object: | |
728 | */ | |
729 | error = dprc_scan_container(mc_dev); | |
730 | if (error < 0) | |
731 | goto error_cleanup_open; | |
732 | ||
f52dee5c GR |
733 | /* |
734 | * Configure interrupt for the DPRC object associated with this MC bus: | |
735 | */ | |
736 | error = dprc_setup_irq(mc_dev); | |
737 | if (error < 0) | |
738 | goto error_cleanup_open; | |
739 | ||
f2f2726b GR |
740 | dev_info(&mc_dev->dev, "DPRC device bound to driver"); |
741 | return 0; | |
742 | ||
743 | error_cleanup_open: | |
1ee695fa | 744 | (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); |
f2f2726b | 745 | |
8804f9fc | 746 | error_cleanup_msi_domain: |
232ae8f2 GR |
747 | if (msi_domain_set) |
748 | dev_set_msi_domain(&mc_dev->dev, NULL); | |
749 | ||
8804f9fc GR |
750 | if (mc_io_created) { |
751 | fsl_destroy_mc_io(mc_dev->mc_io); | |
752 | mc_dev->mc_io = NULL; | |
753 | } | |
754 | ||
f2f2726b GR |
755 | return error; |
756 | } | |
757 | ||
f52dee5c GR |
758 | /* |
759 | * Tear down interrupt for a given DPRC object | |
760 | */ | |
761 | static void dprc_teardown_irq(struct fsl_mc_device *mc_dev) | |
762 | { | |
763 | (void)disable_dprc_irq(mc_dev); | |
764 | fsl_mc_free_irqs(mc_dev); | |
765 | } | |
766 | ||
f2f2726b GR |
767 | /** |
768 | * dprc_remove - callback invoked when a DPRC is being unbound from this driver | |
769 | * | |
770 | * @mc_dev: Pointer to fsl-mc device representing the DPRC | |
771 | * | |
772 | * It removes the DPRC's child objects from Linux (not from the MC) and | |
773 | * closes the DPRC device in the MC. | |
197f4d6a GR |
774 | * It tears down the interrupts that were configured for the DPRC device. |
775 | * It destroys the interrupt pool associated with this MC bus. | |
f2f2726b GR |
776 | */ |
777 | static int dprc_remove(struct fsl_mc_device *mc_dev) | |
778 | { | |
779 | int error; | |
232ae8f2 | 780 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); |
f2f2726b GR |
781 | |
782 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
783 | return -EINVAL; | |
784 | if (WARN_ON(!mc_dev->mc_io)) | |
785 | return -EINVAL; | |
786 | ||
f52dee5c GR |
787 | if (WARN_ON(!mc_bus->irq_resources)) |
788 | return -EINVAL; | |
789 | ||
790 | if (dev_get_msi_domain(&mc_dev->dev)) | |
791 | dprc_teardown_irq(mc_dev); | |
792 | ||
f2f2726b | 793 | device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); |
197f4d6a | 794 | dprc_cleanup_all_resource_pools(mc_dev); |
1ee695fa | 795 | error = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); |
f2f2726b GR |
796 | if (error < 0) |
797 | dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error); | |
798 | ||
232ae8f2 GR |
799 | if (dev_get_msi_domain(&mc_dev->dev)) { |
800 | fsl_mc_cleanup_irq_pool(mc_bus); | |
801 | dev_set_msi_domain(&mc_dev->dev, NULL); | |
802 | } | |
803 | ||
f9362714 BB |
804 | if (!fsl_mc_is_root_dprc(&mc_dev->dev)) { |
805 | fsl_destroy_mc_io(mc_dev->mc_io); | |
806 | mc_dev->mc_io = NULL; | |
807 | } | |
808 | ||
f2f2726b GR |
809 | dev_info(&mc_dev->dev, "DPRC device unbound from driver"); |
810 | return 0; | |
811 | } | |
812 | ||
57538afb | 813 | static const struct fsl_mc_device_id match_id_table[] = { |
f2f2726b GR |
814 | { |
815 | .vendor = FSL_MC_VENDOR_FREESCALE, | |
9787d4e0 | 816 | .obj_type = "dprc"}, |
f2f2726b GR |
817 | {.vendor = 0x0}, |
818 | }; | |
819 | ||
820 | static struct fsl_mc_driver dprc_driver = { | |
821 | .driver = { | |
822 | .name = FSL_MC_DPRC_DRIVER_NAME, | |
823 | .owner = THIS_MODULE, | |
824 | .pm = NULL, | |
825 | }, | |
826 | .match_id_table = match_id_table, | |
827 | .probe = dprc_probe, | |
828 | .remove = dprc_remove, | |
829 | }; | |
830 | ||
831 | int __init dprc_driver_init(void) | |
832 | { | |
833 | return fsl_mc_driver_register(&dprc_driver); | |
834 | } | |
835 | ||
3c7b67f9 | 836 | void dprc_driver_exit(void) |
f2f2726b GR |
837 | { |
838 | fsl_mc_driver_unregister(&dprc_driver); | |
839 | } |