]>
Commit | Line | Data |
---|---|---|
59fcd7c6 SO |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
4 | * Copyright (c) 2003-2013, Intel Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | */ | |
16 | ||
17 | #include <linux/kernel.h> | |
36eda94f | 18 | #include <linux/sched.h> |
59fcd7c6 SO |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> | |
21 | #include <linux/device.h> | |
1f180359 TW |
22 | #include <linux/slab.h> |
23 | ||
59fcd7c6 SO |
24 | #include <linux/mei_cl_bus.h> |
25 | ||
26 | #include "mei_dev.h" | |
27 | #include "client.h" | |
28 | ||
29 | struct mei_nfc_cmd { | |
30 | u8 command; | |
31 | u8 status; | |
32 | u16 req_id; | |
33 | u32 reserved; | |
34 | u16 data_size; | |
35 | u8 sub_command; | |
36 | u8 data[]; | |
37 | } __packed; | |
38 | ||
39 | struct mei_nfc_reply { | |
40 | u8 command; | |
41 | u8 status; | |
42 | u16 req_id; | |
43 | u32 reserved; | |
44 | u16 data_size; | |
45 | u8 sub_command; | |
46 | u8 reply_status; | |
47 | u8 data[]; | |
48 | } __packed; | |
49 | ||
50 | struct mei_nfc_if_version { | |
51 | u8 radio_version_sw[3]; | |
52 | u8 reserved[3]; | |
53 | u8 radio_version_hw[3]; | |
54 | u8 i2c_addr; | |
55 | u8 fw_ivn; | |
56 | u8 vendor_id; | |
57 | u8 radio_type; | |
58 | } __packed; | |
59 | ||
60 | struct mei_nfc_connect { | |
61 | u8 fw_ivn; | |
62 | u8 vendor_id; | |
63 | } __packed; | |
64 | ||
65 | struct mei_nfc_connect_resp { | |
66 | u8 fw_ivn; | |
67 | u8 vendor_id; | |
68 | u16 me_major; | |
69 | u16 me_minor; | |
70 | u16 me_hotfix; | |
71 | u16 me_build; | |
72 | } __packed; | |
73 | ||
74 | struct mei_nfc_hci_hdr { | |
75 | u8 cmd; | |
76 | u8 status; | |
77 | u16 req_id; | |
78 | u32 reserved; | |
79 | u16 data_size; | |
80 | } __packed; | |
81 | ||
82 | #define MEI_NFC_CMD_MAINTENANCE 0x00 | |
83 | #define MEI_NFC_CMD_HCI_SEND 0x01 | |
84 | #define MEI_NFC_CMD_HCI_RECV 0x02 | |
85 | ||
86 | #define MEI_NFC_SUBCMD_CONNECT 0x00 | |
87 | #define MEI_NFC_SUBCMD_IF_VERSION 0x01 | |
88 | ||
89 | #define MEI_NFC_HEADER_SIZE 10 | |
90 | ||
a8605ea2 AU |
91 | /** |
92 | * struct mei_nfc_dev - NFC mei device | |
59fcd7c6 SO |
93 | * |
94 | * @cl: NFC host client | |
95 | * @cl_info: NFC info host client | |
96 | * @init_work: perform connection to the info client | |
ce23139c | 97 | * @send_wq: send completion wait queue |
83ce0741 | 98 | * @fw_ivn: NFC Interface Version Number |
59fcd7c6 SO |
99 | * @vendor_id: NFC manufacturer ID |
100 | * @radio_type: NFC radio type | |
ce23139c AU |
101 | * @bus_name: bus name |
102 | * | |
103 | * @req_id: message counter | |
104 | * @recv_req_id: reception message counter | |
59fcd7c6 SO |
105 | */ |
106 | struct mei_nfc_dev { | |
107 | struct mei_cl *cl; | |
108 | struct mei_cl *cl_info; | |
109 | struct work_struct init_work; | |
36eda94f | 110 | wait_queue_head_t send_wq; |
59fcd7c6 SO |
111 | u8 fw_ivn; |
112 | u8 vendor_id; | |
113 | u8 radio_type; | |
91a6b95f | 114 | char *bus_name; |
36eda94f SO |
115 | |
116 | u16 req_id; | |
117 | u16 recv_req_id; | |
59fcd7c6 SO |
118 | }; |
119 | ||
59fcd7c6 SO |
120 | /* UUIDs for NFC F/W clients */ |
121 | const uuid_le mei_nfc_guid = UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, | |
122 | 0x94, 0xd4, 0x50, 0x26, | |
123 | 0x67, 0x23, 0x77, 0x5c); | |
124 | ||
125 | static const uuid_le mei_nfc_info_guid = UUID_LE(0xd2de1625, 0x382d, 0x417d, | |
126 | 0x48, 0xa4, 0xef, 0xab, | |
127 | 0xba, 0x8a, 0x12, 0x06); | |
128 | ||
91a6b95f SO |
129 | /* Vendors */ |
130 | #define MEI_NFC_VENDOR_INSIDE 0x00 | |
131 | #define MEI_NFC_VENDOR_NXP 0x01 | |
132 | ||
133 | /* Radio types */ | |
134 | #define MEI_NFC_VENDOR_INSIDE_UREAD 0x00 | |
135 | #define MEI_NFC_VENDOR_NXP_PN544 0x01 | |
136 | ||
59fcd7c6 SO |
137 | static void mei_nfc_free(struct mei_nfc_dev *ndev) |
138 | { | |
a176c24d TW |
139 | if (!ndev) |
140 | return; | |
141 | ||
59fcd7c6 SO |
142 | if (ndev->cl) { |
143 | list_del(&ndev->cl->device_link); | |
144 | mei_cl_unlink(ndev->cl); | |
145 | kfree(ndev->cl); | |
146 | } | |
147 | ||
148 | if (ndev->cl_info) { | |
149 | list_del(&ndev->cl_info->device_link); | |
150 | mei_cl_unlink(ndev->cl_info); | |
151 | kfree(ndev->cl_info); | |
152 | } | |
2753ff53 | 153 | |
a176c24d | 154 | kfree(ndev); |
59fcd7c6 SO |
155 | } |
156 | ||
91a6b95f SO |
157 | static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) |
158 | { | |
159 | struct mei_device *dev; | |
160 | ||
161 | if (!ndev->cl) | |
162 | return -ENODEV; | |
163 | ||
164 | dev = ndev->cl->dev; | |
165 | ||
166 | switch (ndev->vendor_id) { | |
167 | case MEI_NFC_VENDOR_INSIDE: | |
168 | switch (ndev->radio_type) { | |
169 | case MEI_NFC_VENDOR_INSIDE_UREAD: | |
170 | ndev->bus_name = "microread"; | |
171 | return 0; | |
172 | ||
173 | default: | |
2bf94cab | 174 | dev_err(dev->dev, "Unknown radio type 0x%x\n", |
91a6b95f SO |
175 | ndev->radio_type); |
176 | ||
177 | return -EINVAL; | |
178 | } | |
179 | ||
180 | case MEI_NFC_VENDOR_NXP: | |
181 | switch (ndev->radio_type) { | |
182 | case MEI_NFC_VENDOR_NXP_PN544: | |
183 | ndev->bus_name = "pn544"; | |
184 | return 0; | |
185 | default: | |
2bf94cab | 186 | dev_err(dev->dev, "Unknown radio type 0x%x\n", |
91a6b95f SO |
187 | ndev->radio_type); |
188 | ||
189 | return -EINVAL; | |
190 | } | |
191 | ||
192 | default: | |
2bf94cab | 193 | dev_err(dev->dev, "Unknown vendor ID 0x%x\n", |
91a6b95f SO |
194 | ndev->vendor_id); |
195 | ||
196 | return -EINVAL; | |
197 | } | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
36eda94f SO |
202 | static int mei_nfc_connect(struct mei_nfc_dev *ndev) |
203 | { | |
204 | struct mei_device *dev; | |
205 | struct mei_cl *cl; | |
206 | struct mei_nfc_cmd *cmd, *reply; | |
207 | struct mei_nfc_connect *connect; | |
208 | struct mei_nfc_connect_resp *connect_resp; | |
209 | size_t connect_length, connect_resp_length; | |
210 | int bytes_recv, ret; | |
211 | ||
212 | cl = ndev->cl; | |
213 | dev = cl->dev; | |
214 | ||
215 | connect_length = sizeof(struct mei_nfc_cmd) + | |
216 | sizeof(struct mei_nfc_connect); | |
217 | ||
218 | connect_resp_length = sizeof(struct mei_nfc_cmd) + | |
219 | sizeof(struct mei_nfc_connect_resp); | |
220 | ||
221 | cmd = kzalloc(connect_length, GFP_KERNEL); | |
222 | if (!cmd) | |
223 | return -ENOMEM; | |
224 | connect = (struct mei_nfc_connect *)cmd->data; | |
225 | ||
226 | reply = kzalloc(connect_resp_length, GFP_KERNEL); | |
227 | if (!reply) { | |
228 | kfree(cmd); | |
229 | return -ENOMEM; | |
230 | } | |
231 | ||
232 | connect_resp = (struct mei_nfc_connect_resp *)reply->data; | |
233 | ||
234 | cmd->command = MEI_NFC_CMD_MAINTENANCE; | |
235 | cmd->data_size = 3; | |
236 | cmd->sub_command = MEI_NFC_SUBCMD_CONNECT; | |
237 | connect->fw_ivn = ndev->fw_ivn; | |
238 | connect->vendor_id = ndev->vendor_id; | |
239 | ||
240 | ret = __mei_cl_send(cl, (u8 *)cmd, connect_length); | |
241 | if (ret < 0) { | |
2bf94cab | 242 | dev_err(dev->dev, "Could not send connect cmd\n"); |
36eda94f SO |
243 | goto err; |
244 | } | |
245 | ||
246 | bytes_recv = __mei_cl_recv(cl, (u8 *)reply, connect_resp_length); | |
247 | if (bytes_recv < 0) { | |
2bf94cab | 248 | dev_err(dev->dev, "Could not read connect response\n"); |
36eda94f SO |
249 | ret = bytes_recv; |
250 | goto err; | |
251 | } | |
252 | ||
2bf94cab | 253 | dev_info(dev->dev, "IVN 0x%x Vendor ID 0x%x\n", |
36eda94f SO |
254 | connect_resp->fw_ivn, connect_resp->vendor_id); |
255 | ||
2bf94cab | 256 | dev_info(dev->dev, "ME FW %d.%d.%d.%d\n", |
36eda94f SO |
257 | connect_resp->me_major, connect_resp->me_minor, |
258 | connect_resp->me_hotfix, connect_resp->me_build); | |
259 | ||
260 | ret = 0; | |
261 | ||
262 | err: | |
263 | kfree(reply); | |
264 | kfree(cmd); | |
265 | ||
266 | return ret; | |
267 | } | |
268 | ||
59fcd7c6 SO |
269 | static int mei_nfc_if_version(struct mei_nfc_dev *ndev) |
270 | { | |
271 | struct mei_device *dev; | |
272 | struct mei_cl *cl; | |
273 | ||
274 | struct mei_nfc_cmd cmd; | |
275 | struct mei_nfc_reply *reply = NULL; | |
276 | struct mei_nfc_if_version *version; | |
277 | size_t if_version_length; | |
278 | int bytes_recv, ret; | |
279 | ||
280 | cl = ndev->cl_info; | |
281 | dev = cl->dev; | |
282 | ||
283 | memset(&cmd, 0, sizeof(struct mei_nfc_cmd)); | |
284 | cmd.command = MEI_NFC_CMD_MAINTENANCE; | |
285 | cmd.data_size = 1; | |
286 | cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION; | |
287 | ||
288 | ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd)); | |
289 | if (ret < 0) { | |
2bf94cab | 290 | dev_err(dev->dev, "Could not send IF version cmd\n"); |
59fcd7c6 SO |
291 | return ret; |
292 | } | |
293 | ||
294 | /* to be sure on the stack we alloc memory */ | |
295 | if_version_length = sizeof(struct mei_nfc_reply) + | |
296 | sizeof(struct mei_nfc_if_version); | |
297 | ||
298 | reply = kzalloc(if_version_length, GFP_KERNEL); | |
299 | if (!reply) | |
300 | return -ENOMEM; | |
301 | ||
302 | bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length); | |
303 | if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { | |
2bf94cab | 304 | dev_err(dev->dev, "Could not read IF version\n"); |
59fcd7c6 SO |
305 | ret = -EIO; |
306 | goto err; | |
307 | } | |
308 | ||
309 | version = (struct mei_nfc_if_version *)reply->data; | |
310 | ||
311 | ndev->fw_ivn = version->fw_ivn; | |
312 | ndev->vendor_id = version->vendor_id; | |
313 | ndev->radio_type = version->radio_type; | |
314 | ||
315 | err: | |
316 | kfree(reply); | |
317 | return ret; | |
318 | } | |
319 | ||
36eda94f SO |
320 | static int mei_nfc_enable(struct mei_cl_device *cldev) |
321 | { | |
322 | struct mei_device *dev; | |
a176c24d | 323 | struct mei_nfc_dev *ndev; |
36eda94f SO |
324 | int ret; |
325 | ||
a176c24d | 326 | ndev = (struct mei_nfc_dev *)cldev->priv_data; |
36eda94f SO |
327 | dev = ndev->cl->dev; |
328 | ||
329 | ret = mei_nfc_connect(ndev); | |
330 | if (ret < 0) { | |
2bf94cab | 331 | dev_err(dev->dev, "Could not connect to NFC"); |
36eda94f SO |
332 | return ret; |
333 | } | |
334 | ||
335 | return 0; | |
336 | } | |
337 | ||
338 | static int mei_nfc_disable(struct mei_cl_device *cldev) | |
339 | { | |
340 | return 0; | |
341 | } | |
342 | ||
343 | static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length) | |
344 | { | |
345 | struct mei_device *dev; | |
346 | struct mei_nfc_dev *ndev; | |
347 | struct mei_nfc_hci_hdr *hdr; | |
348 | u8 *mei_buf; | |
349 | int err; | |
350 | ||
351 | ndev = (struct mei_nfc_dev *) cldev->priv_data; | |
352 | dev = ndev->cl->dev; | |
353 | ||
8e8248b1 | 354 | err = -ENOMEM; |
36eda94f SO |
355 | mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL); |
356 | if (!mei_buf) | |
8e8248b1 | 357 | goto out; |
36eda94f SO |
358 | |
359 | hdr = (struct mei_nfc_hci_hdr *) mei_buf; | |
360 | hdr->cmd = MEI_NFC_CMD_HCI_SEND; | |
361 | hdr->status = 0; | |
362 | hdr->req_id = ndev->req_id; | |
363 | hdr->reserved = 0; | |
364 | hdr->data_size = length; | |
365 | ||
366 | memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length); | |
36eda94f SO |
367 | err = __mei_cl_send(ndev->cl, mei_buf, length + MEI_NFC_HEADER_SIZE); |
368 | if (err < 0) | |
8e8248b1 | 369 | goto out; |
36eda94f SO |
370 | |
371 | if (!wait_event_interruptible_timeout(ndev->send_wq, | |
372 | ndev->recv_req_id == ndev->req_id, HZ)) { | |
2bf94cab | 373 | dev_err(dev->dev, "NFC MEI command timeout\n"); |
7ca96aa2 | 374 | err = -ETIME; |
36eda94f SO |
375 | } else { |
376 | ndev->req_id++; | |
377 | } | |
8e8248b1 AU |
378 | out: |
379 | kfree(mei_buf); | |
36eda94f SO |
380 | return err; |
381 | } | |
382 | ||
383 | static int mei_nfc_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) | |
384 | { | |
385 | struct mei_nfc_dev *ndev; | |
386 | struct mei_nfc_hci_hdr *hci_hdr; | |
387 | int received_length; | |
388 | ||
389 | ndev = (struct mei_nfc_dev *)cldev->priv_data; | |
390 | ||
391 | received_length = __mei_cl_recv(ndev->cl, buf, length); | |
392 | if (received_length < 0) | |
393 | return received_length; | |
394 | ||
395 | hci_hdr = (struct mei_nfc_hci_hdr *) buf; | |
396 | ||
397 | if (hci_hdr->cmd == MEI_NFC_CMD_HCI_SEND) { | |
398 | ndev->recv_req_id = hci_hdr->req_id; | |
399 | wake_up(&ndev->send_wq); | |
400 | ||
401 | return 0; | |
402 | } | |
403 | ||
404 | return received_length; | |
405 | } | |
406 | ||
407 | static struct mei_cl_ops nfc_ops = { | |
408 | .enable = mei_nfc_enable, | |
409 | .disable = mei_nfc_disable, | |
410 | .send = mei_nfc_send, | |
411 | .recv = mei_nfc_recv, | |
412 | }; | |
413 | ||
59fcd7c6 SO |
414 | static void mei_nfc_init(struct work_struct *work) |
415 | { | |
416 | struct mei_device *dev; | |
91a6b95f | 417 | struct mei_cl_device *cldev; |
59fcd7c6 SO |
418 | struct mei_nfc_dev *ndev; |
419 | struct mei_cl *cl_info; | |
420 | ||
421 | ndev = container_of(work, struct mei_nfc_dev, init_work); | |
422 | ||
423 | cl_info = ndev->cl_info; | |
424 | dev = cl_info->dev; | |
425 | ||
426 | mutex_lock(&dev->device_lock); | |
427 | ||
428 | if (mei_cl_connect(cl_info, NULL) < 0) { | |
429 | mutex_unlock(&dev->device_lock); | |
2bf94cab | 430 | dev_err(dev->dev, "Could not connect to the NFC INFO ME client"); |
59fcd7c6 SO |
431 | |
432 | goto err; | |
433 | } | |
434 | ||
435 | mutex_unlock(&dev->device_lock); | |
436 | ||
437 | if (mei_nfc_if_version(ndev) < 0) { | |
2bf94cab | 438 | dev_err(dev->dev, "Could not get the NFC interface version"); |
59fcd7c6 SO |
439 | |
440 | goto err; | |
441 | } | |
442 | ||
2bf94cab | 443 | dev_info(dev->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", |
59fcd7c6 SO |
444 | ndev->fw_ivn, ndev->vendor_id, ndev->radio_type); |
445 | ||
446 | mutex_lock(&dev->device_lock); | |
447 | ||
448 | if (mei_cl_disconnect(cl_info) < 0) { | |
449 | mutex_unlock(&dev->device_lock); | |
2bf94cab | 450 | dev_err(dev->dev, "Could not disconnect the NFC INFO ME client"); |
59fcd7c6 SO |
451 | |
452 | goto err; | |
453 | } | |
454 | ||
455 | mutex_unlock(&dev->device_lock); | |
456 | ||
91a6b95f | 457 | if (mei_nfc_build_bus_name(ndev) < 0) { |
2bf94cab | 458 | dev_err(dev->dev, "Could not build the bus ID name\n"); |
91a6b95f SO |
459 | return; |
460 | } | |
461 | ||
36eda94f | 462 | cldev = mei_cl_add_device(dev, mei_nfc_guid, ndev->bus_name, &nfc_ops); |
91a6b95f | 463 | if (!cldev) { |
2bf94cab | 464 | dev_err(dev->dev, "Could not add the NFC device to the MEI bus\n"); |
91a6b95f SO |
465 | |
466 | goto err; | |
467 | } | |
468 | ||
469 | cldev->priv_data = ndev; | |
470 | ||
471 | ||
59fcd7c6 SO |
472 | return; |
473 | ||
474 | err: | |
0631213f | 475 | mutex_lock(&dev->device_lock); |
59fcd7c6 | 476 | mei_nfc_free(ndev); |
0631213f | 477 | mutex_unlock(&dev->device_lock); |
59fcd7c6 | 478 | |
59fcd7c6 SO |
479 | } |
480 | ||
481 | ||
482 | int mei_nfc_host_init(struct mei_device *dev) | |
483 | { | |
a176c24d | 484 | struct mei_nfc_dev *ndev; |
03b8d341 TW |
485 | struct mei_cl *cl_info, *cl; |
486 | struct mei_me_client *me_cl = NULL; | |
d320832f | 487 | int ret; |
59fcd7c6 | 488 | |
a176c24d TW |
489 | |
490 | /* in case of internal reset bail out | |
491 | * as the device is already setup | |
492 | */ | |
493 | cl = mei_cl_bus_find_cl_by_uuid(dev, mei_nfc_guid); | |
494 | if (cl) | |
59fcd7c6 SO |
495 | return 0; |
496 | ||
a176c24d TW |
497 | ndev = kzalloc(sizeof(struct mei_nfc_dev), GFP_KERNEL); |
498 | if (!ndev) { | |
499 | ret = -ENOMEM; | |
500 | goto err; | |
501 | } | |
502 | ||
59fcd7c6 | 503 | /* check for valid client id */ |
d320832f TW |
504 | me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid); |
505 | if (!me_cl) { | |
2bf94cab | 506 | dev_info(dev->dev, "nfc: failed to find the client\n"); |
7ca96aa2 | 507 | ret = -ENOTTY; |
59fcd7c6 SO |
508 | goto err; |
509 | } | |
510 | ||
03b8d341 TW |
511 | cl_info = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); |
512 | if (IS_ERR(cl_info)) { | |
513 | ret = PTR_ERR(cl_info); | |
514 | goto err; | |
515 | } | |
516 | ||
d320832f | 517 | cl_info->me_client_id = me_cl->client_id; |
d880f329 | 518 | cl_info->cl_uuid = me_cl->props.protocol_name; |
79563db9 | 519 | mei_me_cl_put(me_cl); |
03b8d341 | 520 | me_cl = NULL; |
59fcd7c6 SO |
521 | |
522 | list_add_tail(&cl_info->device_link, &dev->device_list); | |
523 | ||
03b8d341 TW |
524 | ndev->cl_info = cl_info; |
525 | ||
59fcd7c6 | 526 | /* check for valid client id */ |
d320832f TW |
527 | me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid); |
528 | if (!me_cl) { | |
2bf94cab | 529 | dev_info(dev->dev, "nfc: failed to find the client\n"); |
7ca96aa2 | 530 | ret = -ENOTTY; |
59fcd7c6 SO |
531 | goto err; |
532 | } | |
533 | ||
03b8d341 TW |
534 | cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); |
535 | if (IS_ERR(cl)) { | |
536 | ret = PTR_ERR(cl); | |
537 | goto err; | |
538 | } | |
539 | ||
d320832f | 540 | cl->me_client_id = me_cl->client_id; |
d880f329 | 541 | cl->cl_uuid = me_cl->props.protocol_name; |
79563db9 | 542 | mei_me_cl_put(me_cl); |
03b8d341 | 543 | me_cl = NULL; |
59fcd7c6 | 544 | |
59fcd7c6 SO |
545 | list_add_tail(&cl->device_link, &dev->device_list); |
546 | ||
03b8d341 TW |
547 | ndev->cl = cl; |
548 | ||
36eda94f | 549 | ndev->req_id = 1; |
59fcd7c6 SO |
550 | |
551 | INIT_WORK(&ndev->init_work, mei_nfc_init); | |
36eda94f | 552 | init_waitqueue_head(&ndev->send_wq); |
59fcd7c6 SO |
553 | schedule_work(&ndev->init_work); |
554 | ||
555 | return 0; | |
556 | ||
557 | err: | |
03b8d341 | 558 | mei_me_cl_put(me_cl); |
59fcd7c6 SO |
559 | mei_nfc_free(ndev); |
560 | ||
561 | return ret; | |
562 | } | |
563 | ||
dc844b0d | 564 | void mei_nfc_host_exit(struct mei_device *dev) |
59fcd7c6 | 565 | { |
a176c24d TW |
566 | struct mei_nfc_dev *ndev; |
567 | struct mei_cl *cl; | |
568 | struct mei_cl_device *cldev; | |
569 | ||
570 | cl = mei_cl_bus_find_cl_by_uuid(dev, mei_nfc_guid); | |
571 | if (!cl) | |
572 | return; | |
573 | ||
574 | cldev = cl->device; | |
575 | if (!cldev) | |
576 | return; | |
92db1555 | 577 | |
a176c24d TW |
578 | ndev = (struct mei_nfc_dev *)cldev->priv_data; |
579 | if (ndev) | |
580 | cancel_work_sync(&ndev->init_work); | |
581 | ||
582 | cldev->priv_data = NULL; | |
583 | ||
584 | mutex_lock(&dev->device_lock); | |
585 | /* Need to remove the device here | |
586 | * since mei_nfc_free will unlink the clients | |
587 | */ | |
588 | mei_cl_remove_device(cldev); | |
589 | mei_nfc_free(ndev); | |
590 | mutex_unlock(&dev->device_lock); | |
48705693 | 591 | } |
dc844b0d | 592 | |
91a6b95f | 593 |