aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2012-03-06 11:29:22 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-03-08 16:06:48 -0500
commit3cc3615749dbd1b891512d5c9a5bf4559cfa9741 (patch)
tree9e6d3b095c521d7114ac0b74ee280b31d2809d94
parentb0c13860808a528cd580fdca61aef9f73352a331 (diff)
usb: cdc-wdm: adding usb_cdc_wdm_register subdriver support
This driver can be used as a subdriver of another USB driver, allowing it to export a Device Managment interface consisting of a single interrupt endpoint with no dedicated USB interface. Some devices provide a Device Management function combined with a wwan function in a single USB interface having three endpoints (bulk in/out + interrupt). If the interrupt endpoint is used exclusively for DM notifications, then this driver can support that as a subdriver provided that the wwan driver calls the appropriate entry points on probe, suspend, resume, pre_reset, post_reset and disconnect. The main driver must have full control over all interface related settings, including the needs_remote_wakeup flag. A manage_power function must be provided by the main driver. A manage_power stub doing direct flag manipulation is used in normal driver mode. Signed-off-by: Bjørn Mork <bjorn@mork.no> Acked-by: Oliver Neukum <oneukum@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/class/cdc-wdm.c63
-rw-r--r--include/linux/usb/cdc-wdm.h19
2 files changed, 78 insertions, 4 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index 46827373ecf9..c6f6560d436c 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -23,6 +23,7 @@
23#include <linux/usb/cdc.h> 23#include <linux/usb/cdc.h>
24#include <asm/byteorder.h> 24#include <asm/byteorder.h>
25#include <asm/unaligned.h> 25#include <asm/unaligned.h>
26#include <linux/usb/cdc-wdm.h>
26 27
27/* 28/*
28 * Version Information 29 * Version Information
@@ -116,6 +117,7 @@ struct wdm_device {
116 int rerr; 117 int rerr;
117 118
118 struct list_head device_list; 119 struct list_head device_list;
120 int (*manage_power)(struct usb_interface *, int);
119}; 121};
120 122
121static struct usb_driver wdm_driver; 123static struct usb_driver wdm_driver;
@@ -580,7 +582,6 @@ static int wdm_open(struct inode *inode, struct file *file)
580 dev_err(&desc->intf->dev, "Error autopm - %d\n", rv); 582 dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
581 goto out; 583 goto out;
582 } 584 }
583 intf->needs_remote_wakeup = 1;
584 585
585 /* using write lock to protect desc->count */ 586 /* using write lock to protect desc->count */
586 mutex_lock(&desc->wlock); 587 mutex_lock(&desc->wlock);
@@ -597,6 +598,8 @@ static int wdm_open(struct inode *inode, struct file *file)
597 rv = 0; 598 rv = 0;
598 } 599 }
599 mutex_unlock(&desc->wlock); 600 mutex_unlock(&desc->wlock);
601 if (desc->count == 1)
602 desc->manage_power(intf, 1);
600 usb_autopm_put_interface(desc->intf); 603 usb_autopm_put_interface(desc->intf);
601out: 604out:
602 mutex_unlock(&wdm_mutex); 605 mutex_unlock(&wdm_mutex);
@@ -618,7 +621,7 @@ static int wdm_release(struct inode *inode, struct file *file)
618 dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); 621 dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
619 kill_urbs(desc); 622 kill_urbs(desc);
620 if (!test_bit(WDM_DISCONNECTING, &desc->flags)) 623 if (!test_bit(WDM_DISCONNECTING, &desc->flags))
621 desc->intf->needs_remote_wakeup = 0; 624 desc->manage_power(desc->intf, 0);
622 } 625 }
623 mutex_unlock(&wdm_mutex); 626 mutex_unlock(&wdm_mutex);
624 return 0; 627 return 0;
@@ -665,7 +668,8 @@ static void wdm_rxwork(struct work_struct *work)
665 668
666/* --- hotplug --- */ 669/* --- hotplug --- */
667 670
668static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, u16 bufsize) 671static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
672 u16 bufsize, int (*manage_power)(struct usb_interface *, int))
669{ 673{
670 int rv = -ENOMEM; 674 int rv = -ENOMEM;
671 struct wdm_device *desc; 675 struct wdm_device *desc;
@@ -750,6 +754,8 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
750 desc 754 desc
751 ); 755 );
752 756
757 desc->manage_power = manage_power;
758
753 spin_lock(&wdm_device_list_lock); 759 spin_lock(&wdm_device_list_lock);
754 list_add(&desc->device_list, &wdm_device_list); 760 list_add(&desc->device_list, &wdm_device_list);
755 spin_unlock(&wdm_device_list_lock); 761 spin_unlock(&wdm_device_list_lock);
@@ -766,6 +772,19 @@ err:
766 return rv; 772 return rv;
767} 773}
768 774
775static int wdm_manage_power(struct usb_interface *intf, int on)
776{
777 /* need autopm_get/put here to ensure the usbcore sees the new value */
778 int rv = usb_autopm_get_interface(intf);
779 if (rv < 0)
780 goto err;
781
782 intf->needs_remote_wakeup = on;
783 usb_autopm_put_interface(intf);
784err:
785 return rv;
786}
787
769static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) 788static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
770{ 789{
771 int rv = -EINVAL; 790 int rv = -EINVAL;
@@ -809,12 +828,48 @@ next_desc:
809 goto err; 828 goto err;
810 ep = &iface->endpoint[0].desc; 829 ep = &iface->endpoint[0].desc;
811 830
812 rv = wdm_create(intf, ep, maxcom); 831 rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
813 832
814err: 833err:
815 return rv; 834 return rv;
816} 835}
817 836
837/**
838 * usb_cdc_wdm_register - register a WDM subdriver
839 * @intf: usb interface the subdriver will associate with
840 * @ep: interrupt endpoint to monitor for notifications
841 * @bufsize: maximum message size to support for read/write
842 *
843 * Create WDM usb class character device and associate it with intf
844 * without binding, allowing another driver to manage the interface.
845 *
846 * The subdriver will manage the given interrupt endpoint exclusively
847 * and will issue control requests referring to the given intf. It
848 * will otherwise avoid interferring, and in particular not do
849 * usb_set_intfdata/usb_get_intfdata on intf.
850 *
851 * The return value is a pointer to the subdriver's struct usb_driver.
852 * The registering driver is responsible for calling this subdriver's
853 * disconnect, suspend, resume, pre_reset and post_reset methods from
854 * its own.
855 */
856struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
857 struct usb_endpoint_descriptor *ep,
858 int bufsize,
859 int (*manage_power)(struct usb_interface *, int))
860{
861 int rv = -EINVAL;
862
863 rv = wdm_create(intf, ep, bufsize, manage_power);
864 if (rv < 0)
865 goto err;
866
867 return &wdm_driver;
868err:
869 return ERR_PTR(rv);
870}
871EXPORT_SYMBOL(usb_cdc_wdm_register);
872
818static void wdm_disconnect(struct usb_interface *intf) 873static void wdm_disconnect(struct usb_interface *intf)
819{ 874{
820 struct wdm_device *desc; 875 struct wdm_device *desc;
diff --git a/include/linux/usb/cdc-wdm.h b/include/linux/usb/cdc-wdm.h
new file mode 100644
index 000000000000..719c332620fa
--- /dev/null
+++ b/include/linux/usb/cdc-wdm.h
@@ -0,0 +1,19 @@
1/*
2 * USB CDC Device Management subdriver
3 *
4 * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * version 2 as published by the Free Software Foundation.
9 */
10
11#ifndef __LINUX_USB_CDC_WDM_H
12#define __LINUX_USB_CDC_WDM_H
13
14extern struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
15 struct usb_endpoint_descriptor *ep,
16 int bufsize,
17 int (*manage_power)(struct usb_interface *, int));
18
19#endif /* __LINUX_USB_CDC_WDM_H */