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