Commit | Line | Data |
---|---|---|
30c6d9d7 AE |
1 | /* |
2 | * SVC Greybus driver. | |
3 | * | |
4 | * Copyright 2015 Google Inc. | |
5 | * Copyright 2015 Linaro Ltd. | |
6 | * | |
7 | * Released under the GPLv2 only. | |
8 | */ | |
9 | ||
ebe99d61 | 10 | #include <linux/input.h> |
067906f6 | 11 | #include <linux/workqueue.h> |
30c6d9d7 | 12 | |
f66427ad VK |
13 | #include "greybus.h" |
14 | ||
f6c6c138 JH |
15 | #define CPORT_FLAGS_E2EFC BIT(0) |
16 | #define CPORT_FLAGS_CSD_N BIT(1) | |
17 | #define CPORT_FLAGS_CSV_N BIT(2) | |
0b226497 | 18 | |
ebe99d61 | 19 | #define SVC_KEY_ARA_BUTTON KEY_A |
b45864d4 | 20 | |
9ae4109e | 21 | struct gb_svc_deferred_request { |
067906f6 | 22 | struct work_struct work; |
9ae4109e | 23 | struct gb_operation *operation; |
067906f6 VK |
24 | }; |
25 | ||
ead35460 | 26 | |
66069fb0 JH |
27 | static ssize_t endo_id_show(struct device *dev, |
28 | struct device_attribute *attr, char *buf) | |
29 | { | |
30 | struct gb_svc *svc = to_gb_svc(dev); | |
31 | ||
32 | return sprintf(buf, "0x%04x\n", svc->endo_id); | |
33 | } | |
34 | static DEVICE_ATTR_RO(endo_id); | |
35 | ||
36 | static ssize_t ap_intf_id_show(struct device *dev, | |
37 | struct device_attribute *attr, char *buf) | |
38 | { | |
39 | struct gb_svc *svc = to_gb_svc(dev); | |
40 | ||
41 | return sprintf(buf, "%u\n", svc->ap_intf_id); | |
42 | } | |
43 | static DEVICE_ATTR_RO(ap_intf_id); | |
44 | ||
2c92bd52 RMS |
45 | |
46 | // FIXME | |
47 | // This is a hack, we need to do this "right" and clean the interface up | |
48 | // properly, not just forcibly yank the thing out of the system and hope for the | |
49 | // best. But for now, people want their modules to come out without having to | |
50 | // throw the thing to the ground or get out a screwdriver. | |
51 | static ssize_t intf_eject_store(struct device *dev, | |
52 | struct device_attribute *attr, const char *buf, | |
53 | size_t len) | |
54 | { | |
55 | struct gb_svc *svc = to_gb_svc(dev); | |
56 | unsigned short intf_id; | |
57 | int ret; | |
58 | ||
59 | ret = kstrtou16(buf, 10, &intf_id); | |
60 | if (ret < 0) | |
61 | return ret; | |
62 | ||
63 | dev_warn(dev, "Forcibly trying to eject interface %d\n", intf_id); | |
64 | ||
65 | ret = gb_svc_intf_eject(svc, intf_id); | |
66 | if (ret < 0) | |
67 | return ret; | |
68 | ||
69 | return len; | |
70 | } | |
71 | static DEVICE_ATTR_WO(intf_eject); | |
72 | ||
d562853d GKH |
73 | static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr, |
74 | char *buf) | |
75 | { | |
76 | struct gb_svc *svc = to_gb_svc(dev); | |
77 | ||
78 | return sprintf(buf, "%s\n", | |
79 | gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled"); | |
80 | } | |
81 | ||
82 | static ssize_t watchdog_store(struct device *dev, | |
83 | struct device_attribute *attr, const char *buf, | |
84 | size_t len) | |
85 | { | |
86 | struct gb_svc *svc = to_gb_svc(dev); | |
87 | int retval; | |
88 | bool user_request; | |
89 | ||
90 | retval = strtobool(buf, &user_request); | |
91 | if (retval) | |
92 | return retval; | |
93 | ||
94 | if (user_request) | |
95 | retval = gb_svc_watchdog_enable(svc); | |
96 | else | |
97 | retval = gb_svc_watchdog_disable(svc); | |
98 | if (retval) | |
99 | return retval; | |
100 | return len; | |
101 | } | |
102 | static DEVICE_ATTR_RW(watchdog); | |
103 | ||
66069fb0 JH |
104 | static struct attribute *svc_attrs[] = { |
105 | &dev_attr_endo_id.attr, | |
106 | &dev_attr_ap_intf_id.attr, | |
2c92bd52 | 107 | &dev_attr_intf_eject.attr, |
d562853d | 108 | &dev_attr_watchdog.attr, |
66069fb0 JH |
109 | NULL, |
110 | }; | |
111 | ATTRIBUTE_GROUPS(svc); | |
112 | ||
505f16cc | 113 | static int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id) |
30c6d9d7 AE |
114 | { |
115 | struct gb_svc_intf_device_id_request request; | |
116 | ||
117 | request.intf_id = intf_id; | |
118 | request.device_id = device_id; | |
119 | ||
120 | return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_DEVICE_ID, | |
121 | &request, sizeof(request), NULL, 0); | |
122 | } | |
123 | ||
3f0e9183 | 124 | int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id) |
30c6d9d7 AE |
125 | { |
126 | struct gb_svc_intf_reset_request request; | |
127 | ||
128 | request.intf_id = intf_id; | |
129 | ||
130 | return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_RESET, | |
131 | &request, sizeof(request), NULL, 0); | |
132 | } | |
3f0e9183 | 133 | EXPORT_SYMBOL_GPL(gb_svc_intf_reset); |
30c6d9d7 | 134 | |
c5d55fb3 RMS |
135 | int gb_svc_intf_eject(struct gb_svc *svc, u8 intf_id) |
136 | { | |
137 | struct gb_svc_intf_eject_request request; | |
138 | ||
139 | request.intf_id = intf_id; | |
140 | ||
141 | /* | |
142 | * The pulse width for module release in svc is long so we need to | |
143 | * increase the timeout so the operation will not return to soon. | |
144 | */ | |
145 | return gb_operation_sync_timeout(svc->connection, | |
146 | GB_SVC_TYPE_INTF_EJECT, &request, | |
147 | sizeof(request), NULL, 0, | |
148 | GB_SVC_EJECT_TIME); | |
149 | } | |
150 | EXPORT_SYMBOL_GPL(gb_svc_intf_eject); | |
151 | ||
19151c3d VK |
152 | int gb_svc_dme_peer_get(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector, |
153 | u32 *value) | |
154 | { | |
155 | struct gb_svc_dme_peer_get_request request; | |
156 | struct gb_svc_dme_peer_get_response response; | |
157 | u16 result; | |
158 | int ret; | |
159 | ||
160 | request.intf_id = intf_id; | |
161 | request.attr = cpu_to_le16(attr); | |
162 | request.selector = cpu_to_le16(selector); | |
163 | ||
164 | ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_GET, | |
165 | &request, sizeof(request), | |
166 | &response, sizeof(response)); | |
167 | if (ret) { | |
b933fa4a | 168 | dev_err(&svc->dev, "failed to get DME attribute (%u 0x%04x %u): %d\n", |
684156a9 | 169 | intf_id, attr, selector, ret); |
19151c3d VK |
170 | return ret; |
171 | } | |
172 | ||
173 | result = le16_to_cpu(response.result_code); | |
174 | if (result) { | |
b933fa4a | 175 | dev_err(&svc->dev, "UniPro error while getting DME attribute (%u 0x%04x %u): %u\n", |
684156a9 | 176 | intf_id, attr, selector, result); |
4aac6c5a | 177 | return -EIO; |
19151c3d VK |
178 | } |
179 | ||
180 | if (value) | |
181 | *value = le32_to_cpu(response.attr_value); | |
182 | ||
183 | return 0; | |
184 | } | |
185 | EXPORT_SYMBOL_GPL(gb_svc_dme_peer_get); | |
186 | ||
187 | int gb_svc_dme_peer_set(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector, | |
188 | u32 value) | |
189 | { | |
190 | struct gb_svc_dme_peer_set_request request; | |
191 | struct gb_svc_dme_peer_set_response response; | |
192 | u16 result; | |
193 | int ret; | |
194 | ||
195 | request.intf_id = intf_id; | |
196 | request.attr = cpu_to_le16(attr); | |
197 | request.selector = cpu_to_le16(selector); | |
198 | request.value = cpu_to_le32(value); | |
199 | ||
200 | ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_SET, | |
201 | &request, sizeof(request), | |
202 | &response, sizeof(response)); | |
203 | if (ret) { | |
b933fa4a | 204 | dev_err(&svc->dev, "failed to set DME attribute (%u 0x%04x %u %u): %d\n", |
684156a9 | 205 | intf_id, attr, selector, value, ret); |
19151c3d VK |
206 | return ret; |
207 | } | |
208 | ||
209 | result = le16_to_cpu(response.result_code); | |
210 | if (result) { | |
b933fa4a | 211 | dev_err(&svc->dev, "UniPro error while setting DME attribute (%u 0x%04x %u %u): %u\n", |
684156a9 | 212 | intf_id, attr, selector, value, result); |
4aac6c5a | 213 | return -EIO; |
19151c3d VK |
214 | } |
215 | ||
216 | return 0; | |
217 | } | |
218 | EXPORT_SYMBOL_GPL(gb_svc_dme_peer_set); | |
219 | ||
6bec5c78 VK |
220 | /* |
221 | * T_TstSrcIncrement is written by the module on ES2 as a stand-in for boot | |
3563ff88 ES |
222 | * status attribute ES3_INIT_STATUS. AP needs to read and clear it, after |
223 | * reading a non-zero value from it. | |
6bec5c78 VK |
224 | * |
225 | * FIXME: This is module-hardware dependent and needs to be extended for every | |
226 | * type of module we want to support. | |
227 | */ | |
228 | static int gb_svc_read_and_clear_module_boot_status(struct gb_interface *intf) | |
229 | { | |
2537636a | 230 | struct gb_host_device *hd = intf->hd; |
6bec5c78 VK |
231 | int ret; |
232 | u32 value; | |
3563ff88 ES |
233 | u16 attr; |
234 | u8 init_status; | |
6bec5c78 | 235 | |
3563ff88 ES |
236 | /* |
237 | * Check if the module is ES2 or ES3, and choose attr number | |
238 | * appropriately. | |
239 | * FIXME: Remove ES2 support from the kernel entirely. | |
240 | */ | |
241 | if (intf->ddbl1_manufacturer_id == ES2_DDBL1_MFR_ID && | |
242 | intf->ddbl1_product_id == ES2_DDBL1_PROD_ID) | |
243 | attr = DME_ATTR_T_TST_SRC_INCREMENT; | |
244 | else | |
245 | attr = DME_ATTR_ES3_INIT_STATUS; | |
246 | ||
247 | /* Read and clear boot status in ES3_INIT_STATUS */ | |
248 | ret = gb_svc_dme_peer_get(hd->svc, intf->interface_id, attr, | |
6bec5c78 VK |
249 | DME_ATTR_SELECTOR_INDEX, &value); |
250 | ||
251 | if (ret) | |
252 | return ret; | |
253 | ||
254 | /* | |
255 | * A nonzero boot status indicates the module has finished | |
256 | * booting. Clear it. | |
257 | */ | |
258 | if (!value) { | |
259 | dev_err(&intf->dev, "Module not ready yet\n"); | |
260 | return -ENODEV; | |
261 | } | |
262 | ||
1575ef18 | 263 | /* |
3563ff88 | 264 | * Check if the module needs to boot from UniPro. |
1575ef18 VK |
265 | * For ES2: We need to check lowest 8 bits of 'value'. |
266 | * For ES3: We need to check highest 8 bits out of 32 of 'value'. | |
3563ff88 | 267 | * FIXME: Remove ES2 support from the kernel entirely. |
1575ef18 | 268 | */ |
3563ff88 ES |
269 | if (intf->ddbl1_manufacturer_id == ES2_DDBL1_MFR_ID && |
270 | intf->ddbl1_product_id == ES2_DDBL1_PROD_ID) | |
271 | init_status = value; | |
272 | else | |
273 | init_status = value >> 24; | |
274 | ||
275 | if (init_status == DME_DIS_UNIPRO_BOOT_STARTED || | |
276 | init_status == DME_DIS_FALLBACK_UNIPRO_BOOT_STARTED) | |
1575ef18 VK |
277 | intf->boot_over_unipro = true; |
278 | ||
3563ff88 | 279 | return gb_svc_dme_peer_set(hd->svc, intf->interface_id, attr, |
6bec5c78 VK |
280 | DME_ATTR_SELECTOR_INDEX, 0); |
281 | } | |
282 | ||
3f0e9183 | 283 | int gb_svc_connection_create(struct gb_svc *svc, |
30c6d9d7 | 284 | u8 intf1_id, u16 cport1_id, |
1575ef18 VK |
285 | u8 intf2_id, u16 cport2_id, |
286 | bool boot_over_unipro) | |
30c6d9d7 AE |
287 | { |
288 | struct gb_svc_conn_create_request request; | |
289 | ||
290 | request.intf1_id = intf1_id; | |
2498050b | 291 | request.cport1_id = cpu_to_le16(cport1_id); |
30c6d9d7 | 292 | request.intf2_id = intf2_id; |
2498050b | 293 | request.cport2_id = cpu_to_le16(cport2_id); |
0b226497 PH |
294 | /* |
295 | * XXX: fix connections paramaters to TC0 and all CPort flags | |
296 | * for now. | |
297 | */ | |
298 | request.tc = 0; | |
1575ef18 VK |
299 | |
300 | /* | |
301 | * We need to skip setting E2EFC and other flags to the connection | |
302 | * create request, for all cports, on an interface that need to boot | |
303 | * over unipro, i.e. interfaces required to download firmware. | |
304 | */ | |
305 | if (boot_over_unipro) | |
306 | request.flags = CPORT_FLAGS_CSV_N | CPORT_FLAGS_CSD_N; | |
307 | else | |
308 | request.flags = CPORT_FLAGS_CSV_N | CPORT_FLAGS_E2EFC; | |
30c6d9d7 AE |
309 | |
310 | return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_CREATE, | |
311 | &request, sizeof(request), NULL, 0); | |
312 | } | |
3f0e9183 | 313 | EXPORT_SYMBOL_GPL(gb_svc_connection_create); |
30c6d9d7 | 314 | |
3f0e9183 VK |
315 | void gb_svc_connection_destroy(struct gb_svc *svc, u8 intf1_id, u16 cport1_id, |
316 | u8 intf2_id, u16 cport2_id) | |
30c6d9d7 AE |
317 | { |
318 | struct gb_svc_conn_destroy_request request; | |
d9fcffff VK |
319 | struct gb_connection *connection = svc->connection; |
320 | int ret; | |
30c6d9d7 AE |
321 | |
322 | request.intf1_id = intf1_id; | |
2498050b | 323 | request.cport1_id = cpu_to_le16(cport1_id); |
30c6d9d7 | 324 | request.intf2_id = intf2_id; |
2498050b | 325 | request.cport2_id = cpu_to_le16(cport2_id); |
30c6d9d7 | 326 | |
d9fcffff VK |
327 | ret = gb_operation_sync(connection, GB_SVC_TYPE_CONN_DESTROY, |
328 | &request, sizeof(request), NULL, 0); | |
684156a9 | 329 | if (ret) { |
2f3db927 | 330 | dev_err(&svc->dev, "failed to destroy connection (%u:%u %u:%u): %d\n", |
684156a9 JH |
331 | intf1_id, cport1_id, intf2_id, cport2_id, ret); |
332 | } | |
30c6d9d7 | 333 | } |
3f0e9183 | 334 | EXPORT_SYMBOL_GPL(gb_svc_connection_destroy); |
30c6d9d7 | 335 | |
bb106852 | 336 | /* Creates bi-directional routes between the devices */ |
505f16cc VK |
337 | static int gb_svc_route_create(struct gb_svc *svc, u8 intf1_id, u8 dev1_id, |
338 | u8 intf2_id, u8 dev2_id) | |
e08aaa49 PH |
339 | { |
340 | struct gb_svc_route_create_request request; | |
341 | ||
342 | request.intf1_id = intf1_id; | |
343 | request.dev1_id = dev1_id; | |
344 | request.intf2_id = intf2_id; | |
345 | request.dev2_id = dev2_id; | |
346 | ||
347 | return gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_CREATE, | |
348 | &request, sizeof(request), NULL, 0); | |
349 | } | |
e08aaa49 | 350 | |
0a020570 VK |
351 | /* Destroys bi-directional routes between the devices */ |
352 | static void gb_svc_route_destroy(struct gb_svc *svc, u8 intf1_id, u8 intf2_id) | |
353 | { | |
354 | struct gb_svc_route_destroy_request request; | |
355 | int ret; | |
356 | ||
357 | request.intf1_id = intf1_id; | |
358 | request.intf2_id = intf2_id; | |
359 | ||
360 | ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_DESTROY, | |
361 | &request, sizeof(request), NULL, 0); | |
684156a9 | 362 | if (ret) { |
2f3db927 | 363 | dev_err(&svc->dev, "failed to destroy route (%u %u): %d\n", |
684156a9 JH |
364 | intf1_id, intf2_id, ret); |
365 | } | |
0a020570 VK |
366 | } |
367 | ||
aab4a1a3 LP |
368 | int gb_svc_intf_set_power_mode(struct gb_svc *svc, u8 intf_id, u8 hs_series, |
369 | u8 tx_mode, u8 tx_gear, u8 tx_nlanes, | |
370 | u8 rx_mode, u8 rx_gear, u8 rx_nlanes, | |
371 | u8 flags, u32 quirks) | |
784f8761 | 372 | { |
aab4a1a3 LP |
373 | struct gb_svc_intf_set_pwrm_request request; |
374 | struct gb_svc_intf_set_pwrm_response response; | |
375 | int ret; | |
784f8761 LP |
376 | |
377 | request.intf_id = intf_id; | |
aab4a1a3 LP |
378 | request.hs_series = hs_series; |
379 | request.tx_mode = tx_mode; | |
380 | request.tx_gear = tx_gear; | |
381 | request.tx_nlanes = tx_nlanes; | |
382 | request.rx_mode = rx_mode; | |
383 | request.rx_gear = rx_gear; | |
384 | request.rx_nlanes = rx_nlanes; | |
784f8761 | 385 | request.flags = flags; |
aab4a1a3 | 386 | request.quirks = cpu_to_le32(quirks); |
784f8761 | 387 | |
aab4a1a3 LP |
388 | ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_SET_PWRM, |
389 | &request, sizeof(request), | |
390 | &response, sizeof(response)); | |
391 | if (ret < 0) | |
392 | return ret; | |
393 | ||
394 | return le16_to_cpu(response.result_code); | |
784f8761 | 395 | } |
aab4a1a3 | 396 | EXPORT_SYMBOL_GPL(gb_svc_intf_set_power_mode); |
784f8761 | 397 | |
55ec09e8 GKH |
398 | int gb_svc_ping(struct gb_svc *svc) |
399 | { | |
839ac5b9 GKH |
400 | return gb_operation_sync_timeout(svc->connection, GB_SVC_TYPE_PING, |
401 | NULL, 0, NULL, 0, | |
402 | GB_OPERATION_TIMEOUT_DEFAULT * 2); | |
55ec09e8 GKH |
403 | } |
404 | EXPORT_SYMBOL_GPL(gb_svc_ping); | |
405 | ||
ead35460 VK |
406 | static int gb_svc_version_request(struct gb_operation *op) |
407 | { | |
408 | struct gb_connection *connection = op->connection; | |
684156a9 | 409 | struct gb_svc *svc = connection->private; |
cfb16906 JH |
410 | struct gb_protocol_version_request *request; |
411 | struct gb_protocol_version_response *response; | |
ead35460 | 412 | |
55510843 | 413 | if (op->request->payload_size < sizeof(*request)) { |
684156a9 | 414 | dev_err(&svc->dev, "short version request (%zu < %zu)\n", |
55510843 JH |
415 | op->request->payload_size, |
416 | sizeof(*request)); | |
417 | return -EINVAL; | |
418 | } | |
419 | ||
cfb16906 | 420 | request = op->request->payload; |
ead35460 | 421 | |
cfb16906 | 422 | if (request->major > GB_SVC_VERSION_MAJOR) { |
2f3db927 | 423 | dev_warn(&svc->dev, "unsupported major version (%u > %u)\n", |
684156a9 | 424 | request->major, GB_SVC_VERSION_MAJOR); |
ead35460 VK |
425 | return -ENOTSUPP; |
426 | } | |
427 | ||
357de006 JH |
428 | svc->protocol_major = request->major; |
429 | svc->protocol_minor = request->minor; | |
3ea959e3 | 430 | |
684156a9 | 431 | if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL)) |
ead35460 | 432 | return -ENOMEM; |
ead35460 | 433 | |
cfb16906 | 434 | response = op->response->payload; |
357de006 JH |
435 | response->major = svc->protocol_major; |
436 | response->minor = svc->protocol_minor; | |
59832931 | 437 | |
ead35460 VK |
438 | return 0; |
439 | } | |
440 | ||
441 | static int gb_svc_hello(struct gb_operation *op) | |
442 | { | |
443 | struct gb_connection *connection = op->connection; | |
88f7b96d | 444 | struct gb_svc *svc = connection->private; |
ead35460 | 445 | struct gb_svc_hello_request *hello_request; |
ead35460 VK |
446 | int ret; |
447 | ||
0c32d2a5 | 448 | if (op->request->payload_size < sizeof(*hello_request)) { |
684156a9 JH |
449 | dev_warn(&svc->dev, "short hello request (%zu < %zu)\n", |
450 | op->request->payload_size, | |
451 | sizeof(*hello_request)); | |
ead35460 VK |
452 | return -EINVAL; |
453 | } | |
454 | ||
455 | hello_request = op->request->payload; | |
66069fb0 JH |
456 | svc->endo_id = le16_to_cpu(hello_request->endo_id); |
457 | svc->ap_intf_id = hello_request->interface_id; | |
ead35460 | 458 | |
88f7b96d JH |
459 | ret = device_add(&svc->dev); |
460 | if (ret) { | |
461 | dev_err(&svc->dev, "failed to register svc device: %d\n", ret); | |
462 | return ret; | |
463 | } | |
464 | ||
ebe99d61 RMS |
465 | ret = input_register_device(svc->input); |
466 | if (ret) { | |
467 | dev_err(&svc->dev, "failed to register input: %d\n", ret); | |
468 | device_del(&svc->dev); | |
469 | return ret; | |
470 | } | |
471 | ||
ed7279ae GKH |
472 | ret = gb_svc_watchdog_create(svc); |
473 | if (ret) { | |
474 | dev_err(&svc->dev, "failed to create watchdog: %d\n", ret); | |
475 | input_unregister_device(svc->input); | |
476 | device_del(&svc->dev); | |
539d6e11 | 477 | return ret; |
ed7279ae GKH |
478 | } |
479 | ||
ead35460 VK |
480 | return 0; |
481 | } | |
482 | ||
b4ee82ec | 483 | static void gb_svc_intf_remove(struct gb_svc *svc, struct gb_interface *intf) |
bbaca711 | 484 | { |
bbaca711 | 485 | u8 intf_id = intf->interface_id; |
141af4f0 JH |
486 | u8 device_id = intf->device_id; |
487 | ||
488 | intf->disconnected = true; | |
bbaca711 | 489 | |
80d1ede8 | 490 | gb_interface_remove(intf); |
bbaca711 VK |
491 | |
492 | /* | |
493 | * Destroy the two-way route between the AP and the interface. | |
494 | */ | |
66069fb0 | 495 | gb_svc_route_destroy(svc, svc->ap_intf_id, intf_id); |
bbaca711 VK |
496 | |
497 | ida_simple_remove(&svc->device_id_map, device_id); | |
498 | } | |
499 | ||
9ae4109e | 500 | static void gb_svc_process_intf_hotplug(struct gb_operation *operation) |
30c6d9d7 | 501 | { |
24456a09 | 502 | struct gb_svc_intf_hotplug_request *request; |
9ae4109e | 503 | struct gb_connection *connection = operation->connection; |
067906f6 | 504 | struct gb_svc *svc = connection->private; |
2537636a | 505 | struct gb_host_device *hd = connection->hd; |
ead35460 VK |
506 | struct gb_interface *intf; |
507 | u8 intf_id, device_id; | |
f3e6c097 VK |
508 | u32 vendor_id = 0; |
509 | u32 product_id = 0; | |
ead35460 | 510 | int ret; |
30c6d9d7 | 511 | |
24456a09 | 512 | /* The request message size has already been verified. */ |
9ae4109e | 513 | request = operation->request->payload; |
24456a09 JH |
514 | intf_id = request->intf_id; |
515 | ||
516 | dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id); | |
30c6d9d7 | 517 | |
bbaca711 VK |
518 | intf = gb_interface_find(hd, intf_id); |
519 | if (intf) { | |
f3e6c097 VK |
520 | /* |
521 | * For ES2, we need to maintain the same vendor/product ids we | |
522 | * got from bootrom, otherwise userspace can't distinguish | |
523 | * between modules. | |
524 | */ | |
525 | vendor_id = intf->vendor_id; | |
526 | product_id = intf->product_id; | |
527 | ||
bbaca711 VK |
528 | /* |
529 | * We have received a hotplug request for an interface that | |
530 | * already exists. | |
531 | * | |
532 | * This can happen in cases like: | |
533 | * - bootrom loading the firmware image and booting into that, | |
534 | * which only generates a hotplug event. i.e. no hot-unplug | |
535 | * event. | |
536 | * - Or the firmware on the module crashed and sent hotplug | |
537 | * request again to the SVC, which got propagated to AP. | |
538 | * | |
539 | * Remove the interface and add it again, and let user know | |
540 | * about this with a print message. | |
541 | */ | |
2f3db927 | 542 | dev_info(&svc->dev, "removing interface %u to add it again\n", |
684156a9 | 543 | intf_id); |
b4ee82ec | 544 | gb_svc_intf_remove(svc, intf); |
bbaca711 VK |
545 | } |
546 | ||
ead35460 VK |
547 | intf = gb_interface_create(hd, intf_id); |
548 | if (!intf) { | |
2f3db927 | 549 | dev_err(&svc->dev, "failed to create interface %u\n", |
684156a9 | 550 | intf_id); |
9ae4109e | 551 | return; |
ead35460 VK |
552 | } |
553 | ||
63d742b6 VK |
554 | intf->ddbl1_manufacturer_id = le32_to_cpu(request->data.ddbl1_mfr_id); |
555 | intf->ddbl1_product_id = le32_to_cpu(request->data.ddbl1_prod_id); | |
556 | intf->vendor_id = le32_to_cpu(request->data.ara_vend_id); | |
557 | intf->product_id = le32_to_cpu(request->data.ara_prod_id); | |
57c6bcc6 | 558 | intf->serial_number = le64_to_cpu(request->data.serial_number); |
63d742b6 | 559 | |
f3e6c097 VK |
560 | /* |
561 | * Use VID/PID specified at hotplug if: | |
562 | * - Bridge ASIC chip isn't ES2 | |
563 | * - Received non-zero Vendor/Product ids | |
564 | * | |
565 | * Otherwise, use the ids we received from bootrom. | |
566 | */ | |
567 | if (intf->ddbl1_manufacturer_id == ES2_DDBL1_MFR_ID && | |
568 | intf->ddbl1_product_id == ES2_DDBL1_PROD_ID && | |
569 | intf->vendor_id == 0 && intf->product_id == 0) { | |
570 | intf->vendor_id = vendor_id; | |
571 | intf->product_id = product_id; | |
572 | } | |
573 | ||
6bec5c78 | 574 | ret = gb_svc_read_and_clear_module_boot_status(intf); |
b395754a JH |
575 | if (ret) { |
576 | dev_err(&svc->dev, "failed to clear boot status of interface %u: %d\n", | |
577 | intf_id, ret); | |
6bec5c78 | 578 | goto destroy_interface; |
b395754a | 579 | } |
6bec5c78 | 580 | |
ead35460 VK |
581 | /* |
582 | * Create a device id for the interface: | |
583 | * - device id 0 (GB_DEVICE_ID_SVC) belongs to the SVC | |
584 | * - device id 1 (GB_DEVICE_ID_AP) belongs to the AP | |
585 | * | |
586 | * XXX Do we need to allocate device ID for SVC or the AP here? And what | |
587 | * XXX about an AP with multiple interface blocks? | |
588 | */ | |
c09db182 | 589 | device_id = ida_simple_get(&svc->device_id_map, |
89f637f7 | 590 | GB_DEVICE_ID_MODULES_START, 0, GFP_KERNEL); |
ead35460 VK |
591 | if (device_id < 0) { |
592 | ret = device_id; | |
2f3db927 | 593 | dev_err(&svc->dev, "failed to allocate device id for interface %u: %d\n", |
684156a9 | 594 | intf_id, ret); |
ead35460 VK |
595 | goto destroy_interface; |
596 | } | |
597 | ||
3f0e9183 | 598 | ret = gb_svc_intf_device_id(svc, intf_id, device_id); |
ead35460 | 599 | if (ret) { |
2f3db927 | 600 | dev_err(&svc->dev, "failed to set device id %u for interface %u: %d\n", |
684156a9 | 601 | device_id, intf_id, ret); |
ead35460 VK |
602 | goto ida_put; |
603 | } | |
604 | ||
7e275465 PH |
605 | /* |
606 | * Create a two-way route between the AP and the new interface | |
607 | */ | |
66069fb0 | 608 | ret = gb_svc_route_create(svc, svc->ap_intf_id, GB_DEVICE_ID_AP, |
3f0e9183 | 609 | intf_id, device_id); |
7e275465 | 610 | if (ret) { |
2f3db927 | 611 | dev_err(&svc->dev, "failed to create route to interface %u (device id %u): %d\n", |
684156a9 | 612 | intf_id, device_id, ret); |
0a020570 | 613 | goto svc_id_free; |
7e275465 PH |
614 | } |
615 | ||
ead35460 VK |
616 | ret = gb_interface_init(intf, device_id); |
617 | if (ret) { | |
2f3db927 | 618 | dev_err(&svc->dev, "failed to initialize interface %u (device id %u): %d\n", |
684156a9 | 619 | intf_id, device_id, ret); |
0a020570 | 620 | goto destroy_route; |
ead35460 | 621 | } |
30c6d9d7 | 622 | |
9ae4109e | 623 | return; |
ead35460 | 624 | |
0a020570 | 625 | destroy_route: |
66069fb0 | 626 | gb_svc_route_destroy(svc, svc->ap_intf_id, intf_id); |
ead35460 VK |
627 | svc_id_free: |
628 | /* | |
629 | * XXX Should we tell SVC that this id doesn't belong to interface | |
630 | * XXX anymore. | |
631 | */ | |
632 | ida_put: | |
c09db182 | 633 | ida_simple_remove(&svc->device_id_map, device_id); |
ead35460 | 634 | destroy_interface: |
80d1ede8 | 635 | gb_interface_remove(intf); |
9ae4109e JH |
636 | } |
637 | ||
57ccd4b0 JH |
638 | static void gb_svc_process_intf_hot_unplug(struct gb_operation *operation) |
639 | { | |
640 | struct gb_svc *svc = operation->connection->private; | |
641 | struct gb_svc_intf_hot_unplug_request *request; | |
642 | struct gb_host_device *hd = operation->connection->hd; | |
643 | struct gb_interface *intf; | |
644 | u8 intf_id; | |
645 | ||
646 | /* The request message size has already been verified. */ | |
647 | request = operation->request->payload; | |
648 | intf_id = request->intf_id; | |
649 | ||
650 | dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id); | |
651 | ||
652 | intf = gb_interface_find(hd, intf_id); | |
653 | if (!intf) { | |
2f3db927 | 654 | dev_warn(&svc->dev, "could not find hot-unplug interface %u\n", |
57ccd4b0 JH |
655 | intf_id); |
656 | return; | |
657 | } | |
658 | ||
659 | gb_svc_intf_remove(svc, intf); | |
660 | } | |
661 | ||
9ae4109e JH |
662 | static void gb_svc_process_deferred_request(struct work_struct *work) |
663 | { | |
664 | struct gb_svc_deferred_request *dr; | |
665 | struct gb_operation *operation; | |
666 | struct gb_svc *svc; | |
667 | u8 type; | |
668 | ||
669 | dr = container_of(work, struct gb_svc_deferred_request, work); | |
670 | operation = dr->operation; | |
671 | svc = operation->connection->private; | |
672 | type = operation->request->header->type; | |
673 | ||
674 | switch (type) { | |
675 | case GB_SVC_TYPE_INTF_HOTPLUG: | |
676 | gb_svc_process_intf_hotplug(operation); | |
677 | break; | |
57ccd4b0 JH |
678 | case GB_SVC_TYPE_INTF_HOT_UNPLUG: |
679 | gb_svc_process_intf_hot_unplug(operation); | |
680 | break; | |
9ae4109e | 681 | default: |
b933fa4a | 682 | dev_err(&svc->dev, "bad deferred request type: 0x%02x\n", type); |
9ae4109e JH |
683 | } |
684 | ||
685 | gb_operation_put(operation); | |
686 | kfree(dr); | |
687 | } | |
688 | ||
689 | static int gb_svc_queue_deferred_request(struct gb_operation *operation) | |
690 | { | |
3e48acac | 691 | struct gb_svc *svc = operation->connection->private; |
9ae4109e JH |
692 | struct gb_svc_deferred_request *dr; |
693 | ||
694 | dr = kmalloc(sizeof(*dr), GFP_KERNEL); | |
695 | if (!dr) | |
696 | return -ENOMEM; | |
697 | ||
698 | gb_operation_get(operation); | |
699 | ||
700 | dr->operation = operation; | |
701 | INIT_WORK(&dr->work, gb_svc_process_deferred_request); | |
702 | ||
3e48acac | 703 | queue_work(svc->wq, &dr->work); |
9ae4109e JH |
704 | |
705 | return 0; | |
067906f6 | 706 | } |
ead35460 | 707 | |
067906f6 VK |
708 | /* |
709 | * Bringing up a module can be time consuming, as that may require lots of | |
710 | * initialization on the module side. Over that, we may also need to download | |
711 | * the firmware first and flash that on the module. | |
712 | * | |
3e48acac | 713 | * In order not to make other svc events wait for all this to finish, |
067906f6 VK |
714 | * handle most of module hotplug stuff outside of the hotplug callback, with |
715 | * help of a workqueue. | |
716 | */ | |
717 | static int gb_svc_intf_hotplug_recv(struct gb_operation *op) | |
718 | { | |
684156a9 | 719 | struct gb_svc *svc = op->connection->private; |
d34a3643 | 720 | struct gb_svc_intf_hotplug_request *request; |
067906f6 | 721 | |
d34a3643 | 722 | if (op->request->payload_size < sizeof(*request)) { |
684156a9 | 723 | dev_warn(&svc->dev, "short hotplug request received (%zu < %zu)\n", |
d34a3643 | 724 | op->request->payload_size, sizeof(*request)); |
067906f6 VK |
725 | return -EINVAL; |
726 | } | |
727 | ||
d34a3643 JH |
728 | request = op->request->payload; |
729 | ||
730 | dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id); | |
731 | ||
9ae4109e | 732 | return gb_svc_queue_deferred_request(op); |
30c6d9d7 AE |
733 | } |
734 | ||
735 | static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op) | |
736 | { | |
684156a9 | 737 | struct gb_svc *svc = op->connection->private; |
d34a3643 | 738 | struct gb_svc_intf_hot_unplug_request *request; |
30c6d9d7 | 739 | |
d34a3643 | 740 | if (op->request->payload_size < sizeof(*request)) { |
684156a9 | 741 | dev_warn(&svc->dev, "short hot unplug request received (%zu < %zu)\n", |
d34a3643 | 742 | op->request->payload_size, sizeof(*request)); |
30c6d9d7 AE |
743 | return -EINVAL; |
744 | } | |
30c6d9d7 | 745 | |
d34a3643 | 746 | request = op->request->payload; |
30c6d9d7 | 747 | |
57ccd4b0 | 748 | dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id); |
30c6d9d7 | 749 | |
57ccd4b0 | 750 | return gb_svc_queue_deferred_request(op); |
30c6d9d7 AE |
751 | } |
752 | ||
753 | static int gb_svc_intf_reset_recv(struct gb_operation *op) | |
754 | { | |
684156a9 | 755 | struct gb_svc *svc = op->connection->private; |
30c6d9d7 AE |
756 | struct gb_message *request = op->request; |
757 | struct gb_svc_intf_reset_request *reset; | |
758 | u8 intf_id; | |
759 | ||
760 | if (request->payload_size < sizeof(*reset)) { | |
684156a9 JH |
761 | dev_warn(&svc->dev, "short reset request received (%zu < %zu)\n", |
762 | request->payload_size, sizeof(*reset)); | |
30c6d9d7 AE |
763 | return -EINVAL; |
764 | } | |
765 | reset = request->payload; | |
766 | ||
767 | intf_id = reset->intf_id; | |
768 | ||
769 | /* FIXME Reset the interface here */ | |
770 | ||
771 | return 0; | |
772 | } | |
773 | ||
ebe99d61 RMS |
774 | static int gb_svc_key_code_map(struct gb_svc *svc, u16 key_code, u16 *code) |
775 | { | |
776 | switch (key_code) { | |
777 | case GB_KEYCODE_ARA: | |
778 | *code = SVC_KEY_ARA_BUTTON; | |
779 | break; | |
780 | default: | |
781 | dev_warn(&svc->dev, "unknown keycode received: %u\n", key_code); | |
782 | return -EINVAL; | |
783 | } | |
784 | ||
785 | return 0; | |
786 | } | |
787 | ||
788 | static int gb_svc_key_event_recv(struct gb_operation *op) | |
789 | { | |
790 | struct gb_svc *svc = op->connection->private; | |
791 | struct gb_message *request = op->request; | |
792 | struct gb_svc_key_event_request *key; | |
793 | u16 code; | |
794 | u8 event; | |
795 | int ret; | |
796 | ||
797 | if (request->payload_size < sizeof(*key)) { | |
798 | dev_warn(&svc->dev, "short key request received (%zu < %zu)\n", | |
799 | request->payload_size, sizeof(*key)); | |
800 | return -EINVAL; | |
801 | } | |
802 | ||
803 | key = request->payload; | |
804 | ||
805 | ret = gb_svc_key_code_map(svc, le16_to_cpu(key->key_code), &code); | |
806 | if (ret < 0) | |
807 | return ret; | |
808 | ||
809 | event = key->key_event; | |
810 | if ((event != GB_SVC_KEY_PRESSED) && (event != GB_SVC_KEY_RELEASED)) { | |
811 | dev_warn(&svc->dev, "unknown key event received: %u\n", event); | |
812 | return -EINVAL; | |
813 | } | |
814 | ||
815 | input_report_key(svc->input, code, (event == GB_SVC_KEY_PRESSED)); | |
816 | input_sync(svc->input); | |
817 | ||
818 | return 0; | |
819 | } | |
820 | ||
84427943 | 821 | static int gb_svc_request_handler(struct gb_operation *op) |
30c6d9d7 | 822 | { |
3ccb1600 VK |
823 | struct gb_connection *connection = op->connection; |
824 | struct gb_svc *svc = connection->private; | |
84427943 | 825 | u8 type = op->type; |
3ccb1600 VK |
826 | int ret = 0; |
827 | ||
828 | /* | |
829 | * SVC requests need to follow a specific order (at least initially) and | |
830 | * below code takes care of enforcing that. The expected order is: | |
831 | * - PROTOCOL_VERSION | |
832 | * - SVC_HELLO | |
833 | * - Any other request, but the earlier two. | |
834 | * | |
835 | * Incoming requests are guaranteed to be serialized and so we don't | |
836 | * need to protect 'state' for any races. | |
837 | */ | |
30c6d9d7 | 838 | switch (type) { |
0e2462d1 | 839 | case GB_REQUEST_TYPE_PROTOCOL_VERSION: |
3ccb1600 VK |
840 | if (svc->state != GB_SVC_STATE_RESET) |
841 | ret = -EINVAL; | |
842 | break; | |
ead35460 | 843 | case GB_SVC_TYPE_SVC_HELLO: |
3ccb1600 VK |
844 | if (svc->state != GB_SVC_STATE_PROTOCOL_VERSION) |
845 | ret = -EINVAL; | |
846 | break; | |
847 | default: | |
848 | if (svc->state != GB_SVC_STATE_SVC_HELLO) | |
849 | ret = -EINVAL; | |
850 | break; | |
851 | } | |
852 | ||
853 | if (ret) { | |
684156a9 JH |
854 | dev_warn(&svc->dev, "unexpected request 0x%02x received (state %u)\n", |
855 | type, svc->state); | |
3ccb1600 VK |
856 | return ret; |
857 | } | |
858 | ||
859 | switch (type) { | |
860 | case GB_REQUEST_TYPE_PROTOCOL_VERSION: | |
861 | ret = gb_svc_version_request(op); | |
862 | if (!ret) | |
863 | svc->state = GB_SVC_STATE_PROTOCOL_VERSION; | |
864 | return ret; | |
865 | case GB_SVC_TYPE_SVC_HELLO: | |
866 | ret = gb_svc_hello(op); | |
867 | if (!ret) | |
868 | svc->state = GB_SVC_STATE_SVC_HELLO; | |
869 | return ret; | |
30c6d9d7 AE |
870 | case GB_SVC_TYPE_INTF_HOTPLUG: |
871 | return gb_svc_intf_hotplug_recv(op); | |
872 | case GB_SVC_TYPE_INTF_HOT_UNPLUG: | |
873 | return gb_svc_intf_hot_unplug_recv(op); | |
874 | case GB_SVC_TYPE_INTF_RESET: | |
875 | return gb_svc_intf_reset_recv(op); | |
ebe99d61 RMS |
876 | case GB_SVC_TYPE_KEY_EVENT: |
877 | return gb_svc_key_event_recv(op); | |
30c6d9d7 | 878 | default: |
684156a9 | 879 | dev_warn(&svc->dev, "unsupported request 0x%02x\n", type); |
30c6d9d7 AE |
880 | return -EINVAL; |
881 | } | |
882 | } | |
883 | ||
ebe99d61 RMS |
884 | static struct input_dev *gb_svc_input_create(struct gb_svc *svc) |
885 | { | |
886 | struct input_dev *input_dev; | |
887 | ||
888 | input_dev = input_allocate_device(); | |
889 | if (!input_dev) | |
890 | return ERR_PTR(-ENOMEM); | |
891 | ||
892 | input_dev->name = dev_name(&svc->dev); | |
893 | svc->input_phys = kasprintf(GFP_KERNEL, "greybus-%s/input0", | |
894 | input_dev->name); | |
895 | if (!svc->input_phys) | |
896 | goto err_free_input; | |
897 | ||
898 | input_dev->phys = svc->input_phys; | |
899 | input_dev->dev.parent = &svc->dev; | |
900 | ||
901 | input_set_drvdata(input_dev, svc); | |
902 | ||
903 | input_set_capability(input_dev, EV_KEY, SVC_KEY_ARA_BUTTON); | |
904 | ||
905 | return input_dev; | |
906 | ||
907 | err_free_input: | |
908 | input_free_device(svc->input); | |
909 | return ERR_PTR(-ENOMEM); | |
910 | } | |
911 | ||
efe6ef76 JH |
912 | static void gb_svc_release(struct device *dev) |
913 | { | |
88f7b96d | 914 | struct gb_svc *svc = to_gb_svc(dev); |
efe6ef76 | 915 | |
7adeaae7 JH |
916 | if (svc->connection) |
917 | gb_connection_destroy(svc->connection); | |
efe6ef76 | 918 | ida_destroy(&svc->device_id_map); |
3e48acac | 919 | destroy_workqueue(svc->wq); |
ebe99d61 | 920 | kfree(svc->input_phys); |
efe6ef76 JH |
921 | kfree(svc); |
922 | } | |
923 | ||
924 | struct device_type greybus_svc_type = { | |
925 | .name = "greybus_svc", | |
926 | .release = gb_svc_release, | |
927 | }; | |
928 | ||
7adeaae7 | 929 | struct gb_svc *gb_svc_create(struct gb_host_device *hd) |
30c6d9d7 AE |
930 | { |
931 | struct gb_svc *svc; | |
30c6d9d7 AE |
932 | |
933 | svc = kzalloc(sizeof(*svc), GFP_KERNEL); | |
934 | if (!svc) | |
7adeaae7 | 935 | return NULL; |
30c6d9d7 | 936 | |
3e48acac JH |
937 | svc->wq = alloc_workqueue("%s:svc", WQ_UNBOUND, 1, dev_name(&hd->dev)); |
938 | if (!svc->wq) { | |
939 | kfree(svc); | |
7adeaae7 | 940 | return NULL; |
3e48acac JH |
941 | } |
942 | ||
efe6ef76 JH |
943 | svc->dev.parent = &hd->dev; |
944 | svc->dev.bus = &greybus_bus_type; | |
945 | svc->dev.type = &greybus_svc_type; | |
66069fb0 | 946 | svc->dev.groups = svc_groups; |
efe6ef76 JH |
947 | svc->dev.dma_mask = svc->dev.parent->dma_mask; |
948 | device_initialize(&svc->dev); | |
949 | ||
950 | dev_set_name(&svc->dev, "%d-svc", hd->bus_id); | |
951 | ||
6106e51b | 952 | ida_init(&svc->device_id_map); |
3ccb1600 | 953 | svc->state = GB_SVC_STATE_RESET; |
f0960d05 | 954 | svc->hd = hd; |
d3d44840 | 955 | |
ebe99d61 RMS |
956 | svc->input = gb_svc_input_create(svc); |
957 | if (IS_ERR(svc->input)) { | |
958 | dev_err(&svc->dev, "failed to create input device: %ld\n", | |
959 | PTR_ERR(svc->input)); | |
960 | goto err_put_device; | |
961 | } | |
962 | ||
f7ee081e JH |
963 | svc->connection = gb_connection_create_static(hd, GB_SVC_CPORT_ID, |
964 | gb_svc_request_handler); | |
24e094d6 JH |
965 | if (IS_ERR(svc->connection)) { |
966 | dev_err(&svc->dev, "failed to create connection: %ld\n", | |
967 | PTR_ERR(svc->connection)); | |
ebe99d61 | 968 | goto err_free_input; |
7adeaae7 JH |
969 | } |
970 | ||
971 | svc->connection->private = svc; | |
efe6ef76 | 972 | |
7adeaae7 | 973 | return svc; |
ebe99d61 RMS |
974 | |
975 | err_free_input: | |
976 | input_free_device(svc->input); | |
977 | err_put_device: | |
978 | put_device(&svc->dev); | |
979 | return NULL; | |
30c6d9d7 AE |
980 | } |
981 | ||
7adeaae7 | 982 | int gb_svc_add(struct gb_svc *svc) |
30c6d9d7 | 983 | { |
7adeaae7 | 984 | int ret; |
30c6d9d7 | 985 | |
7adeaae7 JH |
986 | /* |
987 | * The SVC protocol is currently driven by the SVC, so the SVC device | |
988 | * is added from the connection request handler when enough | |
989 | * information has been received. | |
990 | */ | |
f7ee081e | 991 | ret = gb_connection_enable(svc->connection); |
7adeaae7 JH |
992 | if (ret) |
993 | return ret; | |
994 | ||
995 | return 0; | |
996 | } | |
997 | ||
998 | void gb_svc_del(struct gb_svc *svc) | |
999 | { | |
ebe99d61 RMS |
1000 | gb_connection_disable(svc->connection); |
1001 | ||
7adeaae7 | 1002 | /* |
ebe99d61 RMS |
1003 | * The SVC device and input device may have been registered |
1004 | * from the request handler. | |
7adeaae7 | 1005 | */ |
ebe99d61 | 1006 | if (device_is_registered(&svc->dev)) { |
ed7279ae | 1007 | gb_svc_watchdog_destroy(svc); |
ebe99d61 | 1008 | input_unregister_device(svc->input); |
88f7b96d | 1009 | device_del(&svc->dev); |
ebe99d61 | 1010 | } |
1cacb456 | 1011 | |
7adeaae7 JH |
1012 | flush_workqueue(svc->wq); |
1013 | } | |
efe6ef76 | 1014 | |
7adeaae7 JH |
1015 | void gb_svc_put(struct gb_svc *svc) |
1016 | { | |
efe6ef76 | 1017 | put_device(&svc->dev); |
30c6d9d7 | 1018 | } |