2009-02-18 09:39:56 +03:00
/*
* Qualcomm Serial USB driver
*
* Copyright ( c ) 2008 QUALCOMM Incorporated .
* Copyright ( c ) 2009 Greg Kroah - Hartman < gregkh @ suse . de >
* Copyright ( c ) 2009 Novell Inc .
*
* 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/tty.h>
# include <linux/tty_flip.h>
2011-07-04 00:09:31 +04:00
# include <linux/module.h>
2009-02-18 09:39:56 +03:00
# include <linux/usb.h>
# include <linux/usb/serial.h>
2010-04-01 20:31:09 +04:00
# include <linux/slab.h>
# include "usb-wwan.h"
2009-02-18 09:39:56 +03:00
# define DRIVER_AUTHOR "Qualcomm Inc"
# define DRIVER_DESC "Qualcomm USB Serial driver"
2012-02-24 23:08:43 +04:00
# define DEVICE_G1K(v, p) \
USB_DEVICE ( v , p ) , . driver_info = 1
2010-01-10 17:34:24 +03:00
static const struct usb_device_id id_table [ ] = {
2012-02-24 23:08:43 +04:00
/* Gobi 1000 devices */
{ DEVICE_G1K ( 0x05c6 , 0x9211 ) } , /* Acer Gobi QDL device */
{ DEVICE_G1K ( 0x05c6 , 0x9212 ) } , /* Acer Gobi Modem Device */
{ DEVICE_G1K ( 0x03f0 , 0x1f1d ) } , /* HP un2400 Gobi Modem Device */
{ DEVICE_G1K ( 0x03f0 , 0x201d ) } , /* HP un2400 Gobi QDL Device */
{ DEVICE_G1K ( 0x04da , 0x250d ) } , /* Panasonic Gobi Modem device */
{ DEVICE_G1K ( 0x04da , 0x250c ) } , /* Panasonic Gobi QDL device */
{ DEVICE_G1K ( 0x413c , 0x8172 ) } , /* Dell Gobi Modem device */
{ DEVICE_G1K ( 0x413c , 0x8171 ) } , /* Dell Gobi QDL device */
{ DEVICE_G1K ( 0x1410 , 0xa001 ) } , /* Novatel Gobi Modem device */
{ DEVICE_G1K ( 0x1410 , 0xa008 ) } , /* Novatel Gobi QDL device */
{ DEVICE_G1K ( 0x0b05 , 0x1776 ) } , /* Asus Gobi Modem device */
{ DEVICE_G1K ( 0x0b05 , 0x1774 ) } , /* Asus Gobi QDL device */
{ DEVICE_G1K ( 0x19d2 , 0xfff3 ) } , /* ONDA Gobi Modem device */
{ DEVICE_G1K ( 0x19d2 , 0xfff2 ) } , /* ONDA Gobi QDL device */
{ DEVICE_G1K ( 0x1557 , 0x0a80 ) } , /* OQO Gobi QDL device */
{ DEVICE_G1K ( 0x05c6 , 0x9001 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9002 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9202 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9203 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9222 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9008 ) } , /* Generic Gobi QDL device */
{ DEVICE_G1K ( 0x05c6 , 0x9009 ) } , /* Generic Gobi Modem device */
{ DEVICE_G1K ( 0x05c6 , 0x9201 ) } , /* Generic Gobi QDL device */
{ DEVICE_G1K ( 0x05c6 , 0x9221 ) } , /* Generic Gobi QDL device */
{ DEVICE_G1K ( 0x05c6 , 0x9231 ) } , /* Generic Gobi QDL device */
{ DEVICE_G1K ( 0x1f45 , 0x0001 ) } , /* Unknown Gobi QDL device */
2013-01-28 19:48:54 +04:00
{ DEVICE_G1K ( 0x1bc7 , 0x900e ) } , /* Telit Gobi QDL device */
2012-02-24 23:08:43 +04:00
/* Gobi 2000 devices */
{ USB_DEVICE ( 0x1410 , 0xa010 ) } , /* Novatel Gobi 2000 QDL device */
{ USB_DEVICE ( 0x1410 , 0xa011 ) } , /* Novatel Gobi 2000 QDL device */
{ USB_DEVICE ( 0x1410 , 0xa012 ) } , /* Novatel Gobi 2000 QDL device */
{ USB_DEVICE ( 0x1410 , 0xa013 ) } , /* Novatel Gobi 2000 QDL device */
{ USB_DEVICE ( 0x1410 , 0xa014 ) } , /* Novatel Gobi 2000 QDL device */
2010-03-10 14:36:43 +03:00
{ USB_DEVICE ( 0x413c , 0x8185 ) } , /* Dell Gobi 2000 QDL device (N0218, VU936) */
{ USB_DEVICE ( 0x413c , 0x8186 ) } , /* Dell Gobi 2000 Modem device (N0218, VU936) */
2010-07-01 23:50:07 +04:00
{ USB_DEVICE ( 0x05c6 , 0x9208 ) } , /* Generic Gobi 2000 QDL device */
{ USB_DEVICE ( 0x05c6 , 0x920b ) } , /* Generic Gobi 2000 Modem device */
2010-03-10 14:36:43 +03:00
{ USB_DEVICE ( 0x05c6 , 0x9224 ) } , /* Sony Gobi 2000 QDL device (N0279, VU730) */
{ USB_DEVICE ( 0x05c6 , 0x9225 ) } , /* Sony Gobi 2000 Modem device (N0279, VU730) */
{ USB_DEVICE ( 0x05c6 , 0x9244 ) } , /* Samsung Gobi 2000 QDL device (VL176) */
{ USB_DEVICE ( 0x05c6 , 0x9245 ) } , /* Samsung Gobi 2000 Modem device (VL176) */
{ USB_DEVICE ( 0x03f0 , 0x241d ) } , /* HP Gobi 2000 QDL device (VP412) */
{ USB_DEVICE ( 0x03f0 , 0x251d ) } , /* HP Gobi 2000 Modem device (VP412) */
{ USB_DEVICE ( 0x05c6 , 0x9214 ) } , /* Acer Gobi 2000 QDL device (VP413) */
{ USB_DEVICE ( 0x05c6 , 0x9215 ) } , /* Acer Gobi 2000 Modem device (VP413) */
{ USB_DEVICE ( 0x05c6 , 0x9264 ) } , /* Asus Gobi 2000 QDL device (VR305) */
{ USB_DEVICE ( 0x05c6 , 0x9265 ) } , /* Asus Gobi 2000 Modem device (VR305) */
{ USB_DEVICE ( 0x05c6 , 0x9234 ) } , /* Top Global Gobi 2000 QDL device (VR306) */
{ USB_DEVICE ( 0x05c6 , 0x9235 ) } , /* Top Global Gobi 2000 Modem device (VR306) */
{ USB_DEVICE ( 0x05c6 , 0x9274 ) } , /* iRex Technologies Gobi 2000 QDL device (VR307) */
{ USB_DEVICE ( 0x05c6 , 0x9275 ) } , /* iRex Technologies Gobi 2000 Modem device (VR307) */
{ USB_DEVICE ( 0x1199 , 0x9000 ) } , /* Sierra Wireless Gobi 2000 QDL device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9001 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9002 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9003 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9004 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9005 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9006 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9007 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9008 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x9009 ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
{ USB_DEVICE ( 0x1199 , 0x900a ) } , /* Sierra Wireless Gobi 2000 Modem device (VT773) */
2011-08-09 15:37:49 +04:00
{ USB_DEVICE ( 0x1199 , 0x9011 ) } , /* Sierra Wireless Gobi 2000 Modem device (MC8305) */
2010-03-10 14:36:43 +03:00
{ USB_DEVICE ( 0x16d8 , 0x8001 ) } , /* CMDTech Gobi 2000 QDL device (VU922) */
{ USB_DEVICE ( 0x16d8 , 0x8002 ) } , /* CMDTech Gobi 2000 Modem device (VU922) */
2010-04-01 20:31:10 +04:00
{ USB_DEVICE ( 0x05c6 , 0x9204 ) } , /* Gobi 2000 QDL device */
{ USB_DEVICE ( 0x05c6 , 0x9205 ) } , /* Gobi 2000 Modem device */
2012-02-02 01:07:17 +04:00
2012-02-24 23:08:43 +04:00
/* Gobi 3000 devices */
{ USB_DEVICE ( 0x03f0 , 0x371d ) } , /* HP un2430 Gobi 3000 QDL */
2012-02-02 01:07:17 +04:00
{ USB_DEVICE ( 0x05c6 , 0x920c ) } , /* Gobi 3000 QDL */
{ USB_DEVICE ( 0x05c6 , 0x920d ) } , /* Gobi 3000 Composite */
{ USB_DEVICE ( 0x1410 , 0xa020 ) } , /* Novatel Gobi 3000 QDL */
{ USB_DEVICE ( 0x1410 , 0xa021 ) } , /* Novatel Gobi 3000 Composite */
{ USB_DEVICE ( 0x413c , 0x8193 ) } , /* Dell Gobi 3000 QDL */
{ USB_DEVICE ( 0x413c , 0x8194 ) } , /* Dell Gobi 3000 Composite */
2012-07-15 18:47:40 +04:00
{ USB_DEVICE ( 0x1199 , 0x68a4 ) } , /* Sierra Wireless QDL */
{ USB_DEVICE ( 0x1199 , 0x68a5 ) } , /* Sierra Wireless Modem */
{ USB_DEVICE ( 0x1199 , 0x68a8 ) } , /* Sierra Wireless QDL */
{ USB_DEVICE ( 0x1199 , 0x68a9 ) } , /* Sierra Wireless Modem */
2012-05-24 13:19:04 +04:00
{ USB_DEVICE ( 0x1199 , 0x9010 ) } , /* Sierra Wireless Gobi 3000 QDL */
{ USB_DEVICE ( 0x1199 , 0x9012 ) } , /* Sierra Wireless Gobi 3000 QDL */
2011-09-20 22:50:51 +04:00
{ USB_DEVICE ( 0x1199 , 0x9013 ) } , /* Sierra Wireless Gobi 3000 Modem device (MC8355) */
2012-05-24 13:19:04 +04:00
{ USB_DEVICE ( 0x1199 , 0x9014 ) } , /* Sierra Wireless Gobi 3000 QDL */
{ USB_DEVICE ( 0x1199 , 0x9015 ) } , /* Sierra Wireless Gobi 3000 Modem device */
{ USB_DEVICE ( 0x1199 , 0x9018 ) } , /* Sierra Wireless Gobi 3000 QDL */
{ USB_DEVICE ( 0x1199 , 0x9019 ) } , /* Sierra Wireless Gobi 3000 Modem device */
2012-07-15 18:47:40 +04:00
{ USB_DEVICE ( 0x1199 , 0x901b ) } , /* Sierra Wireless MC7770 */
2012-02-02 01:07:17 +04:00
{ USB_DEVICE ( 0x12D1 , 0x14F0 ) } , /* Sony Gobi 3000 QDL */
{ USB_DEVICE ( 0x12D1 , 0x14F1 ) } , /* Sony Gobi 3000 Composite */
2012-07-15 18:47:40 +04:00
/* non Gobi Qualcomm serial devices */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x0f3d , 0x68a2 , 0 ) } , /* Sierra Wireless MC7700 Device Management */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x0f3d , 0x68a2 , 2 ) } , /* Sierra Wireless MC7700 NMEA */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x0f3d , 0x68a2 , 3 ) } , /* Sierra Wireless MC7700 Modem */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x114f , 0x68a2 , 0 ) } , /* Sierra Wireless MC7750 Device Management */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x114f , 0x68a2 , 2 ) } , /* Sierra Wireless MC7750 NMEA */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x114f , 0x68a2 , 3 ) } , /* Sierra Wireless MC7750 Modem */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x68a2 , 0 ) } , /* Sierra Wireless MC7710 Device Management */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x68a2 , 2 ) } , /* Sierra Wireless MC7710 NMEA */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x68a2 , 3 ) } , /* Sierra Wireless MC7710 Modem */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x901c , 0 ) } , /* Sierra Wireless EM7700 Device Management */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x901c , 2 ) } , /* Sierra Wireless EM7700 NMEA */
{ USB_DEVICE_INTERFACE_NUMBER ( 0x1199 , 0x901c , 3 ) } , /* Sierra Wireless EM7700 Modem */
2009-02-18 09:39:56 +03:00
{ } /* Terminating entry */
} ;
MODULE_DEVICE_TABLE ( usb , id_table ) ;
static int qcprobe ( struct usb_serial * serial , const struct usb_device_id * id )
{
2010-04-01 20:31:10 +04:00
struct usb_host_interface * intf = serial - > interface - > cur_altsetting ;
2012-05-16 03:27:27 +04:00
struct device * dev = & serial - > dev - > dev ;
2009-02-18 09:39:56 +03:00
int retval = - ENODEV ;
__u8 nintf ;
__u8 ifnum ;
2012-02-24 23:08:43 +04:00
bool is_gobi1k = id - > driver_info ? true : false ;
2012-07-15 18:47:36 +04:00
int altsetting = - 1 ;
2009-02-18 09:39:56 +03:00
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " Is Gobi 1000 = %d \n " , is_gobi1k ) ;
2009-02-18 09:39:56 +03:00
nintf = serial - > dev - > actconfig - > desc . bNumInterfaces ;
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " Num Interfaces = %d \n " , nintf ) ;
2010-04-01 20:31:10 +04:00
ifnum = intf - > desc . bInterfaceNumber ;
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " This Interface = %d \n " , ifnum ) ;
2009-02-18 09:39:56 +03:00
2012-07-15 18:47:38 +04:00
if ( nintf = = 1 ) {
2009-02-18 09:39:56 +03:00
/* QDL mode */
2010-04-01 20:31:10 +04:00
/* Gobi 2000 has a single altsetting, older ones have two */
if ( serial - > interface - > num_altsetting = = 2 )
2009-02-18 09:39:56 +03:00
intf = & serial - > interface - > altsetting [ 1 ] ;
2010-04-01 20:31:10 +04:00
else if ( serial - > interface - > num_altsetting > 2 )
2012-07-15 18:47:38 +04:00
goto done ;
2010-04-01 20:31:10 +04:00
if ( intf - > desc . bNumEndpoints = = 2 & &
usb_endpoint_is_bulk_in ( & intf - > endpoint [ 0 ] . desc ) & &
usb_endpoint_is_bulk_out ( & intf - > endpoint [ 1 ] . desc ) ) {
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " QDL port found \n " ) ;
2010-04-01 20:31:10 +04:00
2012-07-15 18:47:37 +04:00
if ( serial - > interface - > num_altsetting = = 1 )
2011-04-04 20:59:55 +04:00
retval = 0 ; /* Success */
2012-07-15 18:47:37 +04:00
else
altsetting = 1 ;
2009-02-18 09:39:56 +03:00
}
2012-07-15 18:47:38 +04:00
goto done ;
2009-02-18 09:39:56 +03:00
2012-07-15 18:47:38 +04:00
}
2012-07-15 18:47:39 +04:00
/* allow any number of interfaces when doing direct interface match */
if ( id - > match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER ) {
dev_dbg ( dev , " Generic Qualcomm serial interface found \n " ) ;
altsetting = 0 ;
goto done ;
}
2012-07-15 18:47:38 +04:00
if ( nintf < 3 | | nintf > 4 ) {
dev_err ( dev , " unknown number of interfaces: %d \n " , nintf ) ;
goto done ;
}
/* default to enabling interface */
altsetting = 0 ;
2012-02-24 23:08:43 +04:00
2012-07-23 23:26:07 +04:00
/* Composite mode; don't bind to the QMI/net interface as that
* gets handled by other drivers .
*/
if ( is_gobi1k ) {
2012-02-24 23:08:43 +04:00
/* Gobi 1K USB layout:
2013-03-13 18:58:18 +04:00
* 0 : DM / DIAG ( use libqcdm from ModemManager for communication )
2012-02-24 23:08:43 +04:00
* 1 : serial port ( doesn ' t respond )
* 2 : AT - capable modem port
* 3 : QMI / net
2012-07-23 23:26:07 +04:00
*/
2013-03-13 18:58:18 +04:00
if ( ifnum = = 0 ) {
dev_dbg ( dev , " Gobi 1K DM/DIAG interface found \n " ) ;
altsetting = 1 ;
} else if ( ifnum = = 2 )
2012-07-23 23:26:07 +04:00
dev_dbg ( dev , " Modem port found \n " ) ;
else
altsetting = - 1 ;
} else {
/* Gobi 2K+ USB layout:
2012-02-24 23:08:43 +04:00
* 0 : QMI / net
* 1 : DM / DIAG ( use libqcdm from ModemManager for communication )
* 2 : AT - capable modem port
* 3 : NMEA
*/
2012-07-23 23:26:07 +04:00
switch ( ifnum ) {
case 0 :
/* Don't claim the QMI/net interface */
2012-07-15 18:47:38 +04:00
altsetting = - 1 ;
2012-07-23 23:26:07 +04:00
break ;
case 1 :
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " Gobi 2K+ DM/DIAG interface found \n " ) ;
2012-07-23 23:26:07 +04:00
break ;
case 2 :
dev_dbg ( dev , " Modem port found \n " ) ;
break ;
case 3 :
2010-09-24 20:12:01 +04:00
/*
* NMEA ( serial line 9600 8 N1 )
* # echo " \ $GPS_START " > / dev / ttyUSBx
* # echo " \ $GPS_STOP " > / dev / ttyUSBx
*/
2012-05-16 03:27:27 +04:00
dev_dbg ( dev , " Gobi 2K+ NMEA GPS interface found \n " ) ;
2012-07-23 23:26:07 +04:00
break ;
}
2009-02-18 09:39:56 +03:00
}
2012-07-15 18:47:38 +04:00
done :
2012-07-15 18:47:36 +04:00
if ( altsetting > = 0 ) {
retval = usb_set_interface ( serial - > dev , ifnum , altsetting ) ;
if ( retval < 0 ) {
dev_err ( dev ,
" Could not set interface, error %d \n " ,
retval ) ;
retval = - ENODEV ;
}
}
2009-02-18 09:39:56 +03:00
return retval ;
}
2012-10-25 17:42:41 +04:00
static int qc_attach ( struct usb_serial * serial )
{
struct usb_wwan_intf_private * data ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
spin_lock_init ( & data - > susp_lock ) ;
usb_set_serial_data ( serial , data ) ;
return 0 ;
}
2011-04-04 20:57:37 +04:00
static void qc_release ( struct usb_serial * serial )
{
struct usb_wwan_intf_private * priv = usb_get_serial_data ( serial ) ;
usb_set_serial_data ( serial , NULL ) ;
kfree ( priv ) ;
}
2009-02-18 09:39:56 +03:00
static struct usb_serial_driver qcdevice = {
. driver = {
. owner = THIS_MODULE ,
. name = " qcserial " ,
} ,
. description = " Qualcomm USB modem " ,
. id_table = id_table ,
. num_ports = 1 ,
. probe = qcprobe ,
2010-04-01 20:31:09 +04:00
. open = usb_wwan_open ,
. close = usb_wwan_close ,
. write = usb_wwan_write ,
. write_room = usb_wwan_write_room ,
. chars_in_buffer = usb_wwan_chars_in_buffer ,
2012-10-25 17:42:41 +04:00
. attach = qc_attach ,
2011-04-04 20:57:37 +04:00
. release = qc_release ,
2012-10-25 12:29:16 +04:00
. port_probe = usb_wwan_port_probe ,
2012-07-27 03:11:41 +04:00
. port_remove = usb_wwan_port_remove ,
2010-04-01 20:31:09 +04:00
# ifdef CONFIG_PM
. suspend = usb_wwan_suspend ,
. resume = usb_wwan_resume ,
# endif
2009-02-18 09:39:56 +03:00
} ;
2012-02-23 23:57:25 +04:00
static struct usb_serial_driver * const serial_drivers [ ] = {
& qcdevice , NULL
} ;
2012-05-09 02:46:14 +04:00
module_usb_serial_driver ( serial_drivers , id_table ) ;
2009-02-18 09:39:56 +03:00
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL v2 " ) ;