]>
Commit | Line | Data |
---|---|---|
776dc384 TR |
1 | /* |
2 | * Copyright (C) 2012 Avionic Design GmbH | |
3 | * Copyright (C) 2012-2013, NVIDIA Corporation | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | ||
18 | #include <linux/host1x.h> | |
19 | #include <linux/of.h> | |
20 | #include <linux/slab.h> | |
21 | ||
d24b2898 | 22 | #include "bus.h" |
776dc384 TR |
23 | #include "dev.h" |
24 | ||
25 | static DEFINE_MUTEX(clients_lock); | |
26 | static LIST_HEAD(clients); | |
27 | ||
28 | static DEFINE_MUTEX(drivers_lock); | |
29 | static LIST_HEAD(drivers); | |
30 | ||
31 | static DEFINE_MUTEX(devices_lock); | |
32 | static LIST_HEAD(devices); | |
33 | ||
34 | struct host1x_subdev { | |
35 | struct host1x_client *client; | |
36 | struct device_node *np; | |
37 | struct list_head list; | |
38 | }; | |
39 | ||
40 | /** | |
41 | * host1x_subdev_add() - add a new subdevice with an associated device node | |
42 | */ | |
43 | static int host1x_subdev_add(struct host1x_device *device, | |
44 | struct device_node *np) | |
45 | { | |
46 | struct host1x_subdev *subdev; | |
47 | ||
48 | subdev = kzalloc(sizeof(*subdev), GFP_KERNEL); | |
49 | if (!subdev) | |
50 | return -ENOMEM; | |
51 | ||
52 | INIT_LIST_HEAD(&subdev->list); | |
53 | subdev->np = of_node_get(np); | |
54 | ||
55 | mutex_lock(&device->subdevs_lock); | |
56 | list_add_tail(&subdev->list, &device->subdevs); | |
57 | mutex_unlock(&device->subdevs_lock); | |
58 | ||
59 | return 0; | |
60 | } | |
61 | ||
62 | /** | |
63 | * host1x_subdev_del() - remove subdevice | |
64 | */ | |
65 | static void host1x_subdev_del(struct host1x_subdev *subdev) | |
66 | { | |
67 | list_del(&subdev->list); | |
68 | of_node_put(subdev->np); | |
69 | kfree(subdev); | |
70 | } | |
71 | ||
72 | /** | |
73 | * host1x_device_parse_dt() - scan device tree and add matching subdevices | |
74 | */ | |
f4c5cf88 TR |
75 | static int host1x_device_parse_dt(struct host1x_device *device, |
76 | struct host1x_driver *driver) | |
776dc384 TR |
77 | { |
78 | struct device_node *np; | |
79 | int err; | |
80 | ||
81 | for_each_child_of_node(device->dev.parent->of_node, np) { | |
f4c5cf88 | 82 | if (of_match_node(driver->subdevs, np) && |
776dc384 TR |
83 | of_device_is_available(np)) { |
84 | err = host1x_subdev_add(device, np); | |
85 | if (err < 0) | |
86 | return err; | |
87 | } | |
88 | } | |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | static void host1x_subdev_register(struct host1x_device *device, | |
94 | struct host1x_subdev *subdev, | |
95 | struct host1x_client *client) | |
96 | { | |
97 | int err; | |
98 | ||
99 | /* | |
100 | * Move the subdevice to the list of active (registered) subdevices | |
101 | * and associate it with a client. At the same time, associate the | |
102 | * client with its parent device. | |
103 | */ | |
104 | mutex_lock(&device->subdevs_lock); | |
105 | mutex_lock(&device->clients_lock); | |
106 | list_move_tail(&client->list, &device->clients); | |
107 | list_move_tail(&subdev->list, &device->active); | |
108 | client->parent = &device->dev; | |
109 | subdev->client = client; | |
110 | mutex_unlock(&device->clients_lock); | |
111 | mutex_unlock(&device->subdevs_lock); | |
112 | ||
776dc384 | 113 | if (list_empty(&device->subdevs)) { |
f4c5cf88 | 114 | err = device_add(&device->dev); |
776dc384 | 115 | if (err < 0) |
f4c5cf88 | 116 | dev_err(&device->dev, "failed to add: %d\n", err); |
536e1715 | 117 | else |
f4c5cf88 | 118 | device->registered = true; |
776dc384 TR |
119 | } |
120 | } | |
121 | ||
122 | static void __host1x_subdev_unregister(struct host1x_device *device, | |
123 | struct host1x_subdev *subdev) | |
124 | { | |
125 | struct host1x_client *client = subdev->client; | |
776dc384 TR |
126 | |
127 | /* | |
128 | * If all subdevices have been activated, we're about to remove the | |
129 | * first active subdevice, so unload the driver first. | |
130 | */ | |
f4c5cf88 TR |
131 | if (list_empty(&device->subdevs)) { |
132 | if (device->registered) { | |
133 | device->registered = false; | |
134 | device_del(&device->dev); | |
135 | } | |
776dc384 TR |
136 | } |
137 | ||
138 | /* | |
139 | * Move the subdevice back to the list of idle subdevices and remove | |
140 | * it from list of clients. | |
141 | */ | |
142 | mutex_lock(&device->clients_lock); | |
143 | subdev->client = NULL; | |
144 | client->parent = NULL; | |
145 | list_move_tail(&subdev->list, &device->subdevs); | |
146 | /* | |
147 | * XXX: Perhaps don't do this here, but rather explicitly remove it | |
148 | * when the device is about to be deleted. | |
149 | * | |
150 | * This is somewhat complicated by the fact that this function is | |
151 | * used to remove the subdevice when a client is unregistered but | |
152 | * also when the composite device is about to be removed. | |
153 | */ | |
154 | list_del_init(&client->list); | |
155 | mutex_unlock(&device->clients_lock); | |
156 | } | |
157 | ||
158 | static void host1x_subdev_unregister(struct host1x_device *device, | |
159 | struct host1x_subdev *subdev) | |
160 | { | |
161 | mutex_lock(&device->subdevs_lock); | |
162 | __host1x_subdev_unregister(device, subdev); | |
163 | mutex_unlock(&device->subdevs_lock); | |
164 | } | |
165 | ||
166 | int host1x_device_init(struct host1x_device *device) | |
167 | { | |
168 | struct host1x_client *client; | |
169 | int err; | |
170 | ||
171 | mutex_lock(&device->clients_lock); | |
172 | ||
173 | list_for_each_entry(client, &device->clients, list) { | |
174 | if (client->ops && client->ops->init) { | |
175 | err = client->ops->init(client); | |
176 | if (err < 0) { | |
177 | dev_err(&device->dev, | |
178 | "failed to initialize %s: %d\n", | |
179 | dev_name(client->dev), err); | |
180 | mutex_unlock(&device->clients_lock); | |
181 | return err; | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | mutex_unlock(&device->clients_lock); | |
187 | ||
188 | return 0; | |
189 | } | |
fae798a1 | 190 | EXPORT_SYMBOL(host1x_device_init); |
776dc384 TR |
191 | |
192 | int host1x_device_exit(struct host1x_device *device) | |
193 | { | |
194 | struct host1x_client *client; | |
195 | int err; | |
196 | ||
197 | mutex_lock(&device->clients_lock); | |
198 | ||
199 | list_for_each_entry_reverse(client, &device->clients, list) { | |
200 | if (client->ops && client->ops->exit) { | |
201 | err = client->ops->exit(client); | |
202 | if (err < 0) { | |
203 | dev_err(&device->dev, | |
204 | "failed to cleanup %s: %d\n", | |
205 | dev_name(client->dev), err); | |
206 | mutex_unlock(&device->clients_lock); | |
207 | return err; | |
208 | } | |
209 | } | |
210 | } | |
211 | ||
212 | mutex_unlock(&device->clients_lock); | |
213 | ||
214 | return 0; | |
215 | } | |
fae798a1 | 216 | EXPORT_SYMBOL(host1x_device_exit); |
776dc384 | 217 | |
0c7dfd36 TR |
218 | static int host1x_add_client(struct host1x *host1x, |
219 | struct host1x_client *client) | |
776dc384 TR |
220 | { |
221 | struct host1x_device *device; | |
222 | struct host1x_subdev *subdev; | |
223 | ||
224 | mutex_lock(&host1x->devices_lock); | |
225 | ||
226 | list_for_each_entry(device, &host1x->devices, list) { | |
227 | list_for_each_entry(subdev, &device->subdevs, list) { | |
228 | if (subdev->np == client->dev->of_node) { | |
229 | host1x_subdev_register(device, subdev, client); | |
230 | mutex_unlock(&host1x->devices_lock); | |
231 | return 0; | |
232 | } | |
233 | } | |
234 | } | |
235 | ||
236 | mutex_unlock(&host1x->devices_lock); | |
237 | return -ENODEV; | |
238 | } | |
239 | ||
0c7dfd36 TR |
240 | static int host1x_del_client(struct host1x *host1x, |
241 | struct host1x_client *client) | |
776dc384 TR |
242 | { |
243 | struct host1x_device *device, *dt; | |
244 | struct host1x_subdev *subdev; | |
245 | ||
246 | mutex_lock(&host1x->devices_lock); | |
247 | ||
248 | list_for_each_entry_safe(device, dt, &host1x->devices, list) { | |
249 | list_for_each_entry(subdev, &device->active, list) { | |
250 | if (subdev->client == client) { | |
251 | host1x_subdev_unregister(device, subdev); | |
252 | mutex_unlock(&host1x->devices_lock); | |
253 | return 0; | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | mutex_unlock(&host1x->devices_lock); | |
259 | return -ENODEV; | |
260 | } | |
261 | ||
f4c5cf88 TR |
262 | static int host1x_device_match(struct device *dev, struct device_driver *drv) |
263 | { | |
264 | return strcmp(dev_name(dev), drv->name) == 0; | |
265 | } | |
776dc384 | 266 | |
f4c5cf88 | 267 | static int host1x_device_probe(struct device *dev) |
776dc384 | 268 | { |
f4c5cf88 TR |
269 | struct host1x_driver *driver = to_host1x_driver(dev->driver); |
270 | struct host1x_device *device = to_host1x_device(dev); | |
271 | ||
272 | if (driver->probe) | |
273 | return driver->probe(device); | |
274 | ||
275 | return 0; | |
776dc384 TR |
276 | } |
277 | ||
f4c5cf88 | 278 | static int host1x_device_remove(struct device *dev) |
776dc384 | 279 | { |
f4c5cf88 TR |
280 | struct host1x_driver *driver = to_host1x_driver(dev->driver); |
281 | struct host1x_device *device = to_host1x_device(dev); | |
282 | ||
283 | if (driver->remove) | |
284 | return driver->remove(device); | |
285 | ||
286 | return 0; | |
776dc384 TR |
287 | } |
288 | ||
f4c5cf88 TR |
289 | static void host1x_device_shutdown(struct device *dev) |
290 | { | |
291 | struct host1x_driver *driver = to_host1x_driver(dev->driver); | |
292 | struct host1x_device *device = to_host1x_device(dev); | |
293 | ||
294 | if (driver->shutdown) | |
295 | driver->shutdown(device); | |
296 | } | |
297 | ||
298 | static const struct dev_pm_ops host1x_device_pm_ops = { | |
299 | .suspend = pm_generic_suspend, | |
300 | .resume = pm_generic_resume, | |
301 | .freeze = pm_generic_freeze, | |
302 | .thaw = pm_generic_thaw, | |
303 | .poweroff = pm_generic_poweroff, | |
304 | .restore = pm_generic_restore, | |
305 | }; | |
306 | ||
307 | struct bus_type host1x_bus_type = { | |
308 | .name = "host1x", | |
309 | .match = host1x_device_match, | |
310 | .probe = host1x_device_probe, | |
311 | .remove = host1x_device_remove, | |
312 | .shutdown = host1x_device_shutdown, | |
313 | .pm = &host1x_device_pm_ops, | |
314 | }; | |
315 | ||
99d2cd81 TR |
316 | static void __host1x_device_del(struct host1x_device *device) |
317 | { | |
318 | struct host1x_subdev *subdev, *sd; | |
319 | struct host1x_client *client, *cl; | |
320 | ||
321 | mutex_lock(&device->subdevs_lock); | |
322 | ||
323 | /* unregister subdevices */ | |
324 | list_for_each_entry_safe(subdev, sd, &device->active, list) { | |
325 | /* | |
326 | * host1x_subdev_unregister() will remove the client from | |
327 | * any lists, so we'll need to manually add it back to the | |
328 | * list of idle clients. | |
329 | * | |
330 | * XXX: Alternatively, perhaps don't remove the client from | |
331 | * any lists in host1x_subdev_unregister() and instead do | |
332 | * that explicitly from host1x_unregister_client()? | |
333 | */ | |
334 | client = subdev->client; | |
335 | ||
336 | __host1x_subdev_unregister(device, subdev); | |
337 | ||
338 | /* add the client to the list of idle clients */ | |
339 | mutex_lock(&clients_lock); | |
340 | list_add_tail(&client->list, &clients); | |
341 | mutex_unlock(&clients_lock); | |
342 | } | |
343 | ||
344 | /* remove subdevices */ | |
345 | list_for_each_entry_safe(subdev, sd, &device->subdevs, list) | |
346 | host1x_subdev_del(subdev); | |
347 | ||
348 | mutex_unlock(&device->subdevs_lock); | |
349 | ||
350 | /* move clients to idle list */ | |
351 | mutex_lock(&clients_lock); | |
352 | mutex_lock(&device->clients_lock); | |
353 | ||
354 | list_for_each_entry_safe(client, cl, &device->clients, list) | |
355 | list_move_tail(&client->list, &clients); | |
356 | ||
357 | mutex_unlock(&device->clients_lock); | |
358 | mutex_unlock(&clients_lock); | |
359 | ||
360 | /* finally remove the device */ | |
361 | list_del_init(&device->list); | |
362 | } | |
363 | ||
776dc384 TR |
364 | static void host1x_device_release(struct device *dev) |
365 | { | |
366 | struct host1x_device *device = to_host1x_device(dev); | |
367 | ||
99d2cd81 | 368 | __host1x_device_del(device); |
776dc384 TR |
369 | kfree(device); |
370 | } | |
371 | ||
372 | static int host1x_device_add(struct host1x *host1x, | |
373 | struct host1x_driver *driver) | |
374 | { | |
375 | struct host1x_client *client, *tmp; | |
376 | struct host1x_subdev *subdev; | |
377 | struct host1x_device *device; | |
378 | int err; | |
379 | ||
380 | device = kzalloc(sizeof(*device), GFP_KERNEL); | |
381 | if (!device) | |
382 | return -ENOMEM; | |
383 | ||
f4c5cf88 TR |
384 | device_initialize(&device->dev); |
385 | ||
776dc384 TR |
386 | mutex_init(&device->subdevs_lock); |
387 | INIT_LIST_HEAD(&device->subdevs); | |
388 | INIT_LIST_HEAD(&device->active); | |
389 | mutex_init(&device->clients_lock); | |
390 | INIT_LIST_HEAD(&device->clients); | |
391 | INIT_LIST_HEAD(&device->list); | |
392 | device->driver = driver; | |
393 | ||
394 | device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask; | |
395 | device->dev.dma_mask = &device->dev.coherent_dma_mask; | |
f4c5cf88 | 396 | dev_set_name(&device->dev, "%s", driver->driver.name); |
776dc384 | 397 | device->dev.release = host1x_device_release; |
776dc384 TR |
398 | device->dev.bus = &host1x_bus_type; |
399 | device->dev.parent = host1x->dev; | |
400 | ||
f4c5cf88 | 401 | err = host1x_device_parse_dt(device, driver); |
776dc384 | 402 | if (err < 0) { |
f4c5cf88 | 403 | kfree(device); |
776dc384 TR |
404 | return err; |
405 | } | |
406 | ||
776dc384 | 407 | list_add_tail(&device->list, &host1x->devices); |
776dc384 TR |
408 | |
409 | mutex_lock(&clients_lock); | |
410 | ||
411 | list_for_each_entry_safe(client, tmp, &clients, list) { | |
412 | list_for_each_entry(subdev, &device->subdevs, list) { | |
413 | if (subdev->np == client->dev->of_node) { | |
414 | host1x_subdev_register(device, subdev, client); | |
415 | break; | |
416 | } | |
417 | } | |
418 | } | |
419 | ||
420 | mutex_unlock(&clients_lock); | |
421 | ||
422 | return 0; | |
423 | } | |
424 | ||
425 | /* | |
426 | * Removes a device by first unregistering any subdevices and then removing | |
427 | * itself from the list of devices. | |
428 | * | |
429 | * This function must be called with the host1x->devices_lock held. | |
430 | */ | |
431 | static void host1x_device_del(struct host1x *host1x, | |
432 | struct host1x_device *device) | |
433 | { | |
f4c5cf88 TR |
434 | if (device->registered) { |
435 | device->registered = false; | |
436 | device_del(&device->dev); | |
437 | } | |
438 | ||
439 | put_device(&device->dev); | |
776dc384 TR |
440 | } |
441 | ||
442 | static void host1x_attach_driver(struct host1x *host1x, | |
443 | struct host1x_driver *driver) | |
444 | { | |
445 | struct host1x_device *device; | |
446 | int err; | |
447 | ||
448 | mutex_lock(&host1x->devices_lock); | |
449 | ||
450 | list_for_each_entry(device, &host1x->devices, list) { | |
451 | if (device->driver == driver) { | |
452 | mutex_unlock(&host1x->devices_lock); | |
453 | return; | |
454 | } | |
455 | } | |
456 | ||
776dc384 TR |
457 | err = host1x_device_add(host1x, driver); |
458 | if (err < 0) | |
459 | dev_err(host1x->dev, "failed to allocate device: %d\n", err); | |
38d98de4 TR |
460 | |
461 | mutex_unlock(&host1x->devices_lock); | |
776dc384 TR |
462 | } |
463 | ||
464 | static void host1x_detach_driver(struct host1x *host1x, | |
465 | struct host1x_driver *driver) | |
466 | { | |
467 | struct host1x_device *device, *tmp; | |
468 | ||
469 | mutex_lock(&host1x->devices_lock); | |
470 | ||
471 | list_for_each_entry_safe(device, tmp, &host1x->devices, list) | |
472 | if (device->driver == driver) | |
473 | host1x_device_del(host1x, device); | |
474 | ||
475 | mutex_unlock(&host1x->devices_lock); | |
476 | } | |
477 | ||
478 | int host1x_register(struct host1x *host1x) | |
479 | { | |
480 | struct host1x_driver *driver; | |
481 | ||
482 | mutex_lock(&devices_lock); | |
483 | list_add_tail(&host1x->list, &devices); | |
484 | mutex_unlock(&devices_lock); | |
485 | ||
486 | mutex_lock(&drivers_lock); | |
487 | ||
488 | list_for_each_entry(driver, &drivers, list) | |
489 | host1x_attach_driver(host1x, driver); | |
490 | ||
491 | mutex_unlock(&drivers_lock); | |
492 | ||
493 | return 0; | |
494 | } | |
495 | ||
496 | int host1x_unregister(struct host1x *host1x) | |
497 | { | |
498 | struct host1x_driver *driver; | |
499 | ||
500 | mutex_lock(&drivers_lock); | |
501 | ||
502 | list_for_each_entry(driver, &drivers, list) | |
503 | host1x_detach_driver(host1x, driver); | |
504 | ||
505 | mutex_unlock(&drivers_lock); | |
506 | ||
507 | mutex_lock(&devices_lock); | |
508 | list_del_init(&host1x->list); | |
509 | mutex_unlock(&devices_lock); | |
510 | ||
511 | return 0; | |
512 | } | |
513 | ||
f4c5cf88 TR |
514 | int host1x_driver_register_full(struct host1x_driver *driver, |
515 | struct module *owner) | |
776dc384 TR |
516 | { |
517 | struct host1x *host1x; | |
518 | ||
519 | INIT_LIST_HEAD(&driver->list); | |
520 | ||
521 | mutex_lock(&drivers_lock); | |
522 | list_add_tail(&driver->list, &drivers); | |
523 | mutex_unlock(&drivers_lock); | |
524 | ||
525 | mutex_lock(&devices_lock); | |
526 | ||
527 | list_for_each_entry(host1x, &devices, list) | |
528 | host1x_attach_driver(host1x, driver); | |
529 | ||
530 | mutex_unlock(&devices_lock); | |
531 | ||
f4c5cf88 TR |
532 | driver->driver.bus = &host1x_bus_type; |
533 | driver->driver.owner = owner; | |
534 | ||
535 | return driver_register(&driver->driver); | |
776dc384 | 536 | } |
f4c5cf88 | 537 | EXPORT_SYMBOL(host1x_driver_register_full); |
776dc384 TR |
538 | |
539 | void host1x_driver_unregister(struct host1x_driver *driver) | |
540 | { | |
541 | mutex_lock(&drivers_lock); | |
542 | list_del_init(&driver->list); | |
543 | mutex_unlock(&drivers_lock); | |
544 | } | |
545 | EXPORT_SYMBOL(host1x_driver_unregister); | |
546 | ||
547 | int host1x_client_register(struct host1x_client *client) | |
548 | { | |
549 | struct host1x *host1x; | |
550 | int err; | |
551 | ||
552 | mutex_lock(&devices_lock); | |
553 | ||
554 | list_for_each_entry(host1x, &devices, list) { | |
0c7dfd36 | 555 | err = host1x_add_client(host1x, client); |
776dc384 TR |
556 | if (!err) { |
557 | mutex_unlock(&devices_lock); | |
558 | return 0; | |
559 | } | |
560 | } | |
561 | ||
562 | mutex_unlock(&devices_lock); | |
563 | ||
564 | mutex_lock(&clients_lock); | |
565 | list_add_tail(&client->list, &clients); | |
566 | mutex_unlock(&clients_lock); | |
567 | ||
568 | return 0; | |
569 | } | |
570 | EXPORT_SYMBOL(host1x_client_register); | |
571 | ||
572 | int host1x_client_unregister(struct host1x_client *client) | |
573 | { | |
574 | struct host1x_client *c; | |
575 | struct host1x *host1x; | |
576 | int err; | |
577 | ||
578 | mutex_lock(&devices_lock); | |
579 | ||
580 | list_for_each_entry(host1x, &devices, list) { | |
0c7dfd36 | 581 | err = host1x_del_client(host1x, client); |
776dc384 TR |
582 | if (!err) { |
583 | mutex_unlock(&devices_lock); | |
584 | return 0; | |
585 | } | |
586 | } | |
587 | ||
588 | mutex_unlock(&devices_lock); | |
589 | mutex_lock(&clients_lock); | |
590 | ||
591 | list_for_each_entry(c, &clients, list) { | |
592 | if (c == client) { | |
593 | list_del_init(&c->list); | |
594 | break; | |
595 | } | |
596 | } | |
597 | ||
598 | mutex_unlock(&clients_lock); | |
599 | ||
600 | return 0; | |
601 | } | |
602 | EXPORT_SYMBOL(host1x_client_unregister); |