]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/staging/greybus/core.c
greybus: implement core module removal path
[mirror_ubuntu-artful-kernel.git] / drivers / staging / greybus / core.c
CommitLineData
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 */
21static bool nogreybus;
22#ifdef MODULE
23module_param(nogreybus, bool, 0444);
24#else
25core_param(nogreybus, bool, 0444);
26#endif
27int greybus_disabled(void)
28{
29 return nogreybus;
30}
31EXPORT_SYMBOL_GPL(greybus_disabled);
32
1bb3c724
AE
33static spinlock_t cport_id_map_lock;
34
778c69c9 35static 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
48static 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 56static struct bus_type greybus_bus_type = {
c8a797a9 57 .name = "greybus",
778c69c9 58 .match = greybus_module_match,
c8a797a9
GKH
59 .uevent = greybus_uevent,
60};
61
62static 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
81static 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
90int 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}
111EXPORT_SYMBOL_GPL(greybus_register_driver);
112
113void greybus_deregister(struct greybus_driver *driver)
114{
115 driver_unregister(&driver->driver);
116}
117EXPORT_SYMBOL_GPL(greybus_deregister);
118
199d68d4 119
b94295e0
GKH
120static 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
131static 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 140static 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 173error_battery:
778c69c9 174 gb_tty_disconnect(gmod);
33ea3a3f 175
db6e1fd2 176error_tty:
778c69c9 177 gb_sdio_disconnect(gmod);
db6e1fd2
GKH
178
179error_sdio:
778c69c9 180 gb_gpio_disconnect(gmod);
db6e1fd2
GKH
181
182error_gpio:
778c69c9 183 gb_i2c_disconnect(gmod);
db6e1fd2
GKH
184
185error_i2c:
186 return retval;
199d68d4
GKH
187}
188
e1e9dbdd 189static 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
200void 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
245error_subdevs:
246 device_del(&gmod->dev);
247
a239f67c 248error:
778c69c9
AE
249 put_device(&gmod->dev);
250 greybus_module_release(&gmod->dev);
a239f67c
GKH
251}
252
6779997d
GKH
253void 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 270void 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
283static 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 */
292u16 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 */
322void 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
348static 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
357struct 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}
380EXPORT_SYMBOL_GPL(greybus_create_hd);
381
68f1fc4d
GKH
382void greybus_remove_hd(struct greybus_host_device *hd)
383{
384 kref_put_mutex(&hd->kref, free_hd, &hd_mutex);
385}
386EXPORT_SYMBOL_GPL(greybus_remove_hd);
387
a39879fc 388
503c1cdb 389static 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
428error_tty:
45f3678b
GKH
429 gb_gbuf_exit();
430
431error_gbuf:
432 gb_ap_exit();
de536e30 433
45f3678b 434error_ap:
27fb8310
GKH
435 bus_unregister(&greybus_bus_type);
436
437error_bus:
de536e30 438 gb_debugfs_cleanup();
27fb8310
GKH
439
440 return retval;
199d68d4
GKH
441}
442
503c1cdb 443static 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
452module_init(gb_init);
453module_exit(gb_exit);
454
c8a797a9
GKH
455MODULE_LICENSE("GPL");
456MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");