2012-01-19 15:37:22 +00:00
/*
* Copyright ( c ) 2012 Bjørn Mork < bjorn @ mork . no >
*
2012-06-19 00:42:01 +00:00
* The probing code is heavily inspired by cdc_ether , which is :
* Copyright ( C ) 2003 - 2005 by David Brownell
* Copyright ( C ) 2006 by Ole Andre Vadla Ravnas ( ActiveSync )
*
2012-01-19 15:37:22 +00:00
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation .
*/
# include <linux/module.h>
# include <linux/netdevice.h>
# include <linux/ethtool.h>
# include <linux/mii.h>
# include <linux/usb.h>
# include <linux/usb/cdc.h>
# include <linux/usb/usbnet.h>
2012-03-09 12:35:05 +01:00
# include <linux/usb/cdc-wdm.h>
2012-01-19 15:37:22 +00:00
2012-06-19 00:42:01 +00:00
/* This driver supports wwan (3G/LTE/?) devices using a vendor
2012-01-19 15:37:22 +00:00
* specific management protocol called Qualcomm MSM Interface ( QMI ) -
* in addition to the more common AT commands over serial interface
* management
*
* QMI is wrapped in CDC , using CDC encapsulated commands on the
* control ( " master " ) interface of a two - interface CDC Union
* resembling standard CDC ECM . The devices do not use the control
* interface for any other CDC messages . Most likely because the
* management protocol is used in place of the standard CDC
* notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE
*
2012-06-19 00:42:01 +00:00
* Alternatively , control and data functions can be combined in a
* single USB interface .
*
2012-01-19 15:37:22 +00:00
* Handling a protocol like QMI is out of the scope for any driver .
2012-06-19 00:42:01 +00:00
* It is exported as a character device using the cdc - wdm driver as
* a subdriver , enabling userspace applications ( " modem managers " ) to
* handle it .
2012-01-19 15:37:22 +00:00
*
* These devices may alternatively / additionally be configured using AT
2012-06-19 00:42:01 +00:00
* commands on a serial interface
2012-01-19 15:37:22 +00:00
*/
2012-06-19 00:41:59 +00:00
/* driver specific data */
struct qmi_wwan_state {
struct usb_driver * subdriver ;
atomic_t pmcount ;
2012-06-19 00:42:00 +00:00
unsigned long unused ;
struct usb_interface * control ;
struct usb_interface * data ;
2012-06-19 00:41:59 +00:00
} ;
2012-06-19 00:42:00 +00:00
/* using a counter to merge subdriver requests with our own into a combined state */
static int qmi_wwan_manage_power ( struct usbnet * dev , int on )
{
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
int rv = 0 ;
dev_dbg ( & dev - > intf - > dev , " %s() pmcount=%d, on=%d \n " , __func__ , atomic_read ( & info - > pmcount ) , on ) ;
if ( ( on & & atomic_add_return ( 1 , & info - > pmcount ) = = 1 ) | | ( ! on & & atomic_dec_and_test ( & info - > pmcount ) ) ) {
/* need autopm_get/put here to ensure the usbcore sees the new value */
rv = usb_autopm_get_interface ( dev - > intf ) ;
if ( rv < 0 )
goto err ;
dev - > intf - > needs_remote_wakeup = on ;
usb_autopm_put_interface ( dev - > intf ) ;
}
err :
return rv ;
}
static int qmi_wwan_cdc_wdm_manage_power ( struct usb_interface * intf , int on )
{
struct usbnet * dev = usb_get_intfdata ( intf ) ;
2012-06-28 17:37:00 -07:00
/* can be called while disconnecting */
if ( ! dev )
return 0 ;
2012-06-19 00:42:00 +00:00
return qmi_wwan_manage_power ( dev , on ) ;
}
/* collect all three endpoints and register subdriver */
static int qmi_wwan_register_subdriver ( struct usbnet * dev )
{
int rv ;
struct usb_driver * subdriver = NULL ;
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
/* collect bulk endpoints */
rv = usbnet_get_endpoints ( dev , info - > data ) ;
if ( rv < 0 )
goto err ;
/* update status endpoint if separate control interface */
if ( info - > control ! = info - > data )
dev - > status = & info - > control - > cur_altsetting - > endpoint [ 0 ] ;
/* require interrupt endpoint for subdriver */
if ( ! dev - > status ) {
rv = - EINVAL ;
goto err ;
}
/* for subdriver power management */
atomic_set ( & info - > pmcount , 0 ) ;
/* register subdriver */
subdriver = usb_cdc_wdm_register ( info - > control , & dev - > status - > desc , 512 , & qmi_wwan_cdc_wdm_manage_power ) ;
if ( IS_ERR ( subdriver ) ) {
dev_err ( & info - > control - > dev , " subdriver registration failed \n " ) ;
rv = PTR_ERR ( subdriver ) ;
goto err ;
}
/* prevent usbnet from using status endpoint */
dev - > status = NULL ;
/* save subdriver struct for suspend/resume wrappers */
info - > subdriver = subdriver ;
err :
return rv ;
}
2012-01-19 15:37:22 +00:00
static int qmi_wwan_bind ( struct usbnet * dev , struct usb_interface * intf )
{
int status = - 1 ;
u8 * buf = intf - > cur_altsetting - > extra ;
int len = intf - > cur_altsetting - > extralen ;
struct usb_interface_descriptor * desc = & intf - > cur_altsetting - > desc ;
struct usb_cdc_union_desc * cdc_union = NULL ;
struct usb_cdc_ether_desc * cdc_ether = NULL ;
u32 found = 0 ;
2012-06-19 00:42:01 +00:00
struct usb_driver * driver = driver_of ( intf ) ;
2012-06-19 00:41:59 +00:00
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
BUILD_BUG_ON ( ( sizeof ( ( ( struct usbnet * ) 0 ) - > data ) < sizeof ( struct qmi_wwan_state ) ) ) ;
2012-03-09 12:35:05 +01:00
2012-06-19 00:42:01 +00:00
/* require a single interrupt status endpoint for subdriver */
if ( intf - > cur_altsetting - > desc . bNumEndpoints ! = 1 )
goto err ;
2012-01-19 15:37:22 +00:00
while ( len > 3 ) {
struct usb_descriptor_header * h = ( void * ) buf ;
/* ignore any misplaced descriptors */
if ( h - > bDescriptorType ! = USB_DT_CS_INTERFACE )
goto next_desc ;
/* buf[2] is CDC descriptor subtype */
switch ( buf [ 2 ] ) {
case USB_CDC_HEADER_TYPE :
if ( found & 1 < < USB_CDC_HEADER_TYPE ) {
dev_dbg ( & intf - > dev , " extra CDC header \n " ) ;
goto err ;
}
if ( h - > bLength ! = sizeof ( struct usb_cdc_header_desc ) ) {
dev_dbg ( & intf - > dev , " CDC header len %u \n " , h - > bLength ) ;
goto err ;
}
break ;
case USB_CDC_UNION_TYPE :
if ( found & 1 < < USB_CDC_UNION_TYPE ) {
dev_dbg ( & intf - > dev , " extra CDC union \n " ) ;
goto err ;
}
if ( h - > bLength ! = sizeof ( struct usb_cdc_union_desc ) ) {
dev_dbg ( & intf - > dev , " CDC union len %u \n " , h - > bLength ) ;
goto err ;
}
cdc_union = ( struct usb_cdc_union_desc * ) buf ;
break ;
case USB_CDC_ETHERNET_TYPE :
if ( found & 1 < < USB_CDC_ETHERNET_TYPE ) {
dev_dbg ( & intf - > dev , " extra CDC ether \n " ) ;
goto err ;
}
if ( h - > bLength ! = sizeof ( struct usb_cdc_ether_desc ) ) {
dev_dbg ( & intf - > dev , " CDC ether len %u \n " , h - > bLength ) ;
goto err ;
}
cdc_ether = ( struct usb_cdc_ether_desc * ) buf ;
break ;
}
/*
* Remember which CDC functional descriptors we ' ve seen . Works
* for all types we care about , of which USB_CDC_ETHERNET_TYPE
* ( 0x0f ) is the highest numbered
*/
if ( buf [ 2 ] < 32 )
found | = 1 < < buf [ 2 ] ;
next_desc :
len - = h - > bLength ;
buf + = h - > bLength ;
}
/* did we find all the required ones? */
2012-06-25 22:39:45 +00:00
if ( ! ( found & ( 1 < < USB_CDC_HEADER_TYPE ) ) | |
! ( found & ( 1 < < USB_CDC_UNION_TYPE ) ) ) {
2012-01-19 15:37:22 +00:00
dev_err ( & intf - > dev , " CDC functional descriptors missing \n " ) ;
goto err ;
}
2012-06-19 00:42:01 +00:00
/* verify CDC Union */
if ( desc - > bInterfaceNumber ! = cdc_union - > bMasterInterface0 ) {
dev_err ( & intf - > dev , " bogus CDC Union: master=%u \n " , cdc_union - > bMasterInterface0 ) ;
goto err ;
}
/* need to save these for unbind */
info - > control = intf ;
info - > data = usb_ifnum_to_if ( dev - > udev , cdc_union - > bSlaveInterface0 ) ;
if ( ! info - > data ) {
dev_err ( & intf - > dev , " bogus CDC Union: slave=%u \n " , cdc_union - > bSlaveInterface0 ) ;
2012-01-19 15:37:22 +00:00
goto err ;
}
/* errors aren't fatal - we can live with the dynamic address */
if ( cdc_ether ) {
dev - > hard_mtu = le16_to_cpu ( cdc_ether - > wMaxSegmentSize ) ;
usbnet_get_ethernet_addr ( dev , cdc_ether - > iMACAddress ) ;
}
2012-06-19 00:42:01 +00:00
/* claim data interface and set it up */
status = usb_driver_claim_interface ( driver , info - > data , dev ) ;
if ( status < 0 )
goto err ;
2012-01-19 15:37:22 +00:00
2012-06-19 00:42:01 +00:00
status = qmi_wwan_register_subdriver ( dev ) ;
if ( status < 0 ) {
usb_set_intfdata ( info - > data , NULL ) ;
usb_driver_release_interface ( driver , info - > data ) ;
}
2012-01-19 15:37:22 +00:00
err :
return status ;
}
2012-03-09 12:35:05 +01:00
/* Some devices combine the "control" and "data" functions into a
* single interface with all three endpoints : interrupt + bulk in and
* out
2012-06-19 00:42:01 +00:00
*/
2012-03-09 12:35:05 +01:00
static int qmi_wwan_bind_shared ( struct usbnet * dev , struct usb_interface * intf )
{
2012-06-19 00:41:59 +00:00
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
2012-03-09 12:35:05 +01:00
2012-06-19 00:42:00 +00:00
/* control and data is shared */
info - > control = intf ;
info - > data = intf ;
2012-08-12 09:16:30 +00:00
return qmi_wwan_register_subdriver ( dev ) ;
2012-03-09 12:35:05 +01:00
}
2012-06-19 00:42:01 +00:00
static void qmi_wwan_unbind ( struct usbnet * dev , struct usb_interface * intf )
2012-03-09 12:35:05 +01:00
{
2012-06-19 00:41:59 +00:00
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
2012-06-19 00:42:01 +00:00
struct usb_driver * driver = driver_of ( intf ) ;
struct usb_interface * other ;
2012-03-09 12:35:05 +01:00
2012-06-19 00:41:59 +00:00
if ( info - > subdriver & & info - > subdriver - > disconnect )
2012-06-19 00:42:01 +00:00
info - > subdriver - > disconnect ( info - > control ) ;
/* allow user to unbind using either control or data */
if ( intf = = info - > control )
other = info - > data ;
else
other = info - > control ;
/* only if not shared */
if ( other & & intf ! = other ) {
usb_set_intfdata ( other , NULL ) ;
usb_driver_release_interface ( driver , other ) ;
}
2012-03-09 12:35:05 +01:00
2012-06-19 00:41:59 +00:00
info - > subdriver = NULL ;
2012-06-19 00:42:01 +00:00
info - > data = NULL ;
info - > control = NULL ;
2012-03-09 12:35:05 +01:00
}
/* suspend/resume wrappers calling both usbnet and the cdc-wdm
* subdriver if present .
*
* NOTE : cdc - wdm also supports pre / post_reset , but we cannot provide
* wrappers for those without adding usbnet reset support first .
*/
static int qmi_wwan_suspend ( struct usb_interface * intf , pm_message_t message )
{
struct usbnet * dev = usb_get_intfdata ( intf ) ;
2012-06-19 00:41:59 +00:00
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
2012-03-09 12:35:05 +01:00
int ret ;
ret = usbnet_suspend ( intf , message ) ;
if ( ret < 0 )
goto err ;
2012-06-19 00:41:59 +00:00
if ( info - > subdriver & & info - > subdriver - > suspend )
ret = info - > subdriver - > suspend ( intf , message ) ;
2012-03-09 12:35:05 +01:00
if ( ret < 0 )
usbnet_resume ( intf ) ;
err :
return ret ;
}
static int qmi_wwan_resume ( struct usb_interface * intf )
{
struct usbnet * dev = usb_get_intfdata ( intf ) ;
2012-06-19 00:41:59 +00:00
struct qmi_wwan_state * info = ( void * ) & dev - > data ;
2012-03-09 12:35:05 +01:00
int ret = 0 ;
2012-06-19 00:41:59 +00:00
if ( info - > subdriver & & info - > subdriver - > resume )
ret = info - > subdriver - > resume ( intf ) ;
2012-03-09 12:35:05 +01:00
if ( ret < 0 )
goto err ;
ret = usbnet_resume ( intf ) ;
2012-06-19 00:41:59 +00:00
if ( ret < 0 & & info - > subdriver & & info - > subdriver - > resume & & info - > subdriver - > suspend )
info - > subdriver - > suspend ( intf , PMSG_SUSPEND ) ;
2012-03-09 12:35:05 +01:00
err :
return ret ;
}
2012-01-19 15:37:22 +00:00
static const struct driver_info qmi_wwan_info = {
2012-06-19 00:42:02 +00:00
. description = " WWAN/QMI device " ,
2012-01-19 15:37:22 +00:00
. flags = FLAG_WWAN ,
. bind = qmi_wwan_bind ,
2012-06-19 00:42:01 +00:00
. unbind = qmi_wwan_unbind ,
2012-01-19 15:37:22 +00:00
. manage_power = qmi_wwan_manage_power ,
} ;
2012-03-09 12:35:05 +01:00
static const struct driver_info qmi_wwan_shared = {
2012-06-19 00:42:02 +00:00
. description = " WWAN/QMI device " ,
2012-03-09 12:35:05 +01:00
. flags = FLAG_WWAN ,
. bind = qmi_wwan_bind_shared ,
2012-06-19 00:42:01 +00:00
. unbind = qmi_wwan_unbind ,
2012-03-09 12:35:05 +01:00
. manage_power = qmi_wwan_manage_power ,
} ;
2012-01-19 15:37:22 +00:00
# define HUAWEI_VENDOR_ID 0x12D1
2012-06-21 02:45:58 +00:00
2012-08-12 09:16:30 +00:00
/* map QMI/wwan function by a fixed interface number */
# define QMI_FIXED_INTF(vend, prod, num) \
USB_DEVICE_INTERFACE_NUMBER ( vend , prod , num ) , \
. driver_info = ( unsigned long ) & qmi_wwan_shared
2012-06-21 02:45:58 +00:00
/* Gobi 1000 QMI/wwan interface number is 3 according to qcserial */
# define QMI_GOBI1K_DEVICE(vend, prod) \
2012-08-12 09:16:30 +00:00
QMI_FIXED_INTF ( vend , prod , 3 )
2012-06-21 02:45:58 +00:00
2012-08-12 09:16:30 +00:00
/* Gobi 2000/3000 QMI/wwan interface number is 0 according to qcserial */
2012-03-09 12:35:06 +01:00
# define QMI_GOBI_DEVICE(vend, prod) \
2012-08-12 09:16:30 +00:00
QMI_FIXED_INTF ( vend , prod , 0 )
2012-01-19 15:37:22 +00:00
static const struct usb_device_id products [ ] = {
2012-08-12 09:16:30 +00:00
/* 1. CDC ECM like devices match on the control interface */
2012-03-09 12:35:05 +01:00
{ /* Huawei E392, E398 and possibly others sharing both device id and more... */
2012-08-12 09:16:32 +00:00
USB_VENDOR_AND_INTERFACE_INFO ( HUAWEI_VENDOR_ID , USB_CLASS_VENDOR_SPEC , 1 , 9 ) ,
2012-03-09 12:35:05 +01:00
. driver_info = ( unsigned long ) & qmi_wwan_info ,
} ,
2012-05-19 07:20:31 +00:00
{ /* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */
2012-08-12 09:16:32 +00:00
USB_VENDOR_AND_INTERFACE_INFO ( HUAWEI_VENDOR_ID , USB_CLASS_VENDOR_SPEC , 1 , 57 ) ,
2012-05-19 07:20:31 +00:00
. driver_info = ( unsigned long ) & qmi_wwan_info ,
} ,
2012-08-12 09:16:30 +00:00
/* 2. Combined interface devices matching on class+protocol */
{ /* Huawei E392, E398 and possibly others in "Windows mode" */
2012-08-12 09:16:32 +00:00
USB_VENDOR_AND_INTERFACE_INFO ( HUAWEI_VENDOR_ID , USB_CLASS_VENDOR_SPEC , 1 , 17 ) ,
2012-03-09 12:35:05 +01:00
. driver_info = ( unsigned long ) & qmi_wwan_shared ,
} ,
2012-03-09 12:35:06 +01:00
{ /* Pantech UML290 */
2012-08-12 09:16:32 +00:00
USB_DEVICE_AND_INTERFACE_INFO ( 0x106c , 0x3718 , USB_CLASS_VENDOR_SPEC , 0xf0 , 0xff ) ,
2012-03-09 12:35:06 +01:00
. driver_info = ( unsigned long ) & qmi_wwan_shared ,
} ,
2012-08-15 03:42:57 +00:00
{ /* Pantech UML290 - newer firmware */
USB_DEVICE_AND_INTERFACE_INFO ( 0x106c , 0x3718 , USB_CLASS_VENDOR_SPEC , 0xf1 , 0xff ) ,
. driver_info = ( unsigned long ) & qmi_wwan_shared ,
} ,
2012-06-21 02:45:58 +00:00
2012-08-12 09:16:30 +00:00
/* 3. Combined interface devices matching on interface number */
{ QMI_FIXED_INTF ( 0x19d2 , 0x0055 , 1 ) } , /* ZTE (Vodafone) K3520-Z */
{ QMI_FIXED_INTF ( 0x19d2 , 0x0063 , 4 ) } , /* ZTE (Vodafone) K3565-Z */
{ QMI_FIXED_INTF ( 0x19d2 , 0x0104 , 4 ) } , /* ZTE (Vodafone) K4505-Z */
{ QMI_FIXED_INTF ( 0x19d2 , 0x0167 , 4 ) } , /* ZTE MF820D */
{ QMI_FIXED_INTF ( 0x19d2 , 0x0326 , 4 ) } , /* ZTE MF821D */
{ QMI_FIXED_INTF ( 0x19d2 , 0x1008 , 4 ) } , /* ZTE (Vodafone) K3570-Z */
{ QMI_FIXED_INTF ( 0x19d2 , 0x1010 , 4 ) } , /* ZTE (Vodafone) K3571-Z */
2012-08-15 03:42:57 +00:00
{ QMI_FIXED_INTF ( 0x19d2 , 0x1018 , 3 ) } , /* ZTE (Vodafone) K5006-Z */
2012-08-12 09:16:30 +00:00
{ QMI_FIXED_INTF ( 0x19d2 , 0x1402 , 2 ) } , /* ZTE MF60 */
{ QMI_FIXED_INTF ( 0x19d2 , 0x2002 , 4 ) } , /* ZTE (Vodafone) K3765-Z */
2012-08-12 09:16:31 +00:00
{ QMI_FIXED_INTF ( 0x0f3d , 0x68a2 , 8 ) } , /* Sierra Wireless MC7700 */
{ QMI_FIXED_INTF ( 0x114f , 0x68a2 , 8 ) } , /* Sierra Wireless MC7750 */
2012-08-12 09:16:30 +00:00
{ QMI_FIXED_INTF ( 0x1199 , 0x68a2 , 8 ) } , /* Sierra Wireless MC7710 in QMI mode */
{ QMI_FIXED_INTF ( 0x1199 , 0x68a2 , 19 ) } , /* Sierra Wireless MC7710 in QMI mode */
2012-08-12 09:16:31 +00:00
{ QMI_FIXED_INTF ( 0x1199 , 0x901c , 8 ) } , /* Sierra Wireless EM7700 */
2012-08-12 09:16:30 +00:00
/* 4. Gobi 1000 devices */
2012-06-21 02:45:58 +00:00
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9212 ) } , /* Acer Gobi Modem Device */
{ QMI_GOBI1K_DEVICE ( 0x03f0 , 0x1f1d ) } , /* HP un2400 Gobi Modem Device */
{ QMI_GOBI1K_DEVICE ( 0x03f0 , 0x371d ) } , /* HP un2430 Mobile Broadband Module */
{ QMI_GOBI1K_DEVICE ( 0x04da , 0x250d ) } , /* Panasonic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x413c , 0x8172 ) } , /* Dell Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x1410 , 0xa001 ) } , /* Novatel Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x0b05 , 0x1776 ) } , /* Asus Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x19d2 , 0xfff3 ) } , /* ONDA Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9001 ) } , /* Generic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9002 ) } , /* Generic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9202 ) } , /* Generic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9203 ) } , /* Generic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9222 ) } , /* Generic Gobi Modem device */
{ QMI_GOBI1K_DEVICE ( 0x05c6 , 0x9009 ) } , /* Generic Gobi Modem device */
2012-08-12 09:16:30 +00:00
/* 5. Gobi 2000 and 3000 devices */
2012-03-09 12:35:06 +01:00
{ QMI_GOBI_DEVICE ( 0x413c , 0x8186 ) } , /* Dell Gobi 2000 Modem device (N0218, VU936) */
2012-09-01 03:47:26 +00:00
{ QMI_GOBI_DEVICE ( 0x413c , 0x8194 ) } , /* Dell Gobi 3000 Composite */
2012-03-09 12:35:06 +01:00
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x920b ) } , /* Generic Gobi 2000 Modem device */
2012-09-01 03:47:26 +00:00
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x920d ) } , /* Gobi 3000 Composite */
2012-03-09 12:35:06 +01:00
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9225 ) } , /* Sony Gobi 2000 Modem device (N0279, VU730) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9245 ) } , /* Samsung Gobi 2000 Modem device (VL176) */
{ QMI_GOBI_DEVICE ( 0x03f0 , 0x251d ) } , /* HP Gobi 2000 Modem device (VP412) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9215 ) } , /* Acer Gobi 2000 Modem device (VP413) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9265 ) } , /* Asus Gobi 2000 Modem device (VR305) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9235 ) } , /* Top Global Gobi 2000 Modem device (VR306) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9275 ) } , /* iRex Technologies Gobi 2000 Modem device (VR307) */
2012-08-12 09:16:31 +00:00
{ QMI_GOBI_DEVICE ( 0x1199 , 0x68a5 ) } , /* Sierra Wireless Modem */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x68a9 ) } , /* Sierra Wireless Modem */
2012-03-09 12:35:06 +01:00
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9001 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9002 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9003 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9004 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9005 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9006 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9007 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9008 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9009 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x900a ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9011 ) } , /* Sierra Wireless Gobi 2000 Modem device (MC8305) */
2012-08-12 09:16:31 +00:00
{ QMI_FIXED_INTF ( 0x1199 , 0x9011 , 5 ) } , /* alternate interface number!? */
2012-03-09 12:35:06 +01:00
{ QMI_GOBI_DEVICE ( 0x16d8 , 0x8002 ) } , /* CMDTech Gobi 2000 Modem device (VU922) */
{ QMI_GOBI_DEVICE ( 0x05c6 , 0x9205 ) } , /* Gobi 2000 Modem device */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9013 ) } , /* Sierra Wireless Gobi 3000 Modem device (MC8355) */
2012-05-23 23:19:32 +00:00
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9015 ) } , /* Sierra Wireless Gobi 3000 Modem device */
{ QMI_GOBI_DEVICE ( 0x1199 , 0x9019 ) } , /* Sierra Wireless Gobi 3000 Modem device */
2012-08-12 09:16:31 +00:00
{ QMI_GOBI_DEVICE ( 0x1199 , 0x901b ) } , /* Sierra Wireless MC7770 */
2012-09-01 03:47:26 +00:00
{ QMI_GOBI_DEVICE ( 0x12d1 , 0x14f1 ) } , /* Sony Gobi 3000 Composite */
2012-08-28 02:30:32 +00:00
{ QMI_GOBI_DEVICE ( 0x1410 , 0xa021 ) } , /* Foxconn Gobi 3000 Modem device (Novatel E396) */
2012-08-12 09:16:31 +00:00
2012-03-09 12:35:06 +01:00
{ } /* END */
2012-01-19 15:37:22 +00:00
} ;
MODULE_DEVICE_TABLE ( usb , products ) ;
2012-07-17 11:14:32 +00:00
static int qmi_wwan_probe ( struct usb_interface * intf , const struct usb_device_id * prod )
{
struct usb_device_id * id = ( struct usb_device_id * ) prod ;
/* Workaround to enable dynamic IDs. This disables usbnet
* blacklisting functionality . Which , if required , can be
* reimplemented here by using a magic " blacklist " value
* instead of 0 in the static device id table
*/
if ( ! id - > driver_info ) {
dev_dbg ( & intf - > dev , " setting defaults for dynamic device id \n " ) ;
id - > driver_info = ( unsigned long ) & qmi_wwan_shared ;
}
return usbnet_probe ( intf , id ) ;
}
2012-01-19 15:37:22 +00:00
static struct usb_driver qmi_wwan_driver = {
. name = " qmi_wwan " ,
. id_table = products ,
2012-07-17 11:14:32 +00:00
. probe = qmi_wwan_probe ,
2012-01-19 15:37:22 +00:00
. disconnect = usbnet_disconnect ,
2012-03-09 12:35:05 +01:00
. suspend = qmi_wwan_suspend ,
. resume = qmi_wwan_resume ,
. reset_resume = qmi_wwan_resume ,
2012-01-19 15:37:22 +00:00
. supports_autosuspend = 1 ,
2012-04-23 10:08:51 -07:00
. disable_hub_initiated_lpm = 1 ,
2012-01-19 15:37:22 +00:00
} ;
2012-06-19 00:42:03 +00:00
module_usb_driver ( qmi_wwan_driver ) ;
2012-01-19 15:37:22 +00:00
MODULE_AUTHOR ( " Bjørn Mork <bjorn@mork.no> " ) ;
MODULE_DESCRIPTION ( " Qualcomm MSM Interface (QMI) WWAN driver " ) ;
MODULE_LICENSE ( " GPL " ) ;