]>
Commit | Line | Data |
---|---|---|
bbf9d17d GR |
1 | /* |
2 | * Freescale Management Complex (MC) bus driver | |
3 | * | |
6466dac7 | 4 | * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. |
bbf9d17d GR |
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 | ||
a6737837 SY |
12 | #define pr_fmt(fmt) "fsl-mc: " fmt |
13 | ||
bbf9d17d GR |
14 | #include <linux/module.h> |
15 | #include <linux/of_device.h> | |
16 | #include <linux/of_address.h> | |
17 | #include <linux/ioport.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/limits.h> | |
660a24bf GR |
20 | #include <linux/bitops.h> |
21 | #include <linux/msi.h> | |
5143ecf6 | 22 | #include <linux/dma-mapping.h> |
d4e75132 | 23 | #include "../include/mc-bus.h" |
bbf9d17d GR |
24 | #include "../include/dpmng.h" |
25 | #include "../include/mc-sys.h" | |
d4e75132 | 26 | |
243444fb | 27 | #include "fsl-mc-private.h" |
bbf9d17d GR |
28 | #include "dprc-cmd.h" |
29 | ||
30 | static struct kmem_cache *mc_dev_cache; | |
31 | ||
e730d86d SY |
32 | /** |
33 | * Default DMA mask for devices on a fsl-mc bus | |
34 | */ | |
35 | #define FSL_MC_DEFAULT_DMA_MASK (~0ULL) | |
36 | ||
37 | /** | |
38 | * struct fsl_mc - Private data of a "fsl,qoriq-mc" platform device | |
58caaac1 | 39 | * @root_mc_bus_dev: fsl-mc device representing the root DPRC |
e730d86d SY |
40 | * @num_translation_ranges: number of entries in addr_translation_ranges |
41 | * @translation_ranges: array of bus to system address translation ranges | |
42 | */ | |
43 | struct fsl_mc { | |
44 | struct fsl_mc_device *root_mc_bus_dev; | |
45 | u8 num_translation_ranges; | |
46 | struct fsl_mc_addr_translation_range *translation_ranges; | |
47 | }; | |
48 | ||
49 | /** | |
50 | * struct fsl_mc_addr_translation_range - bus to system address translation | |
51 | * range | |
52 | * @mc_region_type: Type of MC region for the range being translated | |
53 | * @start_mc_offset: Start MC offset of the range being translated | |
54 | * @end_mc_offset: MC offset of the first byte after the range (last MC | |
55 | * offset of the range is end_mc_offset - 1) | |
56 | * @start_phys_addr: system physical address corresponding to start_mc_addr | |
57 | */ | |
58 | struct fsl_mc_addr_translation_range { | |
59 | enum dprc_region_type mc_region_type; | |
60 | u64 start_mc_offset; | |
61 | u64 end_mc_offset; | |
62 | phys_addr_t start_phys_addr; | |
63 | }; | |
64 | ||
bbf9d17d GR |
65 | /** |
66 | * fsl_mc_bus_match - device to driver matching callback | |
58caaac1 SY |
67 | * @dev: the fsl-mc device to match against |
68 | * @drv: the device driver to search for matching fsl-mc object type | |
bbf9d17d GR |
69 | * structures |
70 | * | |
71 | * Returns 1 on success, 0 otherwise. | |
72 | */ | |
73 | static int fsl_mc_bus_match(struct device *dev, struct device_driver *drv) | |
74 | { | |
57538afb | 75 | const struct fsl_mc_device_id *id; |
bbf9d17d GR |
76 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); |
77 | struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(drv); | |
78 | bool found = false; | |
79 | ||
bbf9d17d GR |
80 | if (!mc_drv->match_id_table) |
81 | goto out; | |
82 | ||
83 | /* | |
84 | * If the object is not 'plugged' don't match. | |
85 | * Only exception is the root DPRC, which is a special case. | |
86 | */ | |
87 | if ((mc_dev->obj_desc.state & DPRC_OBJ_STATE_PLUGGED) == 0 && | |
b55f00c6 | 88 | !fsl_mc_is_root_dprc(&mc_dev->dev)) |
bbf9d17d GR |
89 | goto out; |
90 | ||
91 | /* | |
92 | * Traverse the match_id table of the given driver, trying to find | |
58caaac1 | 93 | * a matching for the given device. |
bbf9d17d GR |
94 | */ |
95 | for (id = mc_drv->match_id_table; id->vendor != 0x0; id++) { | |
96 | if (id->vendor == mc_dev->obj_desc.vendor && | |
3b0a9b10 | 97 | strcmp(id->obj_type, mc_dev->obj_desc.type) == 0) { |
9787d4e0 | 98 | found = true; |
3b0a9b10 | 99 | |
bbf9d17d GR |
100 | break; |
101 | } | |
102 | } | |
103 | ||
104 | out: | |
105 | dev_dbg(dev, "%smatched\n", found ? "" : "not "); | |
106 | return found; | |
107 | } | |
108 | ||
109 | /** | |
110 | * fsl_mc_bus_uevent - callback invoked when a device is added | |
111 | */ | |
112 | static int fsl_mc_bus_uevent(struct device *dev, struct kobj_uevent_env *env) | |
113 | { | |
d568b767 SY |
114 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); |
115 | ||
116 | if (add_uevent_var(env, "MODALIAS=fsl-mc:v%08Xd%s", | |
117 | mc_dev->obj_desc.vendor, | |
118 | mc_dev->obj_desc.type)) | |
119 | return -ENOMEM; | |
120 | ||
bbf9d17d GR |
121 | return 0; |
122 | } | |
123 | ||
3d579c35 SY |
124 | static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, |
125 | char *buf) | |
126 | { | |
127 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); | |
128 | ||
129 | return sprintf(buf, "fsl-mc:v%08Xd%s\n", mc_dev->obj_desc.vendor, | |
130 | mc_dev->obj_desc.type); | |
131 | } | |
132 | static DEVICE_ATTR_RO(modalias); | |
133 | ||
134 | static struct attribute *fsl_mc_dev_attrs[] = { | |
135 | &dev_attr_modalias.attr, | |
136 | NULL, | |
137 | }; | |
138 | ||
a71a6d96 | 139 | ATTRIBUTE_GROUPS(fsl_mc_dev); |
3d579c35 | 140 | |
bbf9d17d GR |
141 | struct bus_type fsl_mc_bus_type = { |
142 | .name = "fsl-mc", | |
143 | .match = fsl_mc_bus_match, | |
144 | .uevent = fsl_mc_bus_uevent, | |
3d579c35 | 145 | .dev_groups = fsl_mc_dev_groups, |
bbf9d17d GR |
146 | }; |
147 | EXPORT_SYMBOL_GPL(fsl_mc_bus_type); | |
148 | ||
149 | static int fsl_mc_driver_probe(struct device *dev) | |
150 | { | |
151 | struct fsl_mc_driver *mc_drv; | |
152 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); | |
153 | int error; | |
154 | ||
155 | if (WARN_ON(!dev->driver)) | |
156 | return -EINVAL; | |
157 | ||
158 | mc_drv = to_fsl_mc_driver(dev->driver); | |
159 | if (WARN_ON(!mc_drv->probe)) | |
160 | return -EINVAL; | |
161 | ||
162 | error = mc_drv->probe(mc_dev); | |
163 | if (error < 0) { | |
66fcc741 | 164 | dev_err(dev, "%s failed: %d\n", __func__, error); |
bbf9d17d GR |
165 | return error; |
166 | } | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | static int fsl_mc_driver_remove(struct device *dev) | |
172 | { | |
173 | struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); | |
174 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); | |
175 | int error; | |
176 | ||
177 | if (WARN_ON(!dev->driver)) | |
178 | return -EINVAL; | |
179 | ||
180 | error = mc_drv->remove(mc_dev); | |
181 | if (error < 0) { | |
66fcc741 | 182 | dev_err(dev, "%s failed: %d\n", __func__, error); |
bbf9d17d GR |
183 | return error; |
184 | } | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | static void fsl_mc_driver_shutdown(struct device *dev) | |
190 | { | |
191 | struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); | |
192 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); | |
193 | ||
194 | mc_drv->shutdown(mc_dev); | |
195 | } | |
196 | ||
197 | /** | |
198 | * __fsl_mc_driver_register - registers a child device driver with the | |
199 | * MC bus | |
200 | * | |
201 | * This function is implicitly invoked from the registration function of | |
202 | * fsl_mc device drivers, which is generated by the | |
203 | * module_fsl_mc_driver() macro. | |
204 | */ | |
205 | int __fsl_mc_driver_register(struct fsl_mc_driver *mc_driver, | |
206 | struct module *owner) | |
207 | { | |
208 | int error; | |
209 | ||
210 | mc_driver->driver.owner = owner; | |
211 | mc_driver->driver.bus = &fsl_mc_bus_type; | |
212 | ||
213 | if (mc_driver->probe) | |
214 | mc_driver->driver.probe = fsl_mc_driver_probe; | |
215 | ||
216 | if (mc_driver->remove) | |
217 | mc_driver->driver.remove = fsl_mc_driver_remove; | |
218 | ||
219 | if (mc_driver->shutdown) | |
220 | mc_driver->driver.shutdown = fsl_mc_driver_shutdown; | |
221 | ||
222 | error = driver_register(&mc_driver->driver); | |
223 | if (error < 0) { | |
224 | pr_err("driver_register() failed for %s: %d\n", | |
225 | mc_driver->driver.name, error); | |
226 | return error; | |
227 | } | |
228 | ||
bbf9d17d GR |
229 | return 0; |
230 | } | |
231 | EXPORT_SYMBOL_GPL(__fsl_mc_driver_register); | |
232 | ||
233 | /** | |
234 | * fsl_mc_driver_unregister - unregisters a device driver from the | |
235 | * MC bus | |
236 | */ | |
237 | void fsl_mc_driver_unregister(struct fsl_mc_driver *mc_driver) | |
238 | { | |
239 | driver_unregister(&mc_driver->driver); | |
240 | } | |
241 | EXPORT_SYMBOL_GPL(fsl_mc_driver_unregister); | |
242 | ||
e4ea40f9 | 243 | /** |
f86a1808 RO |
244 | * fsl_mc_get_root_dprc - function to traverse to the root dprc |
245 | */ | |
27365d85 SY |
246 | void fsl_mc_get_root_dprc(struct device *dev, |
247 | struct device **root_dprc_dev) | |
e4ea40f9 IK |
248 | { |
249 | if (WARN_ON(!dev)) { | |
250 | *root_dprc_dev = NULL; | |
df5e9b5f | 251 | } else if (WARN_ON(!dev_is_fsl_mc(dev))) { |
e4ea40f9 IK |
252 | *root_dprc_dev = NULL; |
253 | } else { | |
254 | *root_dprc_dev = dev; | |
df5e9b5f | 255 | while (dev_is_fsl_mc((*root_dprc_dev)->parent)) |
e4ea40f9 IK |
256 | *root_dprc_dev = (*root_dprc_dev)->parent; |
257 | } | |
258 | } | |
27365d85 | 259 | EXPORT_SYMBOL_GPL(fsl_mc_get_root_dprc); |
e4ea40f9 | 260 | |
9529d166 IK |
261 | static int get_dprc_attr(struct fsl_mc_io *mc_io, |
262 | int container_id, struct dprc_attributes *attr) | |
bbf9d17d | 263 | { |
a2f9ff6c | 264 | u16 dprc_handle; |
bbf9d17d GR |
265 | int error; |
266 | ||
1ee695fa | 267 | error = dprc_open(mc_io, 0, container_id, &dprc_handle); |
bbf9d17d | 268 | if (error < 0) { |
2e115901 | 269 | dev_err(mc_io->dev, "dprc_open() failed: %d\n", error); |
bbf9d17d GR |
270 | return error; |
271 | } | |
272 | ||
9529d166 IK |
273 | memset(attr, 0, sizeof(struct dprc_attributes)); |
274 | error = dprc_get_attributes(mc_io, 0, dprc_handle, attr); | |
bbf9d17d | 275 | if (error < 0) { |
2e115901 | 276 | dev_err(mc_io->dev, "dprc_get_attributes() failed: %d\n", |
454b0ec8 | 277 | error); |
bbf9d17d GR |
278 | goto common_cleanup; |
279 | } | |
280 | ||
bbf9d17d GR |
281 | error = 0; |
282 | ||
283 | common_cleanup: | |
1ee695fa | 284 | (void)dprc_close(mc_io, 0, dprc_handle); |
bbf9d17d GR |
285 | return error; |
286 | } | |
287 | ||
9529d166 IK |
288 | static int get_dprc_icid(struct fsl_mc_io *mc_io, |
289 | int container_id, u16 *icid) | |
290 | { | |
291 | struct dprc_attributes attr; | |
292 | int error; | |
293 | ||
294 | error = get_dprc_attr(mc_io, container_id, &attr); | |
295 | if (error == 0) | |
296 | *icid = attr.icid; | |
297 | ||
298 | return error; | |
299 | } | |
300 | ||
e4ea40f9 IK |
301 | static int translate_mc_addr(struct fsl_mc_device *mc_dev, |
302 | enum dprc_region_type mc_region_type, | |
1ee695fa | 303 | u64 mc_offset, phys_addr_t *phys_addr) |
bbf9d17d GR |
304 | { |
305 | int i; | |
e4ea40f9 IK |
306 | struct device *root_dprc_dev; |
307 | struct fsl_mc *mc; | |
308 | ||
309 | fsl_mc_get_root_dprc(&mc_dev->dev, &root_dprc_dev); | |
310 | if (WARN_ON(!root_dprc_dev)) | |
311 | return -EINVAL; | |
312 | mc = dev_get_drvdata(root_dprc_dev->parent); | |
bbf9d17d GR |
313 | |
314 | if (mc->num_translation_ranges == 0) { | |
315 | /* | |
316 | * Do identity mapping: | |
317 | */ | |
1ee695fa | 318 | *phys_addr = mc_offset; |
bbf9d17d GR |
319 | return 0; |
320 | } | |
321 | ||
322 | for (i = 0; i < mc->num_translation_ranges; i++) { | |
323 | struct fsl_mc_addr_translation_range *range = | |
324 | &mc->translation_ranges[i]; | |
325 | ||
1ee695fa GR |
326 | if (mc_region_type == range->mc_region_type && |
327 | mc_offset >= range->start_mc_offset && | |
328 | mc_offset < range->end_mc_offset) { | |
bbf9d17d | 329 | *phys_addr = range->start_phys_addr + |
1ee695fa | 330 | (mc_offset - range->start_mc_offset); |
bbf9d17d GR |
331 | return 0; |
332 | } | |
333 | } | |
334 | ||
335 | return -EFAULT; | |
336 | } | |
337 | ||
338 | static int fsl_mc_device_get_mmio_regions(struct fsl_mc_device *mc_dev, | |
339 | struct fsl_mc_device *mc_bus_dev) | |
340 | { | |
341 | int i; | |
342 | int error; | |
343 | struct resource *regions; | |
344 | struct dprc_obj_desc *obj_desc = &mc_dev->obj_desc; | |
345 | struct device *parent_dev = mc_dev->dev.parent; | |
1ee695fa GR |
346 | enum dprc_region_type mc_region_type; |
347 | ||
348 | if (strcmp(obj_desc->type, "dprc") == 0 || | |
349 | strcmp(obj_desc->type, "dpmcp") == 0) { | |
350 | mc_region_type = DPRC_REGION_TYPE_MC_PORTAL; | |
351 | } else if (strcmp(obj_desc->type, "dpio") == 0) { | |
352 | mc_region_type = DPRC_REGION_TYPE_QBMAN_PORTAL; | |
353 | } else { | |
354 | /* | |
355 | * This function should not have been called for this MC object | |
356 | * type, as this object type is not supposed to have MMIO | |
357 | * regions | |
358 | */ | |
359 | WARN_ON(true); | |
360 | return -EINVAL; | |
361 | } | |
bbf9d17d GR |
362 | |
363 | regions = kmalloc_array(obj_desc->region_count, | |
364 | sizeof(regions[0]), GFP_KERNEL); | |
365 | if (!regions) | |
366 | return -ENOMEM; | |
367 | ||
368 | for (i = 0; i < obj_desc->region_count; i++) { | |
369 | struct dprc_region_desc region_desc; | |
370 | ||
371 | error = dprc_get_obj_region(mc_bus_dev->mc_io, | |
1ee695fa | 372 | 0, |
bbf9d17d GR |
373 | mc_bus_dev->mc_handle, |
374 | obj_desc->type, | |
375 | obj_desc->id, i, ®ion_desc); | |
376 | if (error < 0) { | |
377 | dev_err(parent_dev, | |
378 | "dprc_get_obj_region() failed: %d\n", error); | |
379 | goto error_cleanup_regions; | |
380 | } | |
381 | ||
bbf9d17d | 382 | WARN_ON(region_desc.size == 0); |
e4ea40f9 | 383 | error = translate_mc_addr(mc_dev, mc_region_type, |
1ee695fa | 384 | region_desc.base_offset, |
bbf9d17d GR |
385 | ®ions[i].start); |
386 | if (error < 0) { | |
387 | dev_err(parent_dev, | |
1ee695fa GR |
388 | "Invalid MC offset: %#x (for %s.%d\'s region %d)\n", |
389 | region_desc.base_offset, | |
390 | obj_desc->type, obj_desc->id, i); | |
bbf9d17d GR |
391 | goto error_cleanup_regions; |
392 | } | |
393 | ||
394 | regions[i].end = regions[i].start + region_desc.size - 1; | |
395 | regions[i].name = "fsl-mc object MMIO region"; | |
396 | regions[i].flags = IORESOURCE_IO; | |
b3721fc1 IK |
397 | if (region_desc.flags & DPRC_REGION_CACHEABLE) |
398 | regions[i].flags |= IORESOURCE_CACHEABLE; | |
bbf9d17d GR |
399 | } |
400 | ||
401 | mc_dev->regions = regions; | |
402 | return 0; | |
403 | ||
404 | error_cleanup_regions: | |
405 | kfree(regions); | |
406 | return error; | |
407 | } | |
408 | ||
fde867d3 SY |
409 | /** |
410 | * fsl_mc_is_root_dprc - function to check if a given device is a root dprc | |
411 | */ | |
412 | bool fsl_mc_is_root_dprc(struct device *dev) | |
413 | { | |
414 | struct device *root_dprc_dev; | |
415 | ||
416 | fsl_mc_get_root_dprc(dev, &root_dprc_dev); | |
417 | if (!root_dprc_dev) | |
418 | return false; | |
419 | return dev == root_dprc_dev; | |
420 | } | |
421 | ||
bbf9d17d | 422 | /** |
58caaac1 | 423 | * Add a newly discovered fsl-mc device to be visible in Linux |
bbf9d17d GR |
424 | */ |
425 | int fsl_mc_device_add(struct dprc_obj_desc *obj_desc, | |
426 | struct fsl_mc_io *mc_io, | |
427 | struct device *parent_dev, | |
428 | struct fsl_mc_device **new_mc_dev) | |
429 | { | |
430 | int error; | |
431 | struct fsl_mc_device *mc_dev = NULL; | |
432 | struct fsl_mc_bus *mc_bus = NULL; | |
433 | struct fsl_mc_device *parent_mc_dev; | |
434 | ||
df5e9b5f | 435 | if (dev_is_fsl_mc(parent_dev)) |
bbf9d17d GR |
436 | parent_mc_dev = to_fsl_mc_device(parent_dev); |
437 | else | |
438 | parent_mc_dev = NULL; | |
439 | ||
440 | if (strcmp(obj_desc->type, "dprc") == 0) { | |
441 | /* | |
442 | * Allocate an MC bus device object: | |
443 | */ | |
444 | mc_bus = devm_kzalloc(parent_dev, sizeof(*mc_bus), GFP_KERNEL); | |
445 | if (!mc_bus) | |
446 | return -ENOMEM; | |
447 | ||
448 | mc_dev = &mc_bus->mc_dev; | |
449 | } else { | |
450 | /* | |
451 | * Allocate a regular fsl_mc_device object: | |
452 | */ | |
453 | mc_dev = kmem_cache_zalloc(mc_dev_cache, GFP_KERNEL); | |
454 | if (!mc_dev) | |
455 | return -ENOMEM; | |
456 | } | |
457 | ||
458 | mc_dev->obj_desc = *obj_desc; | |
459 | mc_dev->mc_io = mc_io; | |
460 | device_initialize(&mc_dev->dev); | |
461 | mc_dev->dev.parent = parent_dev; | |
462 | mc_dev->dev.bus = &fsl_mc_bus_type; | |
77371fbd | 463 | dev_set_name(&mc_dev->dev, "%s.%d", obj_desc->type, obj_desc->id); |
bbf9d17d GR |
464 | |
465 | if (strcmp(obj_desc->type, "dprc") == 0) { | |
466 | struct fsl_mc_io *mc_io2; | |
467 | ||
468 | mc_dev->flags |= FSL_MC_IS_DPRC; | |
469 | ||
470 | /* | |
471 | * To get the DPRC's ICID, we need to open the DPRC | |
472 | * in get_dprc_icid(). For child DPRCs, we do so using the | |
473 | * parent DPRC's MC portal instead of the child DPRC's MC | |
474 | * portal, in case the child DPRC is already opened with | |
475 | * its own portal (e.g., the DPRC used by AIOP). | |
476 | * | |
477 | * NOTE: There cannot be more than one active open for a | |
478 | * given MC object, using the same MC portal. | |
479 | */ | |
480 | if (parent_mc_dev) { | |
481 | /* | |
482 | * device being added is a child DPRC device | |
483 | */ | |
484 | mc_io2 = parent_mc_dev->mc_io; | |
485 | } else { | |
486 | /* | |
487 | * device being added is the root DPRC device | |
488 | */ | |
489 | if (WARN_ON(!mc_io)) { | |
490 | error = -EINVAL; | |
491 | goto error_cleanup_dev; | |
492 | } | |
493 | ||
494 | mc_io2 = mc_io; | |
bbf9d17d GR |
495 | } |
496 | ||
497 | error = get_dprc_icid(mc_io2, obj_desc->id, &mc_dev->icid); | |
498 | if (error < 0) | |
499 | goto error_cleanup_dev; | |
500 | } else { | |
501 | /* | |
58caaac1 SY |
502 | * A non-DPRC object has to be a child of a DPRC, use the |
503 | * parent's ICID and interrupt domain. | |
bbf9d17d GR |
504 | */ |
505 | mc_dev->icid = parent_mc_dev->icid; | |
506 | mc_dev->dma_mask = FSL_MC_DEFAULT_DMA_MASK; | |
507 | mc_dev->dev.dma_mask = &mc_dev->dma_mask; | |
660a24bf GR |
508 | dev_set_msi_domain(&mc_dev->dev, |
509 | dev_get_msi_domain(&parent_mc_dev->dev)); | |
bbf9d17d GR |
510 | } |
511 | ||
512 | /* | |
513 | * Get MMIO regions for the device from the MC: | |
514 | * | |
515 | * NOTE: the root DPRC is a special case as its MMIO region is | |
516 | * obtained from the device tree | |
517 | */ | |
518 | if (parent_mc_dev && obj_desc->region_count != 0) { | |
519 | error = fsl_mc_device_get_mmio_regions(mc_dev, | |
520 | parent_mc_dev); | |
521 | if (error < 0) | |
522 | goto error_cleanup_dev; | |
523 | } | |
524 | ||
0f90f25b SY |
525 | /* Objects are coherent, unless 'no shareability' flag set. */ |
526 | if (!(obj_desc->flags & DPRC_OBJ_FLAG_NO_MEM_SHAREABILITY)) | |
527 | arch_setup_dma_ops(&mc_dev->dev, 0, 0, NULL, true); | |
528 | ||
bbf9d17d GR |
529 | /* |
530 | * The device-specific probe callback will get invoked by device_add() | |
531 | */ | |
532 | error = device_add(&mc_dev->dev); | |
533 | if (error < 0) { | |
534 | dev_err(parent_dev, | |
535 | "device_add() failed for device %s: %d\n", | |
536 | dev_name(&mc_dev->dev), error); | |
537 | goto error_cleanup_dev; | |
538 | } | |
539 | ||
e3494af5 | 540 | dev_dbg(parent_dev, "added %s\n", dev_name(&mc_dev->dev)); |
bbf9d17d GR |
541 | |
542 | *new_mc_dev = mc_dev; | |
543 | return 0; | |
544 | ||
545 | error_cleanup_dev: | |
546 | kfree(mc_dev->regions); | |
547 | if (mc_bus) | |
548 | devm_kfree(parent_dev, mc_bus); | |
549 | else | |
550 | kmem_cache_free(mc_dev_cache, mc_dev); | |
551 | ||
552 | return error; | |
553 | } | |
554 | EXPORT_SYMBOL_GPL(fsl_mc_device_add); | |
555 | ||
556 | /** | |
58caaac1 | 557 | * fsl_mc_device_remove - Remove an fsl-mc device from being visible to |
bbf9d17d GR |
558 | * Linux |
559 | * | |
58caaac1 | 560 | * @mc_dev: Pointer to an fsl-mc device |
bbf9d17d GR |
561 | */ |
562 | void fsl_mc_device_remove(struct fsl_mc_device *mc_dev) | |
563 | { | |
564 | struct fsl_mc_bus *mc_bus = NULL; | |
565 | ||
566 | kfree(mc_dev->regions); | |
567 | ||
568 | /* | |
569 | * The device-specific remove callback will get invoked by device_del() | |
570 | */ | |
571 | device_del(&mc_dev->dev); | |
572 | put_device(&mc_dev->dev); | |
573 | ||
0e724ba3 | 574 | if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) |
bbf9d17d | 575 | mc_bus = to_fsl_mc_bus(mc_dev); |
2bdc55d9 | 576 | |
bbf9d17d GR |
577 | if (mc_bus) |
578 | devm_kfree(mc_dev->dev.parent, mc_bus); | |
579 | else | |
580 | kmem_cache_free(mc_dev_cache, mc_dev); | |
581 | } | |
582 | EXPORT_SYMBOL_GPL(fsl_mc_device_remove); | |
583 | ||
584 | static int parse_mc_ranges(struct device *dev, | |
585 | int *paddr_cells, | |
586 | int *mc_addr_cells, | |
587 | int *mc_size_cells, | |
588 | const __be32 **ranges_start, | |
ba72f25b | 589 | u8 *num_ranges) |
bbf9d17d GR |
590 | { |
591 | const __be32 *prop; | |
592 | int range_tuple_cell_count; | |
593 | int ranges_len; | |
594 | int tuple_len; | |
595 | struct device_node *mc_node = dev->of_node; | |
596 | ||
597 | *ranges_start = of_get_property(mc_node, "ranges", &ranges_len); | |
598 | if (!(*ranges_start) || !ranges_len) { | |
599 | dev_warn(dev, | |
600 | "missing or empty ranges property for device tree node '%s'\n", | |
601 | mc_node->name); | |
602 | ||
603 | *num_ranges = 0; | |
604 | return 0; | |
605 | } | |
606 | ||
607 | *paddr_cells = of_n_addr_cells(mc_node); | |
608 | ||
609 | prop = of_get_property(mc_node, "#address-cells", NULL); | |
610 | if (prop) | |
611 | *mc_addr_cells = be32_to_cpup(prop); | |
612 | else | |
613 | *mc_addr_cells = *paddr_cells; | |
614 | ||
615 | prop = of_get_property(mc_node, "#size-cells", NULL); | |
616 | if (prop) | |
617 | *mc_size_cells = be32_to_cpup(prop); | |
618 | else | |
619 | *mc_size_cells = of_n_size_cells(mc_node); | |
620 | ||
621 | range_tuple_cell_count = *paddr_cells + *mc_addr_cells + | |
622 | *mc_size_cells; | |
623 | ||
624 | tuple_len = range_tuple_cell_count * sizeof(__be32); | |
625 | if (ranges_len % tuple_len != 0) { | |
626 | dev_err(dev, "malformed ranges property '%s'\n", mc_node->name); | |
627 | return -EINVAL; | |
628 | } | |
629 | ||
630 | *num_ranges = ranges_len / tuple_len; | |
631 | return 0; | |
632 | } | |
633 | ||
634 | static int get_mc_addr_translation_ranges(struct device *dev, | |
635 | struct fsl_mc_addr_translation_range | |
636 | **ranges, | |
ba72f25b | 637 | u8 *num_ranges) |
bbf9d17d GR |
638 | { |
639 | int error; | |
640 | int paddr_cells; | |
641 | int mc_addr_cells; | |
642 | int mc_size_cells; | |
643 | int i; | |
644 | const __be32 *ranges_start; | |
645 | const __be32 *cell; | |
646 | ||
647 | error = parse_mc_ranges(dev, | |
648 | &paddr_cells, | |
649 | &mc_addr_cells, | |
650 | &mc_size_cells, | |
651 | &ranges_start, | |
652 | num_ranges); | |
653 | if (error < 0) | |
654 | return error; | |
655 | ||
656 | if (!(*num_ranges)) { | |
657 | /* | |
658 | * Missing or empty ranges property ("ranges;") for the | |
659 | * 'fsl,qoriq-mc' node. In this case, identity mapping | |
660 | * will be used. | |
661 | */ | |
662 | *ranges = NULL; | |
663 | return 0; | |
664 | } | |
665 | ||
666 | *ranges = devm_kcalloc(dev, *num_ranges, | |
667 | sizeof(struct fsl_mc_addr_translation_range), | |
668 | GFP_KERNEL); | |
669 | if (!(*ranges)) | |
670 | return -ENOMEM; | |
671 | ||
672 | cell = ranges_start; | |
673 | for (i = 0; i < *num_ranges; ++i) { | |
674 | struct fsl_mc_addr_translation_range *range = &(*ranges)[i]; | |
675 | ||
1ee695fa GR |
676 | range->mc_region_type = of_read_number(cell, 1); |
677 | range->start_mc_offset = of_read_number(cell + 1, | |
678 | mc_addr_cells - 1); | |
bbf9d17d GR |
679 | cell += mc_addr_cells; |
680 | range->start_phys_addr = of_read_number(cell, paddr_cells); | |
681 | cell += paddr_cells; | |
1ee695fa | 682 | range->end_mc_offset = range->start_mc_offset + |
bbf9d17d GR |
683 | of_read_number(cell, mc_size_cells); |
684 | ||
685 | cell += mc_size_cells; | |
686 | } | |
687 | ||
688 | return 0; | |
689 | } | |
690 | ||
691 | /** | |
692 | * fsl_mc_bus_probe - callback invoked when the root MC bus is being | |
693 | * added | |
694 | */ | |
695 | static int fsl_mc_bus_probe(struct platform_device *pdev) | |
696 | { | |
697 | struct dprc_obj_desc obj_desc; | |
698 | int error; | |
699 | struct fsl_mc *mc; | |
700 | struct fsl_mc_device *mc_bus_dev = NULL; | |
701 | struct fsl_mc_io *mc_io = NULL; | |
702 | int container_id; | |
703 | phys_addr_t mc_portal_phys_addr; | |
a2f9ff6c | 704 | u32 mc_portal_size; |
bbf9d17d GR |
705 | struct mc_version mc_version; |
706 | struct resource res; | |
707 | ||
bbf9d17d GR |
708 | mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); |
709 | if (!mc) | |
710 | return -ENOMEM; | |
711 | ||
712 | platform_set_drvdata(pdev, mc); | |
713 | ||
714 | /* | |
715 | * Get physical address of MC portal for the root DPRC: | |
716 | */ | |
717 | error = of_address_to_resource(pdev->dev.of_node, 0, &res); | |
718 | if (error < 0) { | |
719 | dev_err(&pdev->dev, | |
720 | "of_address_to_resource() failed for %s\n", | |
721 | pdev->dev.of_node->full_name); | |
722 | return error; | |
723 | } | |
724 | ||
725 | mc_portal_phys_addr = res.start; | |
726 | mc_portal_size = resource_size(&res); | |
727 | error = fsl_create_mc_io(&pdev->dev, mc_portal_phys_addr, | |
1129cde5 GR |
728 | mc_portal_size, NULL, |
729 | FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, &mc_io); | |
bbf9d17d GR |
730 | if (error < 0) |
731 | return error; | |
732 | ||
40577435 | 733 | error = mc_get_version(mc_io, 0, &mc_version); |
bbf9d17d GR |
734 | if (error != 0) { |
735 | dev_err(&pdev->dev, | |
736 | "mc_get_version() failed with error %d\n", error); | |
737 | goto error_cleanup_mc_io; | |
738 | } | |
739 | ||
e3494af5 | 740 | dev_info(&pdev->dev, "MC firmware version: %u.%u.%u\n", |
bbf9d17d GR |
741 | mc_version.major, mc_version.minor, mc_version.revision); |
742 | ||
bbf9d17d GR |
743 | error = get_mc_addr_translation_ranges(&pdev->dev, |
744 | &mc->translation_ranges, | |
745 | &mc->num_translation_ranges); | |
746 | if (error < 0) | |
747 | goto error_cleanup_mc_io; | |
748 | ||
decd3d0c | 749 | error = dprc_get_container_id(mc_io, 0, &container_id); |
bbf9d17d GR |
750 | if (error < 0) { |
751 | dev_err(&pdev->dev, | |
752 | "dpmng_get_container_id() failed: %d\n", error); | |
753 | goto error_cleanup_mc_io; | |
754 | } | |
755 | ||
1716cb4c | 756 | memset(&obj_desc, 0, sizeof(struct dprc_obj_desc)); |
decd3d0c IC |
757 | error = dprc_get_api_version(mc_io, 0, |
758 | &obj_desc.ver_major, | |
759 | &obj_desc.ver_minor); | |
9529d166 IK |
760 | if (error < 0) |
761 | goto error_cleanup_mc_io; | |
762 | ||
bbf9d17d GR |
763 | obj_desc.vendor = FSL_MC_VENDOR_FREESCALE; |
764 | strcpy(obj_desc.type, "dprc"); | |
765 | obj_desc.id = container_id; | |
a17f4aa6 | 766 | obj_desc.irq_count = 1; |
bbf9d17d GR |
767 | obj_desc.region_count = 0; |
768 | ||
769 | error = fsl_mc_device_add(&obj_desc, mc_io, &pdev->dev, &mc_bus_dev); | |
770 | if (error < 0) | |
771 | goto error_cleanup_mc_io; | |
772 | ||
773 | mc->root_mc_bus_dev = mc_bus_dev; | |
774 | return 0; | |
775 | ||
776 | error_cleanup_mc_io: | |
777 | fsl_destroy_mc_io(mc_io); | |
778 | return error; | |
779 | } | |
780 | ||
781 | /** | |
782 | * fsl_mc_bus_remove - callback invoked when the root MC bus is being | |
783 | * removed | |
784 | */ | |
785 | static int fsl_mc_bus_remove(struct platform_device *pdev) | |
786 | { | |
787 | struct fsl_mc *mc = platform_get_drvdata(pdev); | |
788 | ||
b55f00c6 | 789 | if (WARN_ON(!fsl_mc_is_root_dprc(&mc->root_mc_bus_dev->dev))) |
bbf9d17d GR |
790 | return -EINVAL; |
791 | ||
792 | fsl_mc_device_remove(mc->root_mc_bus_dev); | |
f9362714 BB |
793 | |
794 | fsl_destroy_mc_io(mc->root_mc_bus_dev->mc_io); | |
795 | mc->root_mc_bus_dev->mc_io = NULL; | |
796 | ||
bbf9d17d GR |
797 | return 0; |
798 | } | |
799 | ||
800 | static const struct of_device_id fsl_mc_bus_match_table[] = { | |
801 | {.compatible = "fsl,qoriq-mc",}, | |
802 | {}, | |
803 | }; | |
804 | ||
805 | MODULE_DEVICE_TABLE(of, fsl_mc_bus_match_table); | |
806 | ||
807 | static struct platform_driver fsl_mc_bus_driver = { | |
808 | .driver = { | |
809 | .name = "fsl_mc_bus", | |
bbf9d17d GR |
810 | .pm = NULL, |
811 | .of_match_table = fsl_mc_bus_match_table, | |
812 | }, | |
813 | .probe = fsl_mc_bus_probe, | |
814 | .remove = fsl_mc_bus_remove, | |
815 | }; | |
816 | ||
817 | static int __init fsl_mc_bus_driver_init(void) | |
818 | { | |
819 | int error; | |
820 | ||
821 | mc_dev_cache = kmem_cache_create("fsl_mc_device", | |
822 | sizeof(struct fsl_mc_device), 0, 0, | |
823 | NULL); | |
824 | if (!mc_dev_cache) { | |
825 | pr_err("Could not create fsl_mc_device cache\n"); | |
826 | return -ENOMEM; | |
827 | } | |
828 | ||
829 | error = bus_register(&fsl_mc_bus_type); | |
830 | if (error < 0) { | |
a6737837 | 831 | pr_err("bus type registration failed: %d\n", error); |
bbf9d17d GR |
832 | goto error_cleanup_cache; |
833 | } | |
834 | ||
bbf9d17d GR |
835 | error = platform_driver_register(&fsl_mc_bus_driver); |
836 | if (error < 0) { | |
837 | pr_err("platform_driver_register() failed: %d\n", error); | |
838 | goto error_cleanup_bus; | |
839 | } | |
840 | ||
f2f2726b GR |
841 | error = dprc_driver_init(); |
842 | if (error < 0) | |
843 | goto error_cleanup_driver; | |
844 | ||
e91ffa9e GR |
845 | error = fsl_mc_allocator_driver_init(); |
846 | if (error < 0) | |
847 | goto error_cleanup_dprc_driver; | |
848 | ||
660a24bf GR |
849 | error = its_fsl_mc_msi_init(); |
850 | if (error < 0) | |
851 | goto error_cleanup_mc_allocator; | |
852 | ||
bbf9d17d GR |
853 | return 0; |
854 | ||
660a24bf GR |
855 | error_cleanup_mc_allocator: |
856 | fsl_mc_allocator_driver_exit(); | |
857 | ||
e91ffa9e GR |
858 | error_cleanup_dprc_driver: |
859 | dprc_driver_exit(); | |
860 | ||
f2f2726b GR |
861 | error_cleanup_driver: |
862 | platform_driver_unregister(&fsl_mc_bus_driver); | |
863 | ||
bbf9d17d GR |
864 | error_cleanup_bus: |
865 | bus_unregister(&fsl_mc_bus_type); | |
866 | ||
867 | error_cleanup_cache: | |
868 | kmem_cache_destroy(mc_dev_cache); | |
869 | return error; | |
870 | } | |
bbf9d17d | 871 | postcore_initcall(fsl_mc_bus_driver_init); |