diff options
Diffstat (limited to 'drivers/nfc/nfcmrvl/usb.c')
| -rw-r--r-- | drivers/nfc/nfcmrvl/usb.c | 459 | 
1 files changed, 459 insertions, 0 deletions
diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c new file mode 100644 index 00000000000..3221ca37d6c --- /dev/null +++ b/drivers/nfc/nfcmrvl/usb.c @@ -0,0 +1,459 @@ +/** + * Marvell NFC-over-USB driver: USB interface related functions + * + * Copyright (C) 2014, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License").  You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available on the worldwide web at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED.  The License provides additional details about + * this warranty disclaimer. + **/ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/nfc.h> +#include <net/nfc/nci.h> +#include <net/nfc/nci_core.h> +#include "nfcmrvl.h" + +#define VERSION "1.0" + +static struct usb_device_id nfcmrvl_table[] = { +	{ USB_DEVICE_INTERFACE_CLASS(0x1286, 0x2046, 0xff) }, +	{ }	/* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, nfcmrvl_table); + +#define NFCMRVL_USB_BULK_RUNNING	1 +#define NFCMRVL_USB_SUSPENDING		2 + +struct nfcmrvl_usb_drv_data { +	struct usb_device *udev; +	struct usb_interface *intf; +	unsigned long flags; +	struct work_struct waker; +	struct usb_anchor tx_anchor; +	struct usb_anchor bulk_anchor; +	struct usb_anchor deferred; +	int tx_in_flight; +	/* protects tx_in_flight */ +	spinlock_t txlock; +	struct usb_endpoint_descriptor *bulk_tx_ep; +	struct usb_endpoint_descriptor *bulk_rx_ep; +	int suspend_count; +	struct nfcmrvl_private *priv; +}; + +static int nfcmrvl_inc_tx(struct nfcmrvl_usb_drv_data *drv_data) +{ +	unsigned long flags; +	int rv; + +	spin_lock_irqsave(&drv_data->txlock, flags); +	rv = test_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); +	if (!rv) +		drv_data->tx_in_flight++; +	spin_unlock_irqrestore(&drv_data->txlock, flags); + +	return rv; +} + +static void nfcmrvl_bulk_complete(struct urb *urb) +{ +	struct nfcmrvl_usb_drv_data *drv_data = urb->context; +	int err; + +	dev_dbg(&drv_data->udev->dev, "urb %p status %d count %d", +		urb, urb->status, urb->actual_length); + +	if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) +		return; + +	if (!urb->status) { +		if (nfcmrvl_nci_recv_frame(drv_data->priv, urb->transfer_buffer, +					   urb->actual_length) < 0) +			nfc_err(&drv_data->udev->dev, "corrupted Rx packet"); +	} + +	if (!test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) +		return; + +	usb_anchor_urb(urb, &drv_data->bulk_anchor); +	usb_mark_last_busy(drv_data->udev); + +	err = usb_submit_urb(urb, GFP_ATOMIC); +	if (err) { +		/* -EPERM: urb is being killed; +		 * -ENODEV: device got disconnected +		 */ +		if (err != -EPERM && err != -ENODEV) +			nfc_err(&drv_data->udev->dev, +				"urb %p failed to resubmit (%d)", urb, -err); +		usb_unanchor_urb(urb); +	} +} + +static int +nfcmrvl_submit_bulk_urb(struct nfcmrvl_usb_drv_data *drv_data, gfp_t mem_flags) +{ +	struct urb *urb; +	unsigned char *buf; +	unsigned int pipe; +	int err, size = NFCMRVL_NCI_MAX_EVENT_SIZE; + +	if (!drv_data->bulk_rx_ep) +		return -ENODEV; + +	urb = usb_alloc_urb(0, mem_flags); +	if (!urb) +		return -ENOMEM; + +	buf = kmalloc(size, mem_flags); +	if (!buf) { +		usb_free_urb(urb); +		return -ENOMEM; +	} + +	pipe = usb_rcvbulkpipe(drv_data->udev, +			       drv_data->bulk_rx_ep->bEndpointAddress); + +	usb_fill_bulk_urb(urb, drv_data->udev, pipe, buf, size, +			  nfcmrvl_bulk_complete, drv_data); + +	urb->transfer_flags |= URB_FREE_BUFFER; + +	usb_mark_last_busy(drv_data->udev); +	usb_anchor_urb(urb, &drv_data->bulk_anchor); + +	err = usb_submit_urb(urb, mem_flags); +	if (err) { +		if (err != -EPERM && err != -ENODEV) +			nfc_err(&drv_data->udev->dev, +				"urb %p submission failed (%d)", urb, -err); +		usb_unanchor_urb(urb); +	} + +	usb_free_urb(urb); + +	return err; +} + +static void nfcmrvl_tx_complete(struct urb *urb) +{ +	struct sk_buff *skb = urb->context; +	struct nci_dev *ndev = (struct nci_dev *)skb->dev; +	struct nfcmrvl_private *priv = nci_get_drvdata(ndev); +	struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; + +	nfc_info(priv->dev, "urb %p status %d count %d", +		 urb, urb->status, urb->actual_length); + +	spin_lock(&drv_data->txlock); +	drv_data->tx_in_flight--; +	spin_unlock(&drv_data->txlock); + +	kfree(urb->setup_packet); +	kfree_skb(skb); +} + +static int nfcmrvl_usb_nci_open(struct nfcmrvl_private *priv) +{ +	struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; +	int err; + +	err = usb_autopm_get_interface(drv_data->intf); +	if (err) +		return err; + +	drv_data->intf->needs_remote_wakeup = 1; + +	err = nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); +	if (err) +		goto failed; + +	set_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); +	nfcmrvl_submit_bulk_urb(drv_data, GFP_KERNEL); + +	usb_autopm_put_interface(drv_data->intf); +	return 0; + +failed: +	usb_autopm_put_interface(drv_data->intf); +	return err; +} + +static void nfcmrvl_usb_stop_traffic(struct nfcmrvl_usb_drv_data *drv_data) +{ +	usb_kill_anchored_urbs(&drv_data->bulk_anchor); +} + +static int nfcmrvl_usb_nci_close(struct nfcmrvl_private *priv) +{ +	struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; +	int err; + +	cancel_work_sync(&drv_data->waker); + +	clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); + +	nfcmrvl_usb_stop_traffic(drv_data); +	usb_kill_anchored_urbs(&drv_data->tx_anchor); +	err = usb_autopm_get_interface(drv_data->intf); +	if (err) +		goto failed; + +	drv_data->intf->needs_remote_wakeup = 0; +	usb_autopm_put_interface(drv_data->intf); + +failed: +	usb_scuttle_anchored_urbs(&drv_data->deferred); +	return 0; +} + +static int nfcmrvl_usb_nci_send(struct nfcmrvl_private *priv, +				struct sk_buff *skb) +{ +	struct nfcmrvl_usb_drv_data *drv_data = priv->drv_data; +	struct urb *urb; +	unsigned int pipe; +	int err; + +	if (!drv_data->bulk_tx_ep) +		return -ENODEV; + +	urb = usb_alloc_urb(0, GFP_ATOMIC); +	if (!urb) +		return -ENOMEM; + +	pipe = usb_sndbulkpipe(drv_data->udev, +				drv_data->bulk_tx_ep->bEndpointAddress); + +	usb_fill_bulk_urb(urb, drv_data->udev, pipe, skb->data, skb->len, +			  nfcmrvl_tx_complete, skb); + +	err = nfcmrvl_inc_tx(drv_data); +	if (err) { +		usb_anchor_urb(urb, &drv_data->deferred); +		schedule_work(&drv_data->waker); +		err = 0; +		goto done; +	} + +	usb_anchor_urb(urb, &drv_data->tx_anchor); + +	err = usb_submit_urb(urb, GFP_ATOMIC); +	if (err) { +		if (err != -EPERM && err != -ENODEV) +			nfc_err(&drv_data->udev->dev, +				"urb %p submission failed (%d)", urb, -err); +		kfree(urb->setup_packet); +		usb_unanchor_urb(urb); +	} else { +		usb_mark_last_busy(drv_data->udev); +	} + +done: +	usb_free_urb(urb); +	return err; +} + +static struct nfcmrvl_if_ops usb_ops = { +	.nci_open = nfcmrvl_usb_nci_open, +	.nci_close = nfcmrvl_usb_nci_close, +	.nci_send = nfcmrvl_usb_nci_send, +}; + +static void nfcmrvl_waker(struct work_struct *work) +{ +	struct nfcmrvl_usb_drv_data *drv_data = +			container_of(work, struct nfcmrvl_usb_drv_data, waker); +	int err; + +	err = usb_autopm_get_interface(drv_data->intf); +	if (err) +		return; + +	usb_autopm_put_interface(drv_data->intf); +} + +static int nfcmrvl_probe(struct usb_interface *intf, +			 const struct usb_device_id *id) +{ +	struct usb_endpoint_descriptor *ep_desc; +	struct nfcmrvl_usb_drv_data *drv_data; +	struct nfcmrvl_private *priv; +	int i; +	struct usb_device *udev = interface_to_usbdev(intf); + +	nfc_info(&udev->dev, "intf %p id %p", intf, id); + +	drv_data = devm_kzalloc(&intf->dev, sizeof(*drv_data), GFP_KERNEL); +	if (!drv_data) +		return -ENOMEM; + +	for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { +		ep_desc = &intf->cur_altsetting->endpoint[i].desc; + +		if (!drv_data->bulk_tx_ep && +		    usb_endpoint_is_bulk_out(ep_desc)) { +			drv_data->bulk_tx_ep = ep_desc; +			continue; +		} + +		if (!drv_data->bulk_rx_ep && +		    usb_endpoint_is_bulk_in(ep_desc)) { +			drv_data->bulk_rx_ep = ep_desc; +			continue; +		} +	} + +	if (!drv_data->bulk_tx_ep || !drv_data->bulk_rx_ep) +		return -ENODEV; + +	drv_data->udev = udev; +	drv_data->intf = intf; + +	INIT_WORK(&drv_data->waker, nfcmrvl_waker); +	spin_lock_init(&drv_data->txlock); + +	init_usb_anchor(&drv_data->tx_anchor); +	init_usb_anchor(&drv_data->bulk_anchor); +	init_usb_anchor(&drv_data->deferred); + +	priv = nfcmrvl_nci_register_dev(drv_data, &usb_ops, +					&drv_data->udev->dev); +	if (IS_ERR(priv)) +		return PTR_ERR(priv); + +	drv_data->priv = priv; +	priv->dev = &drv_data->udev->dev; + +	usb_set_intfdata(intf, drv_data); + +	return 0; +} + +static void nfcmrvl_disconnect(struct usb_interface *intf) +{ +	struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); + +	if (!drv_data) +		return; + +	nfc_info(&drv_data->udev->dev, "intf %p", intf); + +	nfcmrvl_nci_unregister_dev(drv_data->priv); + +	usb_set_intfdata(drv_data->intf, NULL); +} + +#ifdef CONFIG_PM +static int nfcmrvl_suspend(struct usb_interface *intf, pm_message_t message) +{ +	struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); + +	nfc_info(&drv_data->udev->dev, "intf %p", intf); + +	if (drv_data->suspend_count++) +		return 0; + +	spin_lock_irq(&drv_data->txlock); +	if (!(PMSG_IS_AUTO(message) && drv_data->tx_in_flight)) { +		set_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); +		spin_unlock_irq(&drv_data->txlock); +	} else { +		spin_unlock_irq(&drv_data->txlock); +		drv_data->suspend_count--; +		return -EBUSY; +	} + +	nfcmrvl_usb_stop_traffic(drv_data); +	usb_kill_anchored_urbs(&drv_data->tx_anchor); + +	return 0; +} + +static void nfcmrvl_play_deferred(struct nfcmrvl_usb_drv_data *drv_data) +{ +	struct urb *urb; +	int err; + +	while ((urb = usb_get_from_anchor(&drv_data->deferred))) { +		err = usb_submit_urb(urb, GFP_ATOMIC); +		if (err) +			break; + +		drv_data->tx_in_flight++; +	} +	usb_scuttle_anchored_urbs(&drv_data->deferred); +} + +static int nfcmrvl_resume(struct usb_interface *intf) +{ +	struct nfcmrvl_usb_drv_data *drv_data = usb_get_intfdata(intf); +	int err = 0; + +	nfc_info(&drv_data->udev->dev, "intf %p", intf); + +	if (--drv_data->suspend_count) +		return 0; + +	if (!test_bit(NFCMRVL_NCI_RUNNING, &drv_data->flags)) +		goto done; + +	if (test_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags)) { +		err = nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); +		if (err) { +			clear_bit(NFCMRVL_USB_BULK_RUNNING, &drv_data->flags); +			goto failed; +		} + +		nfcmrvl_submit_bulk_urb(drv_data, GFP_NOIO); +	} + +	spin_lock_irq(&drv_data->txlock); +	nfcmrvl_play_deferred(drv_data); +	clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); +	spin_unlock_irq(&drv_data->txlock); + +	return 0; + +failed: +	usb_scuttle_anchored_urbs(&drv_data->deferred); +done: +	spin_lock_irq(&drv_data->txlock); +	clear_bit(NFCMRVL_USB_SUSPENDING, &drv_data->flags); +	spin_unlock_irq(&drv_data->txlock); + +	return err; +} +#endif + +static struct usb_driver nfcmrvl_usb_driver = { +	.name		= "nfcmrvl", +	.probe		= nfcmrvl_probe, +	.disconnect	= nfcmrvl_disconnect, +#ifdef CONFIG_PM +	.suspend	= nfcmrvl_suspend, +	.resume		= nfcmrvl_resume, +	.reset_resume	= nfcmrvl_resume, +#endif +	.id_table	= nfcmrvl_table, +	.supports_autosuspend = 1, +	.disable_hub_initiated_lpm = 1, +	.soft_unbind = 1, +}; +module_usb_driver(nfcmrvl_usb_driver); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell NFC-over-USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL v2");  | 
