Commit | Line | Data |
---|---|---|
c8a797a9 GKH |
1 | /* |
2 | * Greybus "Core" | |
3 | * | |
4 | * Copyright 2014 Google Inc. | |
a46e9671 | 5 | * Copyright 2014 Linaro Ltd. |
c8a797a9 GKH |
6 | * |
7 | * Released under the GPLv2 only. | |
8 | */ | |
9 | ||
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
11 | ||
12 | #include <linux/types.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/moduleparam.h> | |
15 | #include <linux/kernel.h> | |
a239f67c | 16 | #include <linux/slab.h> |
c8a797a9 GKH |
17 | #include <linux/device.h> |
18 | ||
19 | #include "greybus.h" | |
20 | ||
21 | /* Allow greybus to be disabled at boot if needed */ | |
22 | static bool nogreybus; | |
23 | #ifdef MODULE | |
24 | module_param(nogreybus, bool, 0444); | |
25 | #else | |
8597e6b2 | 26 | core_param(nogreybus, nogreybus, bool, 0444); |
c8a797a9 GKH |
27 | #endif |
28 | int greybus_disabled(void) | |
29 | { | |
30 | return nogreybus; | |
31 | } | |
32 | EXPORT_SYMBOL_GPL(greybus_disabled); | |
33 | ||
778c69c9 | 34 | static int greybus_module_match(struct device *dev, struct device_driver *drv) |
c8a797a9 | 35 | { |
95bd99de | 36 | struct greybus_driver *driver = to_greybus_driver(drv); |
4ec7b079 | 37 | struct gb_interface_block *gb_ib = to_gb_interface_block(dev); |
2f0c8aa4 | 38 | const struct greybus_interface_block_id *id; |
c8a797a9 | 39 | |
4ec7b079 | 40 | id = gb_ib_match_id(gb_ib, driver->id_table); |
c8a797a9 GKH |
41 | if (id) |
42 | return 1; | |
696e0cca | 43 | /* FIXME - Dynamic ids? */ |
c8a797a9 GKH |
44 | return 0; |
45 | } | |
46 | ||
47 | static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env) | |
48 | { | |
4ec7b079 | 49 | struct gb_interface_block *gb_ib = NULL; |
1db0a5ff | 50 | struct gb_bundle *bundle = NULL; |
0ac5a838 GKH |
51 | struct gb_connection *connection = NULL; |
52 | ||
4ec7b079 GKH |
53 | if (is_gb_interface_block(dev)) { |
54 | gb_ib = to_gb_interface_block(dev); | |
1db0a5ff GKH |
55 | } else if (is_gb_bundle(dev)) { |
56 | bundle = to_gb_bundle(dev); | |
57 | gb_ib = bundle->gb_ib; | |
0ac5a838 GKH |
58 | } else if (is_gb_connection(dev)) { |
59 | connection = to_gb_connection(dev); | |
1db0a5ff GKH |
60 | bundle = connection->bundle; |
61 | gb_ib = bundle->gb_ib; | |
0ac5a838 GKH |
62 | } else { |
63 | dev_WARN(dev, "uevent for unknown greybus device \"type\"!\n"); | |
64 | return -EINVAL; | |
65 | } | |
66 | ||
67 | if (connection) { | |
68 | // FIXME | |
69 | // add a uevent that can "load" a connection type | |
70 | return 0; | |
71 | } | |
72 | ||
1db0a5ff | 73 | if (bundle) { |
0ac5a838 | 74 | // FIXME |
1db0a5ff | 75 | // add a uevent that can "load" a bundle type |
0ac5a838 GKH |
76 | // This is what we need to bind a driver to so use the info |
77 | // in gmod here as well | |
78 | return 0; | |
79 | } | |
80 | ||
81 | // FIXME | |
82 | // "just" a module, be vague here, nothing binds to a module except | |
83 | // the greybus core, so there's not much, if anything, we need to | |
84 | // advertise. | |
c8a797a9 GKH |
85 | return 0; |
86 | } | |
87 | ||
f0f61b90 | 88 | struct bus_type greybus_bus_type = { |
c8a797a9 | 89 | .name = "greybus", |
778c69c9 | 90 | .match = greybus_module_match, |
c8a797a9 GKH |
91 | .uevent = greybus_uevent, |
92 | }; | |
93 | ||
94 | static int greybus_probe(struct device *dev) | |
95 | { | |
96 | struct greybus_driver *driver = to_greybus_driver(dev->driver); | |
4ec7b079 | 97 | struct gb_interface_block *gb_ib = to_gb_interface_block(dev); |
2f0c8aa4 | 98 | const struct greybus_interface_block_id *id; |
c8a797a9 GKH |
99 | int retval; |
100 | ||
101 | /* match id */ | |
4ec7b079 | 102 | id = gb_ib_match_id(gb_ib, driver->id_table); |
c8a797a9 GKH |
103 | if (!id) |
104 | return -ENODEV; | |
105 | ||
4ec7b079 | 106 | retval = driver->probe(gb_ib, id); |
c8a797a9 GKH |
107 | if (retval) |
108 | return retval; | |
109 | ||
110 | return 0; | |
111 | } | |
112 | ||
113 | static int greybus_remove(struct device *dev) | |
114 | { | |
115 | struct greybus_driver *driver = to_greybus_driver(dev->driver); | |
4ec7b079 | 116 | struct gb_interface_block *gb_ib = to_gb_interface_block(dev); |
c8a797a9 | 117 | |
4ec7b079 | 118 | driver->disconnect(gb_ib); |
c8a797a9 GKH |
119 | return 0; |
120 | } | |
121 | ||
122 | int greybus_register_driver(struct greybus_driver *driver, struct module *owner, | |
123 | const char *mod_name) | |
124 | { | |
125 | int retval; | |
126 | ||
127 | if (greybus_disabled()) | |
128 | return -ENODEV; | |
129 | ||
130 | driver->driver.name = driver->name; | |
131 | driver->driver.probe = greybus_probe; | |
132 | driver->driver.remove = greybus_remove; | |
133 | driver->driver.owner = owner; | |
134 | driver->driver.mod_name = mod_name; | |
135 | ||
136 | retval = driver_register(&driver->driver); | |
137 | if (retval) | |
138 | return retval; | |
139 | ||
140 | pr_info("registered new driver %s\n", driver->name); | |
141 | return 0; | |
142 | } | |
143 | EXPORT_SYMBOL_GPL(greybus_register_driver); | |
144 | ||
145 | void greybus_deregister(struct greybus_driver *driver) | |
146 | { | |
147 | driver_unregister(&driver->driver); | |
148 | } | |
149 | EXPORT_SYMBOL_GPL(greybus_deregister); | |
150 | ||
199d68d4 | 151 | |
68f1fc4d GKH |
152 | static DEFINE_MUTEX(hd_mutex); |
153 | ||
154 | static void free_hd(struct kref *kref) | |
155 | { | |
156 | struct greybus_host_device *hd; | |
157 | ||
158 | hd = container_of(kref, struct greybus_host_device, kref); | |
159 | ||
160 | kfree(hd); | |
a06df4b0 | 161 | mutex_unlock(&hd_mutex); |
68f1fc4d GKH |
162 | } |
163 | ||
a39879fc GKH |
164 | struct greybus_host_device *greybus_create_hd(struct greybus_host_driver *driver, |
165 | struct device *parent) | |
166 | { | |
167 | struct greybus_host_device *hd; | |
168 | ||
724b619d GKH |
169 | /* |
170 | * Validate that the driver implements all of the callbacks | |
171 | * so that we don't have to every time we make them. | |
172 | */ | |
8b337308 | 173 | if ((!driver->buffer_send) || (!driver->buffer_cancel) || |
a9163b2c | 174 | (!driver->submit_svc)) { |
724b619d GKH |
175 | pr_err("Must implement all greybus_host_driver callbacks!\n"); |
176 | return NULL; | |
177 | } | |
178 | ||
a39879fc GKH |
179 | hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL); |
180 | if (!hd) | |
181 | return NULL; | |
182 | ||
183 | kref_init(&hd->kref); | |
772149b6 GKH |
184 | hd->parent = parent; |
185 | hd->driver = driver; | |
e1e9dbdd | 186 | INIT_LIST_HEAD(&hd->modules); |
2c43ce49 | 187 | INIT_LIST_HEAD(&hd->connections); |
177404bd | 188 | ida_init(&hd->cport_id_map); |
1bb3c724 | 189 | |
a39879fc GKH |
190 | return hd; |
191 | } | |
192 | EXPORT_SYMBOL_GPL(greybus_create_hd); | |
193 | ||
68f1fc4d GKH |
194 | void greybus_remove_hd(struct greybus_host_device *hd) |
195 | { | |
f0f61b90 GKH |
196 | /* Tear down all modules that happen to be associated with this host |
197 | * controller */ | |
63e4a8ee | 198 | gb_remove_modules(hd); |
68f1fc4d GKH |
199 | kref_put_mutex(&hd->kref, free_hd, &hd_mutex); |
200 | } | |
201 | EXPORT_SYMBOL_GPL(greybus_remove_hd); | |
202 | ||
503c1cdb | 203 | static int __init gb_init(void) |
199d68d4 | 204 | { |
db6e1fd2 GKH |
205 | int retval; |
206 | ||
1bb3c724 | 207 | BUILD_BUG_ON(HOST_DEV_CPORT_ID_MAX >= (long)CPORT_ID_BAD); |
1bb3c724 | 208 | |
de536e30 | 209 | retval = gb_debugfs_init(); |
168db1cd GKH |
210 | if (retval) { |
211 | pr_err("debugfs failed\n"); | |
db6e1fd2 | 212 | return retval; |
168db1cd | 213 | } |
db6e1fd2 | 214 | |
27fb8310 | 215 | retval = bus_register(&greybus_bus_type); |
168db1cd GKH |
216 | if (retval) { |
217 | pr_err("bus_register failed\n"); | |
27fb8310 | 218 | goto error_bus; |
168db1cd | 219 | } |
27fb8310 | 220 | |
45f3678b | 221 | retval = gb_ap_init(); |
168db1cd | 222 | if (retval) { |
45f3678b GKH |
223 | pr_err("gb_ap_init failed\n"); |
224 | goto error_ap; | |
168db1cd | 225 | } |
de536e30 | 226 | |
2eb585f8 AE |
227 | retval = gb_operation_init(); |
228 | if (retval) { | |
229 | pr_err("gb_operation_init failed\n"); | |
230 | goto error_operation; | |
231 | } | |
232 | ||
19d03dec AE |
233 | if (!gb_protocol_init()) { |
234 | /* This only fails for duplicate protocol registration */ | |
235 | retval = -EEXIST; | |
236 | pr_err("gb_protocol_init failed\n"); | |
237 | goto error_protocol; | |
238 | } | |
27fb8310 | 239 | |
19d03dec AE |
240 | return 0; /* Success */ |
241 | ||
242 | error_protocol: | |
243 | gb_operation_exit(); | |
2eb585f8 | 244 | error_operation: |
45f3678b | 245 | gb_ap_exit(); |
45f3678b | 246 | error_ap: |
27fb8310 | 247 | bus_unregister(&greybus_bus_type); |
27fb8310 | 248 | error_bus: |
de536e30 | 249 | gb_debugfs_cleanup(); |
27fb8310 GKH |
250 | |
251 | return retval; | |
199d68d4 GKH |
252 | } |
253 | ||
503c1cdb | 254 | static void __exit gb_exit(void) |
199d68d4 | 255 | { |
35a52caf | 256 | gb_protocol_exit(); |
2eb585f8 | 257 | gb_operation_exit(); |
45f3678b | 258 | gb_ap_exit(); |
27fb8310 | 259 | bus_unregister(&greybus_bus_type); |
de536e30 | 260 | gb_debugfs_cleanup(); |
199d68d4 GKH |
261 | } |
262 | ||
263 | module_init(gb_init); | |
264 | module_exit(gb_exit); | |
265 | ||
c8a797a9 GKH |
266 | MODULE_LICENSE("GPL"); |
267 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>"); |