diff options
author | Bjørn Mork <bjorn@mork.no> | 2012-03-06 11:29:22 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-03-08 16:06:48 -0500 |
commit | 3cc3615749dbd1b891512d5c9a5bf4559cfa9741 (patch) | |
tree | 9e6d3b095c521d7114ac0b74ee280b31d2809d94 | |
parent | b0c13860808a528cd580fdca61aef9f73352a331 (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.c | 63 | ||||
-rw-r--r-- | include/linux/usb/cdc-wdm.h | 19 |
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 | ||
121 | static struct usb_driver wdm_driver; | 123 | static 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); |
601 | out: | 604 | out: |
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 | ||
668 | static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, u16 bufsize) | 671 | static 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 | ||
775 | static 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); | ||
784 | err: | ||
785 | return rv; | ||
786 | } | ||
787 | |||
769 | static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) | 788 | static 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 | ||
814 | err: | 833 | err: |
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 | */ | ||
856 | struct 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; | ||
868 | err: | ||
869 | return ERR_PTR(rv); | ||
870 | } | ||
871 | EXPORT_SYMBOL(usb_cdc_wdm_register); | ||
872 | |||
818 | static void wdm_disconnect(struct usb_interface *intf) | 873 | static 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 | |||
14 | extern 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 */ | ||