]>
Commit | Line | Data |
---|---|---|
c8a797a9 GKH |
1 | /* |
2 | * Greybus "Core" | |
3 | * | |
4 | * Copyright 2014 Google Inc. | |
5 | * | |
6 | * Released under the GPLv2 only. | |
7 | */ | |
8 | ||
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
10 | ||
11 | #include <linux/types.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/moduleparam.h> | |
14 | #include <linux/kernel.h> | |
a239f67c | 15 | #include <linux/slab.h> |
c8a797a9 GKH |
16 | #include <linux/device.h> |
17 | ||
18 | #include "greybus.h" | |
19 | ||
20 | /* Allow greybus to be disabled at boot if needed */ | |
21 | static bool nogreybus; | |
22 | #ifdef MODULE | |
23 | module_param(nogreybus, bool, 0444); | |
24 | #else | |
25 | core_param(nogreybus, bool, 0444); | |
26 | #endif | |
27 | int greybus_disabled(void) | |
28 | { | |
29 | return nogreybus; | |
30 | } | |
31 | EXPORT_SYMBOL_GPL(greybus_disabled); | |
32 | ||
1bb3c724 AE |
33 | static spinlock_t cport_id_map_lock; |
34 | ||
778c69c9 | 35 | static int greybus_module_match(struct device *dev, struct device_driver *drv) |
c8a797a9 GKH |
36 | { |
37 | struct greybus_driver *driver = to_greybus_driver(dev->driver); | |
e1e9dbdd | 38 | struct gb_module *gmod = to_gb_module(dev); |
6584c8af | 39 | const struct greybus_module_id *id; |
c8a797a9 | 40 | |
e1e9dbdd | 41 | id = gb_module_match_id(gmod, driver->id_table); |
c8a797a9 GKH |
42 | if (id) |
43 | return 1; | |
44 | /* FIXME - Dyanmic ids? */ | |
45 | return 0; | |
46 | } | |
47 | ||
48 | static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env) | |
49 | { | |
e1e9dbdd | 50 | /* struct gb_module *gmod = to_gb_module(dev); */ |
c8a797a9 GKH |
51 | |
52 | /* FIXME - add some uevents here... */ | |
53 | return 0; | |
54 | } | |
55 | ||
27fb8310 | 56 | static struct bus_type greybus_bus_type = { |
c8a797a9 | 57 | .name = "greybus", |
778c69c9 | 58 | .match = greybus_module_match, |
c8a797a9 GKH |
59 | .uevent = greybus_uevent, |
60 | }; | |
61 | ||
62 | static int greybus_probe(struct device *dev) | |
63 | { | |
64 | struct greybus_driver *driver = to_greybus_driver(dev->driver); | |
e1e9dbdd | 65 | struct gb_module *gmod = to_gb_module(dev); |
6584c8af | 66 | const struct greybus_module_id *id; |
c8a797a9 GKH |
67 | int retval; |
68 | ||
69 | /* match id */ | |
e1e9dbdd | 70 | id = gb_module_match_id(gmod, driver->id_table); |
c8a797a9 GKH |
71 | if (!id) |
72 | return -ENODEV; | |
73 | ||
778c69c9 | 74 | retval = driver->probe(gmod, id); |
c8a797a9 GKH |
75 | if (retval) |
76 | return retval; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static int greybus_remove(struct device *dev) | |
82 | { | |
83 | struct greybus_driver *driver = to_greybus_driver(dev->driver); | |
e1e9dbdd | 84 | struct gb_module *gmod = to_gb_module(dev); |
c8a797a9 | 85 | |
778c69c9 | 86 | driver->disconnect(gmod); |
c8a797a9 GKH |
87 | return 0; |
88 | } | |
89 | ||
90 | int greybus_register_driver(struct greybus_driver *driver, struct module *owner, | |
91 | const char *mod_name) | |
92 | { | |
93 | int retval; | |
94 | ||
95 | if (greybus_disabled()) | |
96 | return -ENODEV; | |
97 | ||
98 | driver->driver.name = driver->name; | |
99 | driver->driver.probe = greybus_probe; | |
100 | driver->driver.remove = greybus_remove; | |
101 | driver->driver.owner = owner; | |
102 | driver->driver.mod_name = mod_name; | |
103 | ||
104 | retval = driver_register(&driver->driver); | |
105 | if (retval) | |
106 | return retval; | |
107 | ||
108 | pr_info("registered new driver %s\n", driver->name); | |
109 | return 0; | |
110 | } | |
111 | EXPORT_SYMBOL_GPL(greybus_register_driver); | |
112 | ||
113 | void greybus_deregister(struct greybus_driver *driver) | |
114 | { | |
115 | driver_unregister(&driver->driver); | |
116 | } | |
117 | EXPORT_SYMBOL_GPL(greybus_deregister); | |
118 | ||
199d68d4 | 119 | |
b94295e0 GKH |
120 | static void greybus_module_release(struct device *dev) |
121 | { | |
e1e9dbdd | 122 | struct gb_module *gmod = to_gb_module(dev); |
b94295e0 GKH |
123 | int i; |
124 | ||
778c69c9 AE |
125 | for (i = 0; i < gmod->num_strings; ++i) |
126 | kfree(gmod->string[i]); | |
778c69c9 | 127 | kfree(gmod); |
b94295e0 GKH |
128 | } |
129 | ||
130 | ||
b94295e0 GKH |
131 | static struct device_type greybus_module_type = { |
132 | .name = "greybus_module", | |
133 | .release = greybus_module_release, | |
134 | }; | |
135 | ||
c68adb2f AE |
136 | /* XXX |
137 | * This needs to be driven by the list of functions that the | |
138 | * manifest says are present. | |
139 | */ | |
e1e9dbdd | 140 | static int gb_init_subdevs(struct gb_module *gmod, |
a239f67c | 141 | const struct greybus_module_id *id) |
199d68d4 GKH |
142 | { |
143 | int retval; | |
144 | ||
145 | /* Allocate all of the different "sub device types" for this device */ | |
c68adb2f AE |
146 | |
147 | /* XXX | |
148 | * Decide what exactly we should get supplied for the i2c | |
149 | * probe, and then work that back to what should be present | |
150 | * in the manifest. | |
151 | */ | |
778c69c9 | 152 | retval = gb_i2c_probe(gmod, id); |
db6e1fd2 GKH |
153 | if (retval) |
154 | goto error_i2c; | |
155 | ||
778c69c9 | 156 | retval = gb_gpio_probe(gmod, id); |
db6e1fd2 GKH |
157 | if (retval) |
158 | goto error_gpio; | |
159 | ||
778c69c9 | 160 | retval = gb_sdio_probe(gmod, id); |
db6e1fd2 GKH |
161 | if (retval) |
162 | goto error_sdio; | |
163 | ||
778c69c9 | 164 | retval = gb_tty_probe(gmod, id); |
db6e1fd2 GKH |
165 | if (retval) |
166 | goto error_tty; | |
33ea3a3f | 167 | |
778c69c9 | 168 | retval = gb_battery_probe(gmod, id); |
33ea3a3f GKH |
169 | if (retval) |
170 | goto error_battery; | |
199d68d4 | 171 | return 0; |
db6e1fd2 | 172 | |
33ea3a3f | 173 | error_battery: |
778c69c9 | 174 | gb_tty_disconnect(gmod); |
33ea3a3f | 175 | |
db6e1fd2 | 176 | error_tty: |
778c69c9 | 177 | gb_sdio_disconnect(gmod); |
db6e1fd2 GKH |
178 | |
179 | error_sdio: | |
778c69c9 | 180 | gb_gpio_disconnect(gmod); |
db6e1fd2 GKH |
181 | |
182 | error_gpio: | |
778c69c9 | 183 | gb_i2c_disconnect(gmod); |
db6e1fd2 GKH |
184 | |
185 | error_i2c: | |
186 | return retval; | |
199d68d4 GKH |
187 | } |
188 | ||
e1e9dbdd | 189 | static const struct greybus_module_id fake_greybus_module_id = { |
3be03d42 GKH |
190 | GREYBUS_DEVICE(0x42, 0x42) |
191 | }; | |
a239f67c | 192 | |
b94295e0 | 193 | |
a239f67c | 194 | /** |
4a833fdb | 195 | * gb_add_module |
a239f67c | 196 | * |
05ad189c | 197 | * Pass in a buffer that _should_ contain a Greybus module manifest |
4a833fdb | 198 | * and register a greybus device structure with the kernel core. |
a239f67c | 199 | */ |
d0cfd109 GKH |
200 | void gb_add_module(struct greybus_host_device *hd, u8 module_id, |
201 | u8 *data, int size) | |
a239f67c | 202 | { |
e1e9dbdd | 203 | struct gb_module *gmod; |
a239f67c | 204 | int retval; |
a239f67c | 205 | |
b09c94a1 AE |
206 | /* |
207 | * Parse the manifest and build up our data structures | |
208 | * representing what's in it. | |
209 | */ | |
210 | gmod = gb_manifest_parse(data, size); | |
211 | if (!gmod) { | |
212 | dev_err(hd->parent, "manifest error\n"); | |
4a833fdb | 213 | return; |
b09c94a1 | 214 | } |
a239f67c | 215 | |
459164b1 AE |
216 | /* |
217 | * XXX | |
218 | * We've successfully parsed the manifest. Now we need to | |
219 | * allocate CPort Id's for connecting to the CPorts found on | |
220 | * other modules. For each of these, establish a connection | |
221 | * between the local and remote CPorts (including | |
222 | * configuring the switch to allow them to communicate). | |
223 | */ | |
224 | ||
778c69c9 AE |
225 | gmod->dev.parent = hd->parent; |
226 | gmod->dev.driver = NULL; | |
227 | gmod->dev.bus = &greybus_bus_type; | |
228 | gmod->dev.type = &greybus_module_type; | |
229 | gmod->dev.groups = greybus_module_groups; | |
230 | gmod->dev.dma_mask = hd->parent->dma_mask; | |
231 | device_initialize(&gmod->dev); | |
232 | dev_set_name(&gmod->dev, "%d", module_id); | |
a239f67c | 233 | |
32dff13d | 234 | retval = device_add(&gmod->dev); |
a239f67c GKH |
235 | if (retval) |
236 | goto error; | |
b94295e0 | 237 | |
32dff13d MP |
238 | retval = gb_init_subdevs(gmod, &fake_greybus_module_id); |
239 | if (retval) | |
240 | goto error_subdevs; | |
b94295e0 | 241 | |
778c69c9 | 242 | //return gmod; |
4a833fdb | 243 | return; |
32dff13d MP |
244 | |
245 | error_subdevs: | |
246 | device_del(&gmod->dev); | |
247 | ||
a239f67c | 248 | error: |
778c69c9 AE |
249 | put_device(&gmod->dev); |
250 | greybus_module_release(&gmod->dev); | |
a239f67c GKH |
251 | } |
252 | ||
6779997d GKH |
253 | void gb_remove_module(struct greybus_host_device *hd, u8 module_id) |
254 | { | |
d7f9be48 MP |
255 | struct gb_module *gmod; |
256 | bool found = false; | |
257 | ||
258 | list_for_each_entry(gmod, &hd->modules, links) | |
259 | if (gmod->module_id == module_id) { | |
260 | found = true; | |
261 | break; | |
262 | } | |
263 | ||
264 | if (found) | |
265 | greybus_remove_device(gmod); | |
266 | else | |
267 | dev_err(hd->parent, "module id %d remove error\n", module_id); | |
6779997d GKH |
268 | } |
269 | ||
e1e9dbdd | 270 | void greybus_remove_device(struct gb_module *gmod) |
db6e1fd2 GKH |
271 | { |
272 | /* tear down all of the "sub device types" for this device */ | |
778c69c9 AE |
273 | gb_i2c_disconnect(gmod); |
274 | gb_gpio_disconnect(gmod); | |
275 | gb_sdio_disconnect(gmod); | |
276 | gb_tty_disconnect(gmod); | |
277 | gb_battery_disconnect(gmod); | |
b94295e0 | 278 | |
d7f9be48 MP |
279 | device_del(&gmod->dev); |
280 | put_device(&gmod->dev); | |
db6e1fd2 | 281 | } |
199d68d4 | 282 | |
68f1fc4d GKH |
283 | static DEFINE_MUTEX(hd_mutex); |
284 | ||
1bb3c724 AE |
285 | /* |
286 | * Allocate an available CPort Id for use on the given host device. | |
287 | * Returns the CPort Id, or CPORT_ID_BAD of none remain. | |
288 | * | |
289 | * The lowest-available id is returned, so the first call is | |
290 | * guaranteed to allocate CPort Id 0. | |
291 | */ | |
292 | u16 greybus_hd_cport_id_alloc(struct greybus_host_device *hd) | |
293 | { | |
294 | unsigned long cport_id; | |
295 | ||
296 | /* If none left, return BAD */ | |
297 | if (hd->cport_id_count == HOST_DEV_CPORT_ID_MAX) | |
298 | return CPORT_ID_BAD; | |
299 | ||
300 | spin_lock_irq(&cport_id_map_lock); | |
301 | cport_id = find_next_zero_bit(hd->cport_id_map, hd->cport_id_count, | |
302 | hd->cport_id_next_free); | |
303 | if (cport_id < hd->cport_id_count) { | |
304 | hd->cport_id_next_free = cport_id + 1; /* Success */ | |
305 | hd->cport_id_count++; | |
306 | } else { | |
307 | /* Lost a race for the last one */ | |
308 | if (hd->cport_id_count != HOST_DEV_CPORT_ID_MAX) { | |
309 | pr_err("bad cport_id_count in alloc"); | |
310 | hd->cport_id_count = HOST_DEV_CPORT_ID_MAX; | |
311 | } | |
312 | cport_id = CPORT_ID_BAD; | |
313 | } | |
314 | spin_unlock_irq(&cport_id_map_lock); | |
315 | ||
316 | return cport_id; | |
317 | } | |
318 | ||
319 | /* | |
320 | * Free a previously-allocated CPort Id on the given host device. | |
321 | */ | |
322 | void greybus_hd_cport_id_free(struct greybus_host_device *hd, u16 cport_id) | |
323 | { | |
324 | if (cport_id >= HOST_DEV_CPORT_ID_MAX) { | |
325 | pr_err("bad cport_id %hu\n", cport_id); | |
326 | return; | |
327 | } | |
328 | if (!hd->cport_id_count) { | |
329 | pr_err("too many cport_id frees\n"); | |
330 | return; | |
331 | } | |
332 | ||
333 | spin_lock_irq(&cport_id_map_lock); | |
334 | if (test_and_clear_bit(cport_id, hd->cport_id_map)) { | |
335 | if (hd->cport_id_count) { | |
336 | hd->cport_id_count--; | |
337 | if (cport_id < hd->cport_id_next_free) | |
338 | hd->cport_id_next_free = cport_id; | |
339 | } else { | |
340 | pr_err("bad cport_id_count in free"); | |
341 | } | |
342 | } else { | |
343 | pr_err("duplicate cport_id %hu free\n", cport_id); | |
344 | } | |
345 | spin_unlock_irq(&cport_id_map_lock); | |
346 | } | |
347 | ||
68f1fc4d GKH |
348 | static void free_hd(struct kref *kref) |
349 | { | |
350 | struct greybus_host_device *hd; | |
351 | ||
352 | hd = container_of(kref, struct greybus_host_device, kref); | |
353 | ||
354 | kfree(hd); | |
355 | } | |
356 | ||
a39879fc GKH |
357 | struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver, |
358 | struct device *parent) | |
359 | { | |
360 | struct greybus_host_device *hd; | |
361 | ||
362 | hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL); | |
363 | if (!hd) | |
364 | return NULL; | |
365 | ||
366 | kref_init(&hd->kref); | |
772149b6 GKH |
367 | hd->parent = parent; |
368 | hd->driver = driver; | |
e1e9dbdd | 369 | INIT_LIST_HEAD(&hd->modules); |
a39879fc | 370 | |
1bb3c724 AE |
371 | /* Pre-allocate CPort 0 for control stuff. XXX */ |
372 | if (greybus_hd_cport_id_alloc(hd) != 0) { | |
373 | pr_err("couldn't allocate cport 0\n"); | |
374 | kfree(hd); | |
375 | return NULL; | |
376 | } | |
377 | ||
a39879fc GKH |
378 | return hd; |
379 | } | |
380 | EXPORT_SYMBOL_GPL(greybus_create_hd); | |
381 | ||
68f1fc4d GKH |
382 | void greybus_remove_hd(struct greybus_host_device *hd) |
383 | { | |
384 | kref_put_mutex(&hd->kref, free_hd, &hd_mutex); | |
385 | } | |
386 | EXPORT_SYMBOL_GPL(greybus_remove_hd); | |
387 | ||
a39879fc | 388 | |
503c1cdb | 389 | static int __init gb_init(void) |
199d68d4 | 390 | { |
db6e1fd2 GKH |
391 | int retval; |
392 | ||
1bb3c724 AE |
393 | BUILD_BUG_ON(HOST_DEV_CPORT_ID_MAX >= (long)CPORT_ID_BAD); |
394 | spin_lock_init(&cport_id_map_lock); | |
395 | ||
de536e30 | 396 | retval = gb_debugfs_init(); |
168db1cd GKH |
397 | if (retval) { |
398 | pr_err("debugfs failed\n"); | |
db6e1fd2 | 399 | return retval; |
168db1cd | 400 | } |
db6e1fd2 | 401 | |
27fb8310 | 402 | retval = bus_register(&greybus_bus_type); |
168db1cd GKH |
403 | if (retval) { |
404 | pr_err("bus_register failed\n"); | |
27fb8310 | 405 | goto error_bus; |
168db1cd | 406 | } |
27fb8310 | 407 | |
45f3678b | 408 | retval = gb_ap_init(); |
168db1cd | 409 | if (retval) { |
45f3678b GKH |
410 | pr_err("gb_ap_init failed\n"); |
411 | goto error_ap; | |
168db1cd | 412 | } |
de536e30 | 413 | |
45f3678b GKH |
414 | retval = gb_gbuf_init(); |
415 | if (retval) { | |
416 | pr_err("gb_gbuf_init failed\n"); | |
417 | goto error_gbuf; | |
418 | } | |
27fb8310 GKH |
419 | |
420 | retval = gb_tty_init(); | |
168db1cd GKH |
421 | if (retval) { |
422 | pr_err("gb_tty_init failed\n"); | |
27fb8310 | 423 | goto error_tty; |
168db1cd | 424 | } |
27fb8310 | 425 | |
199d68d4 | 426 | return 0; |
27fb8310 GKH |
427 | |
428 | error_tty: | |
45f3678b GKH |
429 | gb_gbuf_exit(); |
430 | ||
431 | error_gbuf: | |
432 | gb_ap_exit(); | |
de536e30 | 433 | |
45f3678b | 434 | error_ap: |
27fb8310 GKH |
435 | bus_unregister(&greybus_bus_type); |
436 | ||
437 | error_bus: | |
de536e30 | 438 | gb_debugfs_cleanup(); |
27fb8310 GKH |
439 | |
440 | return retval; | |
199d68d4 GKH |
441 | } |
442 | ||
503c1cdb | 443 | static void __exit gb_exit(void) |
199d68d4 | 444 | { |
db6e1fd2 | 445 | gb_tty_exit(); |
45f3678b GKH |
446 | gb_gbuf_exit(); |
447 | gb_ap_exit(); | |
27fb8310 | 448 | bus_unregister(&greybus_bus_type); |
de536e30 | 449 | gb_debugfs_cleanup(); |
199d68d4 GKH |
450 | } |
451 | ||
452 | module_init(gb_init); | |
453 | module_exit(gb_exit); | |
454 | ||
c8a797a9 GKH |
455 | MODULE_LICENSE("GPL"); |
456 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>"); |