/*
- USB Driver for GSM modems
+ Option Card (PCMCIA to) USB to Serial Driver
Copyright (C) 2005 Matthias Urlichs <smurf@smurf.noris.de>
Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
- History: see the git log.
+ History:
+
+ 2005-05-19 v0.1 Initial version, based on incomplete docs
+ and analysis of misbehavior with the standard driver
+ 2005-05-20 v0.2 Extended the input buffer to avoid losing
+ random 64-byte chunks of data
+ 2005-05-21 v0.3 implemented chars_in_buffer()
+ turned on low_latency
+ simplified the code somewhat
+ 2005-05-24 v0.4 option_write() sometimes deadlocked under heavy load
+ removed some dead code
+ added sponsor notice
+ coding style clean-up
+ 2005-06-20 v0.4.1 add missing braces :-/
+ killed end-of-line whitespace
+ 2005-07-15 v0.4.2 rename WLAN product to FUSION, add FUSION2
+ 2005-09-10 v0.4.3 added HUAWEI E600 card and Audiovox AirCard
+ 2005-09-20 v0.4.4 increased recv buffer size: the card sometimes
+ wants to send >2000 bytes.
Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
- This driver exists because the "normal" serial driver doesn't work too well
- with GSM modems. Issues:
- - data loss -- one single Receive URB is not nearly enough
- - nonstandard flow (Option devices) control
- - controlling the baud rate doesn't make sense
-
- This driver is named "option" because the most common device it's
- used for is a PC-Card (with an internal OHCI-USB interface, behind
- which the GSM interface sits), made by Option Inc.
-
- Some of the "one port" devices actually exhibit multiple USB instances
- on the USB bus. This is not a bug, these ports are used for different
- device features.
*/
-#define DRIVER_VERSION "v0.7.1"
+#define DRIVER_VERSION "v0.4"
#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
-#define DRIVER_DESC "USB Driver for GSM modems"
+#define DRIVER_DESC "Option Card (PC-Card to) USB to Serial Driver"
+#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
-#include <linux/usb/serial.h>
+#include "usb-serial.h"
/* Function prototypes */
static int option_open(struct usb_serial_port *port, struct file *filp);
static int option_send_setup(struct usb_serial_port *port);
/* Vendor and product IDs */
-#define OPTION_VENDOR_ID 0x0AF0
-#define HUAWEI_VENDOR_ID 0x12D1
-#define AUDIOVOX_VENDOR_ID 0x0F3D
-#define NOVATELWIRELESS_VENDOR_ID 0x1410
-#define ANYDATA_VENDOR_ID 0x16d5
-
-#define OPTION_PRODUCT_OLD 0x5000
-#define OPTION_PRODUCT_FUSION 0x6000
-#define OPTION_PRODUCT_FUSION2 0x6300
-#define OPTION_PRODUCT_COBRA 0x6500
-#define OPTION_PRODUCT_COBRA2 0x6600
-#define HUAWEI_PRODUCT_E600 0x1001
-#define AUDIOVOX_PRODUCT_AIRCARD 0x0112
-#define NOVATELWIRELESS_PRODUCT_U740 0x1400
-#define ANYDATA_PRODUCT_ID 0x6501
+#define OPTION_VENDOR_ID 0x0AF0
+#define HUAWEI_VENDOR_ID 0x12D1
+#define AUDIOVOX_VENDOR_ID 0x0F3D
-static struct usb_device_id option_ids[] = {
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_OLD) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION2) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA2) },
- { USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600) },
- { USB_DEVICE(AUDIOVOX_VENDOR_ID, AUDIOVOX_PRODUCT_AIRCARD) },
- { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID,NOVATELWIRELESS_PRODUCT_U740) },
- { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ID) },
- { } /* Terminating entry */
-};
+#define OPTION_PRODUCT_OLD 0x5000
+#define OPTION_PRODUCT_FUSION 0x6000
+#define OPTION_PRODUCT_FUSION2 0x6300
+#define HUAWEI_PRODUCT_E600 0x1001
+#define AUDIOVOX_PRODUCT_AIRCARD 0x0112
-static struct usb_device_id option_ids1[] = {
+static struct usb_device_id option_ids[] = {
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_OLD) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION2) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA) },
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA2) },
{ USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600) },
{ USB_DEVICE(AUDIOVOX_VENDOR_ID, AUDIOVOX_PRODUCT_AIRCARD) },
- { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID,NOVATELWIRELESS_PRODUCT_U740) },
- { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ID) },
{ } /* Terminating entry */
};
.no_dynamic_id = 1,
};
-/* The card has three separate interfaces, which the serial driver
+/* The card has three separate interfaces, wich the serial driver
* recognizes separately, thus num_port=1.
*/
-
-static struct usb_serial_driver option_1port_device = {
+static struct usb_serial_driver option_3port_device = {
.driver = {
.owner = THIS_MODULE,
- .name = "option1",
+ .name = "option",
},
- .description = "GSM modem (1-port)",
- .id_table = option_ids1,
+ .description = "Option 3G data card",
+ .id_table = option_ids,
.num_interrupt_in = NUM_DONT_CARE,
.num_bulk_in = NUM_DONT_CARE,
.num_bulk_out = NUM_DONT_CARE,
- .num_ports = 1,
+ .num_ports = 1, /* 3, but the card reports its ports separately */
.open = option_open,
.close = option_close,
.write = option_write,
static int __init option_init(void)
{
int retval;
- retval = usb_serial_register(&option_1port_device);
+ retval = usb_serial_register(&option_3port_device);
if (retval)
- goto failed_1port_device_register;
+ goto failed_3port_device_register;
retval = usb_register(&option_driver);
if (retval)
goto failed_driver_register;
return 0;
failed_driver_register:
- usb_serial_deregister (&option_1port_device);
-failed_1port_device_register:
+ usb_serial_deregister (&option_3port_device);
+failed_3port_device_register:
return retval;
}
static void __exit option_exit(void)
{
usb_deregister (&option_driver);
- usb_serial_deregister (&option_1port_device);
+ usb_serial_deregister (&option_3port_device);
}
module_init(option_init);
port = (struct usb_serial_port *) urb->context;
- usb_serial_port_softint(port);
+ if (port->open_count)
+ schedule_work(&port->work);
}
static void option_instat_callback(struct urb *urb, struct pt_regs *regs)
/* Setup urbs */
static void option_setup_urbs(struct usb_serial *serial)
{
- int i,j;
+ int j;
struct usb_serial_port *port;
struct option_port_private *portdata;
dbg("%s", __FUNCTION__);
- for (i = 0; i < serial->num_ports; i++) {
- port = serial->port[i];
- portdata = usb_get_serial_port_data(port);
+ port = serial->port[0];
+ portdata = usb_get_serial_port_data(port);
/* Do indat endpoints first */
- for (j = 0; j < N_IN_URB; ++j) {
- portdata->in_urbs[j] = option_setup_urb (serial,
- port->bulk_in_endpointAddress, USB_DIR_IN, port,
- portdata->in_buffer[j], IN_BUFLEN, option_indat_callback);
- }
+ for (j = 0; j < N_IN_URB; ++j) {
+ portdata->in_urbs[j] = option_setup_urb (serial,
+ port->bulk_in_endpointAddress, USB_DIR_IN, port,
+ portdata->in_buffer[j], IN_BUFLEN, option_indat_callback);
+ }
- /* outdat endpoints */
- for (j = 0; j < N_OUT_URB; ++j) {
- portdata->out_urbs[j] = option_setup_urb (serial,
- port->bulk_out_endpointAddress, USB_DIR_OUT, port,
- portdata->out_buffer[j], OUT_BUFLEN, option_outdat_callback);
- }
+ /* outdat endpoints */
+ for (j = 0; j < N_OUT_URB; ++j) {
+ portdata->out_urbs[j] = option_setup_urb (serial,
+ port->bulk_out_endpointAddress, USB_DIR_OUT, port,
+ portdata->out_buffer[j], OUT_BUFLEN, option_outdat_callback);
}
}
/* Now setup per port private data */
for (i = 0; i < serial->num_ports; i++) {
port = serial->port[i];
- portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
+ portdata = kmalloc(sizeof(*portdata), GFP_KERNEL);
if (!portdata) {
dbg("%s: kmalloc for option_port_private (%d) failed!.",
__FUNCTION__, i);
return (1);
}
+ memset(portdata, 0, sizeof(struct option_port_private));
usb_set_serial_port_data(port, portdata);