Commit | Line | Data |
---|---|---|
f26e30cc AK |
1 | /** |
2 | * Marvell NFC-over-USB driver: USB interface related functions | |
3 | * | |
4 | * Copyright (C) 2014, Marvell International Ltd. | |
5 | * | |
6 | * This software file (the "File") is distributed by Marvell International | |
7 | * Ltd. under the terms of the GNU General Public License Version 2, June 1991 | |
8 | * (the "License"). You may use, redistribute and/or modify this File in | |
9 | * accordance with the terms and conditions of the License, a copy of which | |
10 | * is available on the worldwide web at | |
11 | * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | |
12 | * | |
13 | * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE | |
14 | * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE | |
15 | * ARE EXPRESSLY DISCLAIMED. The License provides additional details about | |
16 | * this warranty disclaimer. | |
17 | **/ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/usb.h> | |
21 | #include <linux/nfc.h> | |
22 | #include <net/nfc/nci.h> | |
23 | #include <net/nfc/nci_core.h> | |
24 | #include "nfcmrvl.h" | |
25 | ||
26 | #define VERSION "1.0" | |
27 | ||
28 | static struct usb_device_id nfcmrvl_table[] = { | |
29 | { USB_DEVICE_INTERFACE_CLASS(0x1286, 0x2046, 0xff) }, | |
30 | { } /* Terminating entry */ | |
31 | }; | |
32 | ||
33 | MODULE_DEVICE_TABLE(usb, nfcmrvl_table); | |
34 | ||
35 | #define NFCMRVL_USB_BULK_RUNNING 1 | |
36 | #define NFCMRVL_USB_SUSPENDING 2 | |
37 | ||
38 | struct nfcmrvl_usb_drv_data { | |
39 | struct usb_device *udev; | |
40 | struct usb_interface *intf; | |
41 | unsigned long flags; | |
42 | struct work_struct waker; | |
43 | struct usb_anchor tx_anchor; | |
44 | struct usb_anchor bulk_anchor; | |
45 | struct usb_anchor deferred; | |
46 | int tx_in_flight; | |
47 | /* protects tx_in_flight */ | |
48 | spinlock_t txlock; | |
49 | struct usb_endpoint_descriptor *bulk_tx_ep; | |
50 | struct usb_endpoint_descriptor *bulk_rx_ep; | |
51 | int suspend_count; | |
52 | struct nfcmrvl_private *priv; | |
53 | }; | |
54 | ||
55 | static int nfcmrvl_inc_tx(struct nfcmrvl_usb_drv_data *drv_data) | |
56 | { | |
57 | unsigned long flags; | |
58 | int rv; | |
59 | ||
60 | spin_lock_irqsave(&drv_data->txlock, flags); | |
61 | rv = test_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); | |
62 | if (!rv) | |
63 | drv_data->tx_in_flight++; | |
64 | spin_unlock_irqrestore(&drv_data->txlock, flags); | |
65 | ||
66 | return rv; | |
67 | } | |
68 | ||
69 | static void nfcmrvl_bulk_complete(struct urb *urb) | |
70 | { | |
71 | struct nfcmrvl_usb_drv_data *drv_data = urb->context; | |
72 | int err; | |
73 | ||
74 | dev_dbg(&drv_data->udev->dev, "urb %p status %d count %d", | |
75 | urb, urb->status, urb->actual_length); | |
76 | ||
77 | if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) | |
78 | return; | |
79 | ||
80 | if (!urb->status) { | |
81 | if (nfcmrvl_nci_recv_frame(drv_data->priv, urb->transfer_buffer, | |
82 | urb->actual_length) < 0) | |
83 | nfc_err(&drv_data->udev->dev, "corrupted Rx packet"); | |
84 | } | |
85 | ||
86 | if (!test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) | |
87 | return; | |
88 | ||
89 | usb_anchor_urb(urb, &drv_data->bulk_anchor); | |
90 | usb_mark_last_busy(drv_data->udev); | |
91 | ||
92 | err = usb_submit_urb(urb, GFP_ATOMIC); | |
93 | if (err) { | |
94 | /* -EPERM: urb is being killed; | |
95 | * -ENODEV: device got disconnected | |
96 | */ | |
97 | if (err != -EPERM && err != -ENODEV) | |
98 | nfc_err(&drv_data->udev->dev, | |
99 | "urb %p failed to resubmit (%d)", urb, -err); | |
100 | usb_unanchor_urb(urb); | |
101 | } | |
102 | } | |
103 | ||
104 | static int | |
105 | nfcmrvl_submit_bulk_urb(struct nfcmrvl_usb_drv_data *drv_data, gfp_t mem_flags) | |
106 | { | |
107 | struct urb *urb; | |
108 | unsigned char *buf; | |
109 | unsigned int pipe; | |
110 | int err, size = NFCMRVL_NCI_MAX_EVENT_SIZE; | |
111 | ||
112 | if (!drv_data->bulk_rx_ep) | |
113 | return -ENODEV; | |
114 | ||
115 | urb = usb_alloc_urb(0, mem_flags); | |
116 | if (!urb) | |
117 | return -ENOMEM; | |
118 | ||
119 | buf = kmalloc(size, mem_flags); | |
120 | if (!buf) { | |
121 | usb_free_urb(urb); | |
122 | return -ENOMEM; | |
123 | } | |
124 | ||
125 | pipe = usb_rcvbulkpipe(drv_data->udev, | |
126 | drv_data->bulk_rx_ep->bEndpointAddress); | |
127 | ||
128 | usb_fill_bulk_urb(urb, drv_data->udev, pipe, buf, size, | |
129 | nfcmrvl_bulk_complete, drv_data); | |
130 | ||
131 | urb->transfer_flags |= URB_FREE_BUFFER; | |
132 | ||
133 | usb_mark_last_busy(drv_data->udev); | |
134 | usb_anchor_urb(urb, &drv_data->bulk_anchor); | |
135 | ||
136 | err = usb_submit_urb(urb, mem_flags); | |
137 | if (err) { | |
138 | if (err != -EPERM && err != -ENODEV) | |
139 | nfc_err(&drv_data->udev->dev, | |
140 | "urb %p submission failed (%d)", urb, -err); | |
141 | usb_unanchor_urb(urb); | |
142 | } | |
143 | ||
144 | usb_free_urb(urb); | |
145 | ||
146 | return err; | |
147 | } | |
148 | ||
149 | static void nfcmrvl_tx_complete(struct urb *urb) | |
150 | { | |
151 | struct sk_buff *skb = urb->context; | |
152 | struct nci_dev *ndev = (struct nci_dev *)skb->dev; | |
153 | struct nfcmrvl_private *priv = nci_get_drvdata(ndev); | |
154 | struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; | |
155 | ||
156 | nfc_info(priv->dev, "urb %p status %d count %d", | |
157 | urb, urb->status, urb->actual_length); | |
158 | ||
159 | spin_lock(&drv_data->txlock); | |
160 | drv_data->tx_in_flight--; | |
161 | spin_unlock(&drv_data->txlock); | |
162 | ||
163 | kfree(urb->setup_packet); | |
164 | kfree_skb(skb); | |
165 | } | |
166 | ||
167 | static int nfcmrvl_usb_nci_open(struct nfcmrvl_private *priv) | |
168 | { | |
169 | struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; | |
170 | int err; | |
171 | ||
172 | err = usb_autopm_get_interface(drv_data->intf); | |
173 | if (err) | |
174 | return err; | |
175 | ||
176 | drv_data->intf->needs_remote_wakeup = 1; | |
177 | ||
178 | err = nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); | |
179 | if (err) | |
180 | goto failed; | |
181 | ||
182 | set_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); | |
183 | nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); | |
184 | ||
185 | usb_autopm_put_interface(drv_data->intf); | |
186 | return 0; | |
187 | ||
188 | failed: | |
189 | usb_autopm_put_interface(drv_data->intf); | |
190 | return err; | |
191 | } | |
192 | ||
193 | static void nfcmrvl_usb_stop_traffic(struct nfcmrvl_usb_drv_data *drv_data) | |
194 | { | |
195 | usb_kill_anchored_urbs(&drv_data->bulk_anchor); | |
196 | } | |
197 | ||
198 | static int nfcmrvl_usb_nci_close(struct nfcmrvl_private *priv) | |
199 | { | |
200 | struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; | |
201 | int err; | |
202 | ||
203 | cancel_work_sync(&drv_data->waker); | |
204 | ||
205 | clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); | |
206 | ||
207 | nfcmrvl_usb_stop_traffic(drv_data); | |
208 | usb_kill_anchored_urbs(&drv_data->tx_anchor); | |
209 | err = usb_autopm_get_interface(drv_data->intf); | |
210 | if (err) | |
211 | goto failed; | |
212 | ||
213 | drv_data->intf->needs_remote_wakeup = 0; | |
214 | usb_autopm_put_interface(drv_data->intf); | |
215 | ||
216 | failed: | |
217 | usb_scuttle_anchored_urbs(&drv_data->deferred); | |
218 | return 0; | |
219 | } | |
220 | ||
221 | static int nfcmrvl_usb_nci_send(struct nfcmrvl_private *priv, | |
222 | struct sk_buff *skb) | |
223 | { | |
224 | struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; | |
225 | struct urb *urb; | |
226 | unsigned int pipe; | |
227 | int err; | |
228 | ||
229 | if (!drv_data->bulk_tx_ep) | |
230 | return -ENODEV; | |
231 | ||
232 | urb = usb_alloc_urb(0, GFP_ATOMIC); | |
233 | if (!urb) | |
234 | return -ENOMEM; | |
235 | ||
236 | pipe = usb_sndbulkpipe(drv_data->udev, | |
237 | drv_data->bulk_tx_ep->bEndpointAddress); | |
238 | ||
239 | usb_fill_bulk_urb(urb, drv_data->udev, pipe, skb->data, skb->len, | |
240 | nfcmrvl_tx_complete, skb); | |
241 | ||
242 | err = nfcmrvl_inc_tx(drv_data); | |
243 | if (err) { | |
244 | usb_anchor_urb(urb, &drv_data->deferred); | |
245 | schedule_work(&drv_data->waker); | |
246 | err = 0; | |
247 | goto done; | |
248 | } | |
249 | ||
250 | usb_anchor_urb(urb, &drv_data->tx_anchor); | |
251 | ||
252 | err = usb_submit_urb(urb, GFP_ATOMIC); | |
253 | if (err) { | |
254 | if (err != -EPERM && err != -ENODEV) | |
255 | nfc_err(&drv_data->udev->dev, | |
256 | "urb %p submission failed (%d)", urb, -err); | |
257 | kfree(urb->setup_packet); | |
258 | usb_unanchor_urb(urb); | |
259 | } else { | |
260 | usb_mark_last_busy(drv_data->udev); | |
261 | } | |
262 | ||
263 | done: | |
264 | usb_free_urb(urb); | |
265 | return err; | |
266 | } | |
267 | ||
268 | static struct nfcmrvl_if_ops usb_ops = { | |
269 | .nci_open = nfcmrvl_usb_nci_open, | |
270 | .nci_close = nfcmrvl_usb_nci_close, | |
271 | .nci_send = nfcmrvl_usb_nci_send, | |
272 | }; | |
273 | ||
274 | static void nfcmrvl_waker(struct work_struct *work) | |
275 | { | |
276 | struct nfcmrvl_usb_drv_data *drv_data = | |
277 | container_of(work, struct nfcmrvl_usb_drv_data, waker); | |
278 | int err; | |
279 | ||
280 | err = usb_autopm_get_interface(drv_data->intf); | |
281 | if (err) | |
282 | return; | |
283 | ||
284 | usb_autopm_put_interface(drv_data->intf); | |
285 | } | |
286 | ||
287 | static int nfcmrvl_probe(struct usb_interface *intf, | |
288 | const struct usb_device_id *id) | |
289 | { | |
290 | struct usb_endpoint_descriptor *ep_desc; | |
291 | struct nfcmrvl_usb_drv_data *drv_data; | |
292 | struct nfcmrvl_private *priv; | |
293 | int i; | |
294 | struct usb_device *udev = interface_to_usbdev(intf); | |
295 | ||
296 | nfc_info(&udev->dev, "intf %p id %p", intf, id); | |
297 | ||
298 | drv_data = devm_kzalloc(&intf->dev, sizeof(*drv_data), GFP_KERNEL); | |
299 | if (!drv_data) | |
300 | return -ENOMEM; | |
301 | ||
302 | for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { | |
303 | ep_desc = &intf->cur_altsetting->endpoint[i].desc; | |
304 | ||
305 | if (!drv_data->bulk_tx_ep && | |
306 | usb_endpoint_is_bulk_out(ep_desc)) { | |
307 | drv_data->bulk_tx_ep = ep_desc; | |
308 | continue; | |
309 | } | |
310 | ||
311 | if (!drv_data->bulk_rx_ep && | |
312 | usb_endpoint_is_bulk_in(ep_desc)) { | |
313 | drv_data->bulk_rx_ep = ep_desc; | |
314 | continue; | |
315 | } | |
316 | } | |
317 | ||
318 | if (!drv_data->bulk_tx_ep || !drv_data->bulk_rx_ep) | |
319 | return -ENODEV; | |
320 | ||
321 | drv_data->udev = udev; | |
322 | drv_data->intf = intf; | |
323 | ||
324 | INIT_WORK(&drv_data->waker, nfcmrvl_waker); | |
325 | spin_lock_init(&drv_data->txlock); | |
326 | ||
327 | init_usb_anchor(&drv_data->tx_anchor); | |
328 | init_usb_anchor(&drv_data->bulk_anchor); | |
329 | init_usb_anchor(&drv_data->deferred); | |
330 | ||
331 | priv = nfcmrvl_nci_register_dev(drv_data, &usb_ops, | |
332 | &drv_data->udev->dev); | |
333 | if (IS_ERR(priv)) | |
334 | return PTR_ERR(priv); | |
335 | ||
336 | drv_data->priv = priv; | |
337 | priv->dev = &drv_data->udev->dev; | |
338 | ||
339 | usb_set_intfdata(intf, drv_data); | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | static void nfcmrvl_disconnect(struct usb_interface *intf) | |
345 | { | |
346 | struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); | |
347 | ||
348 | if (!drv_data) | |
349 | return; | |
350 | ||
351 | nfc_info(&drv_data->udev->dev, "intf %p", intf); | |
352 | ||
353 | nfcmrvl_nci_unregister_dev(drv_data->priv); | |
354 | ||
355 | usb_set_intfdata(drv_data->intf, NULL); | |
356 | } | |
357 | ||
358 | #ifdef CONFIG_PM | |
359 | static int nfcmrvl_suspend(struct usb_interface *intf, pm_message_t message) | |
360 | { | |
361 | struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); | |
362 | ||
363 | nfc_info(&drv_data->udev->dev, "intf %p", intf); | |
364 | ||
365 | if (drv_data->suspend_count++) | |
366 | return 0; | |
367 | ||
368 | spin_lock_irq(&drv_data->txlock); | |
369 | if (!(PMSG_IS_AUTO(message) && drv_data->tx_in_flight)) { | |
370 | set_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); | |
371 | spin_unlock_irq(&drv_data->txlock); | |
372 | } else { | |
373 | spin_unlock_irq(&drv_data->txlock); | |
374 | drv_data->suspend_count--; | |
375 | return -EBUSY; | |
376 | } | |
377 | ||
378 | nfcmrvl_usb_stop_traffic(drv_data); | |
379 | usb_kill_anchored_urbs(&drv_data->tx_anchor); | |
380 | ||
381 | return 0; | |
382 | } | |
383 | ||
384 | static void nfcmrvl_play_deferred(struct nfcmrvl_usb_drv_data *drv_data) | |
385 | { | |
386 | struct urb *urb; | |
387 | int err; | |
388 | ||
389 | while ((urb = usb_get_from_anchor(&drv_data->deferred))) { | |
390 | err = usb_submit_urb(urb, GFP_ATOMIC); | |
391 | if (err) | |
392 | break; | |
393 | ||
394 | drv_data->tx_in_flight++; | |
395 | } | |
396 | usb_scuttle_anchored_urbs(&drv_data->deferred); | |
397 | } | |
398 | ||
399 | static int nfcmrvl_resume(struct usb_interface *intf) | |
400 | { | |
401 | struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); | |
402 | int err = 0; | |
403 | ||
404 | nfc_info(&drv_data->udev->dev, "intf %p", intf); | |
405 | ||
406 | if (--drv_data->suspend_count) | |
407 | return 0; | |
408 | ||
409 | if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) | |
410 | goto done; | |
411 | ||
412 | if (test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) { | |
413 | err = nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); | |
414 | if (err) { | |
415 | clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); | |
416 | goto failed; | |
417 | } | |
418 | ||
419 | nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); | |
420 | } | |
421 | ||
422 | spin_lock_irq(&drv_data->txlock); | |
423 | nfcmrvl_play_deferred(drv_data); | |
424 | clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); | |
425 | spin_unlock_irq(&drv_data->txlock); | |
426 | ||
427 | return 0; | |
428 | ||
429 | failed: | |
430 | usb_scuttle_anchored_urbs(&drv_data->deferred); | |
431 | done: | |
432 | spin_lock_irq(&drv_data->txlock); | |
433 | clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); | |
434 | spin_unlock_irq(&drv_data->txlock); | |
435 | ||
436 | return err; | |
437 | } | |
438 | #endif | |
439 | ||
440 | static struct usb_driver nfcmrvl_usb_driver = { | |
441 | .name = "nfcmrvl", | |
442 | .probe = nfcmrvl_probe, | |
443 | .disconnect = nfcmrvl_disconnect, | |
444 | #ifdef CONFIG_PM | |
445 | .suspend = nfcmrvl_suspend, | |
446 | .resume = nfcmrvl_resume, | |
447 | .reset_resume = nfcmrvl_resume, | |
448 | #endif | |
449 | .id_table = nfcmrvl_table, | |
450 | .supports_autosuspend = 1, | |
451 | .disable_hub_initiated_lpm = 1, | |
452 | .soft_unbind = 1, | |
453 | }; | |
454 | module_usb_driver(nfcmrvl_usb_driver); | |
455 | ||
456 | MODULE_AUTHOR("Marvell International Ltd."); | |
457 | MODULE_DESCRIPTION("Marvell NFC-over-USB driver ver " VERSION); | |
458 | MODULE_VERSION(VERSION); | |
459 | MODULE_LICENSE("GPL v2"); |