Re: Support for Quatech ESU2-100 USB 2.0 8-port serial adaptor

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, 2009-06-02 at 16:00 -0700, Greg KH wrote:
> > Judging by your description, writing a new driver would be better than 
> > trying to make a dual-purpose driver.
> 
> I agree.  Especially as someone just rewrote the serqt_usb driver in the
> staging tree to be a "proper" usb-serial driver.  So your patches will
> not apply anymore anyway :(

I have started (slowly due to other work) on such a driver, which is
indeed much easier to do. I am using the re-written USB 1.1 driver as a
guide for structuring the new driver. Where I am struggling is with what
exactly is done for you by the usb-serial layer, as I can't find any
specific documentation on writing USB serial drivers using the kernel
infrastructure. Where the devices are similar this isn't a problem, but
it makes things harder where the hardware design is eccentric. 

In particular, the USB 2.0 series devices have only on bulk in and out
endpoint, which is shared between the multiple ports using headers on
each URB (as far as I can see based on the vendor driver code). I'm not
clear what the best way of handling this is - do I need to initialise
the endpoints / URBs, and if so, what function do it do it in? Will
keeping the URBs in port 0's structure and then referencing them there
as required be a good idea or a bad one?

The patch below is of the "works as far as it goes" variety, in that the
module compiles and loads, the device nodes are registered and the unit
switched on, but nothing actually works.

Richard Ash

Index: linux/drivers/staging/Kconfig
===================================================================
--- linux.orig/drivers/staging/Kconfig
+++ linux/drivers/staging/Kconfig
@@ -117,6 +117,8 @@ source "drivers/staging/line6/Kconfig"
 
 source "drivers/staging/serqt_usb2/Kconfig"
 
+source "drivers/staging/quatech_usb2/Kconfig"
+
 source "drivers/staging/vt6655/Kconfig"
 
 source "drivers/staging/cpc-usb/Kconfig"
Index: linux/drivers/staging/Makefile
===================================================================
--- linux.orig/drivers/staging/Makefile
+++ linux/drivers/staging/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_PLAN9AUTH)		+= p9auth/
 obj-$(CONFIG_HECI)		+= heci/
 obj-$(CONFIG_LINE6_USB)		+= line6/
 obj-$(CONFIG_USB_SERIAL_QUATECH2)	+= serqt_usb2/
+obj-$(CONFIG_USB_SERIAL_QUATECH_USB2)	+= quatech_usb2/
 obj-$(CONFIG_VT6655)		+= vt6655/
 obj-$(CONFIG_USB_CPC)		+= cpc-usb/
 obj-$(CONFIG_RDC_17F3101X)	+= pata_rdc/
Index: linux/drivers/staging/quatech_usb2/Kconfig
===================================================================
--- /dev/null
+++ linux/drivers/staging/quatech_usb2/Kconfig
@@ -0,0 +1,15 @@
+config USB_SERIAL_QUATECH_USB2
+	tristate "USB Quatech xSU2-[14]00 USB Serial Driver"
+	depends on USB_SERIAL
+	help
+	  Say Y here if you want to use a Quatech USB2.0 to serial adaptor.
This
+	  driver supports the SSU2-100, DSU2-100, DSU2-400, QSU2-100,
QSU2-400,
+	  ESU2-400 and ESU2-100 USB2.0 to RS232 / 485 / 422 serial adaptors.
+
+	  Some hardware has an incorrect product string and announces itself
as
+	  ESU-100 (which uses the serqt driver) even though it is an ESU2-100.
+	  Check the label on the bottom of your device.
+
+	  To compile this driver as a module, choose M here: the module will
be
+	  called quatech_usb2 .
+
Index: linux/drivers/staging/quatech_usb2/Makefile
===================================================================
--- /dev/null
+++ linux/drivers/staging/quatech_usb2/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_SERIAL_QUATECH_USB2)		+= quatech_usb2.o
Index: linux/drivers/staging/quatech_usb2/quatech_usb2.c
===================================================================
--- /dev/null
+++ linux/drivers/staging/quatech_usb2/quatech_usb2.c
@@ -0,0 +1,573 @@
+/*
+ * Driver for Quatech Inc USB2.0 to serial adaptors. Largely unrelated
to the
+ * serqt_usb driver, based on a re-write of the vendor supplied
serqt_usb2 code,
+ * which is unrelated to the serqt_usb2 in the staging kernel
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/serial.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/uaccess.h>
+
+static int debug;
+
+/* Version Information */
+#define DRIVER_VERSION "v2.00"
+#define DRIVER_AUTHOR "Tim Gobeli, Quatech, Inc"
+#define DRIVER_DESC "Quatech USB 2.0 to Serial Driver"
+
+/* vendor and device IDs */
+#define	USB_VENDOR_ID_QUATECH 0x061d	/* Quatech VID */
+#define QUATECH_SSU2_100 0xC120		/* RS232 single port */
+#define QUATECH_DSU2_100 0xC140		/* RS232 dual port */
+#define QUATECH_DSU2_400 0xC150		/* RS232/422/485 dual port */
+#define QUATECH_QSU2_100 0xC160		/* RS232 four port */
+#define QUATECH_QSU2_400 0xC170		/* RS232/422/485 four port */
+#define QUATECH_ESU2_100 0xC1A0		/* RS232 eight port */
+#define QUATECH_ESU2_400 0xC180		/* RS232/422/485 eight port */
+
+/* magic numbers go here, when we find out which ones are needed */
+
+#define QU2BOXPWRON 0x8000		/* magic number to turn FPGA power on */
+#define QU2BOX232 0x40			/* RS232 mode on MEI devices */
+#define QU2BOXSPD9600 0x60		/* set speed to 9600 baud */
+/* directions for USB transfers */
+#define USBD_TRANSFER_DIRECTION_IN    0xc0
+#define USBD_TRANSFER_DIRECTION_OUT   0x40
+/* special Quatech command IDs */
+#define QT_SET_GET_DEVICE		0xc2
+#define QT_OPEN_CLOSE_CHANNEL		0xca
+/*#define QT_GET_SET_PREBUF_TRIG_LVL	0xcc
+#define QT_SET_ATF			0xcd
+#define QT_GET_SET_REGISTER		0xc0*/
+#define QT_GET_SET_UART			0xc1
+/*#define QT_HW_FLOW_CONTROL_MASK		0xc5
+#define QT_SW_FLOW_CONTROL_MASK		0xc6
+#define QT_SW_FLOW_CONTROL_DISABLE	0xc7
+#define QT_BREAK_CONTROL 		0xc8
+#define QT_STOP_RECEIVE			0xe0
+#define QT_FLUSH_DEVICE			0xc4*/
+#define QT_GET_SET_QMCR			0xe1
+/* port setting constants */
+#define  SERIAL_MCR_DTR             0x01
+#define  SERIAL_MCR_RTS             0x02
+#define  SERIAL_MCR_LOOP            0x10
+
+#define  SERIAL_MSR_CTS             0x10
+#define  SERIAL_MSR_CD              0x80
+#define  SERIAL_MSR_RI              0x40
+#define  SERIAL_MSR_DSR             0x20
+#define  SERIAL_MSR_MASK            0xf0
+
+#define  SERIAL_8_DATA              0x03
+#define  SERIAL_7_DATA              0x02
+#define  SERIAL_6_DATA              0x01
+#define  SERIAL_5_DATA              0x00
+
+#define  SERIAL_ODD_PARITY          0X08
+#define  SERIAL_EVEN_PARITY         0X18
+#define  SERIAL_TWO_STOPB           0x04
+#define  SERIAL_ONE_STOPB           0x00
+
+#define  MAX_BAUD_RATE              921600
+#define  MAX_BAUD_REMAINDER         4608
+
+#define SERIAL_LSR_OE       0x02
+#define SERIAL_LSR_PE       0x04
+#define SERIAL_LSR_FE       0x08
+#define SERIAL_LSR_BI       0x10
+
+
+static struct usb_device_id quausb2_id_table[] = {
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU2_100)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_100)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_400)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_100)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_400)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_100)},
+	{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_400)},
+	{}	/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, quausb2_id_table);
+
+/* custom structures we need go here */
+
+
+static struct usb_driver quausb2_usb_driver = {
+	.name = "quatech-usb2-serial",
+	.probe = usb_serial_probe,
+	.disconnect = usb_serial_disconnect,
+	.id_table = quausb2_id_table,
+	.no_dynamic_id = 1,
+};
+
+/* structure in which to keep all the messy stuff that this driver
needs
+ * alongside the usb_serial_port structure */
+struct quatech2_port {
+	int	magic;
+	char	active;		/* someone has this device open */
+	unsigned char		*xfer_to_tty_buffer;
+	wait_queue_head_t	wait;
+	int	open_count;	/* number of times this	port has been opened */
+	struct semaphore	sem;	/* locks this structure */
+	__u8	shadowLCR;	/* last LCR value received */
+	__u8	shadowMCR;	/* last MCR value received */
+	__u8	shadowMSR;	/* last MSR value received */
+	__u8	shadowLSR;	/* last LSR value received */
+	char	open_ports;	/* ports open on whole device */
+	char	RxHolding;
+	char	Rcv_Flush;
+	char	Xmit_Flush;
+	char	closePending;
+	char	fifo_empty_flag;
+	int	xmit_pending_bytes;
+	int	xmit_fifo_room_bytes;
+	struct semaphore	pend_xmit_sem;	/* locks this structure */
+	spinlock_t lock;
+};
+
+/* structure which holds line and modem status flags */
+struct qt2_status_data {
+	__u8 line_status;
+	__u8 modem_status;
+};
+
+/* Function prototypes */
+static int qt2_boxpoweron(struct usb_serial *serial);
+static int qt2_boxsetQMCR(struct usb_serial *serial, __u16 Uart_Number,
+			__u8 QMCR_Value);
+static int port_paranoia_check(struct usb_serial_port *port,
+			const char *function);
+static int serial_paranoia_check(struct usb_serial *serial,
+			 const char *function);
+static inline struct quatech2_port *qt2_get_port_private(struct
usb_serial_port
+			*port);
+static inline void qt2_set_port_private(struct usb_serial_port *port,
+			struct quatech2_port *data);
+static int qt2_openboxchannel(struct usb_serial *serial, __u16
+			Uart_Number, struct qt2_status_data *pDeviceData);
+static int qt2_closeboxchannel(struct usb_serial *serial, __u16
+			Uart_Number);
+static int qt2_conf_uart(struct usb_serial *serial,  unsigned short
Uart_Number,
+			 unsigned short divisor, unsigned char LCR);
+/* implementation functions, roughly in order of use, are here */
+static int qt2_calc_num_ports(struct usb_serial *serial)
+{
+	int num_ports;
+	int flag_as_400;
+	switch (serial->dev->descriptor.idProduct) {
+	case QUATECH_SSU2_100:
+		num_ports = 1;
+		break;
+
+	case QUATECH_DSU2_400:
+		flag_as_400 = true;
+	case QUATECH_DSU2_100:
+		num_ports = 2;
+	break;
+
+	case QUATECH_QSU2_400:
+		flag_as_400 = true;
+	case QUATECH_QSU2_100:
+		num_ports = 4;
+	break;
+
+	case QUATECH_ESU2_400:
+		flag_as_400 = true;
+	case QUATECH_ESU2_100:
+		num_ports = 8;
+	break;
+	default:
+	num_ports = 1;
+	break;
+	}
+	return num_ports;
+}
+
+static int qt2_attach(struct usb_serial *serial)
+{
+	struct usb_serial_port *port;
+	struct quatech2_port *qt2_port;
+	int i;
+
+	/* Now setup per port private data, which replaces all the things
+	* that quatech added to standard kernel structures in their driver */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		qt2_port = kzalloc(sizeof(*qt2_port), GFP_KERNEL);
+		if (!qt2_port) {
+			dbg("%s: kmalloc for quatech2_port (%d) failed!.",
+			    __func__, i);
+			return -ENOMEM;
+		}
+		spin_lock_init(&qt2_port->lock);
+		if (i == 0)
+			qt2_port->open_ports = 0; /* no ports */
+		else
+			qt2_port->open_ports = -1; /* unused */
+
+		usb_set_serial_port_data(port, qt2_port);
+
+	}
+	/* switch on power to the hardware */
+	if (qt2_boxpoweron(serial) < 0) {
+		dbg("qt2_boxpoweron() failed");
+		goto startup_error;
+	}
+	/* set all ports to RS232 mode */
+	for (i = 0; i < serial->num_ports; ++i) {
+		if (qt2_boxsetQMCR(serial, i, QU2BOX232) < 0) {
+			dbg("qt2_boxsetQMCR() on port %d failed",
+				i);
+			goto startup_error;
+		}
+	}
+
+	return 0;
+
+startup_error:
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		qt2_port = qt2_get_port_private(port);
+		kfree(qt2_port);
+		usb_set_serial_port_data(port, NULL);
+	}
+
+	dbg("Exit fail %s\n", __func__);
+	return -EIO;
+}
+
+static void qt2_release(struct usb_serial *serial)
+{
+	struct usb_serial_port *port;
+	struct quatech2_port *qt_port;
+	int i;
+
+	dbg("enterting %s", __func__);
+
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		if (!port)
+			continue;
+
+		qt_port = usb_get_serial_port_data(port);
+		kfree(qt_port);
+		usb_set_serial_port_data(port, NULL);
+	}
+}
+/* This function is called once per serial port on the device.
+ * The tty_struct and the usb_serial_port belong to this port,
+ * i.e. there are multiple ones for a multi-port device.
+ * However the usb_serial_port structure has a back-pointer
+ * to the parent usb_serial structure which belongs to the device,
+ * so we can access either the device-wide information or
+ * any other port's information (because there are also forward
+ * pointers) via that pointer.
+ * This is most helpful if the device shares resources (e.g. end
+ * points) between different ports
+ */
+int qt2_open(struct tty_struct *tty,
+	    struct usb_serial_port *port, struct file *filp)
+{
+	struct usb_serial *serial;	/* device structure */
+	struct usb_serial_port *port0;	/* first port structure on device */
+	struct quatech2_port *port_extra;	/* extra data for this port */
+	struct quatech2_port *port0_extra;	/* extra data for first port */
+	struct qt2_status_data ChannelData;
+	unsigned short default_divisor = QU2BOXSPD9600;
+	unsigned char  default_LCR = SERIAL_8_DATA;
+	int status;
+
+	if (port_paranoia_check(port, __func__))
+		return -ENODEV;
+
+	dbg("%s - port %d\n", __func__, port->number);
+
+	serial = port->serial;	/* get the parent device structure */
+	if (serial_paranoia_check(serial, __func__))
+		return -ENODEV;
+	port0 = serial->port[0]; /* get the first port's device structure */
+
+	port_extra = qt2_get_port_private(port);
+	port0_extra = qt2_get_port_private(port0);
+
+	if (port_extra == NULL || port0_extra == NULL)
+		return -ENODEV;
+
+	usb_clear_halt(serial->dev, port->write_urb->pipe);
+	usb_clear_halt(serial->dev, port->read_urb->pipe);
+	port0_extra->open_ports++;
+
+	/* FIXME: are these needed?  Does it even do anything useful? */
+	/* get the modem and line status values from the UART */
+	status = qt2_openboxchannel(serial, port->number,
+			&ChannelData);
+	if (status < 0) {
+		dbg("qt2_openboxchannel on channel %d failed",
+		    port->number);
+		return status;
+	}
+	port_extra->shadowLSR = ChannelData.line_status &
+			(SERIAL_LSR_OE | SERIAL_LSR_PE | SERIAL_LSR_FE |
+			SERIAL_LSR_BI);
+
+	port_extra->shadowMSR = ChannelData.modem_status &
+			(SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI |
+			SERIAL_MSR_CD);
+
+	port_extra->fifo_empty_flag = true;
+	dbg("qt2_openboxchannel on channel %d completed.",
+	    port->number);
+
+	/* Set Baud rate to default and turn off flow control here */
+	status = qt2_conf_uart(serial, port->number, default_divisor,
+				default_LCR);
+	if (status < 0) {
+		dbg("qt2_conf_uart() failed on channel %d",
+		    port->number);
+		return status;
+	}
+	dbg("qt2_conf_uart() completed on channel %d",
+		port->number);
+
+	dbg("port number is %d", port->number);
+	dbg("serial number is %d", port->serial->minor);
+
+	/* We need to set up URBs and endpoints here. We only
+	 * have one pair of endpoints per device, so in fact
+	 * we only need to set up endpoints on the first time
+	 * round, not subsequent ones.
+	 * When we do a write to a port, we will use the same URBs
+	 * regadless of the port, with a 5-byte header added on to
+	 * tell the box which port it should eventually come out of,
+	 * so the same URBs need to be visible to write calls
+	 * regardless of which port is being written.
+	 * To this end we actually keep the relevant endpoints and
+	 * URBs in port 0's structure, because that's always there
+	 * and avoids providing our own duplicate members in some
+	 * user data structure for the same purpose.
+	 */
+	if (port0_extra->open_ports == 1) {
+		/* this is first port to be opened */
+	}
+
+	dbg("Bulkin endpoint is %d", port->bulk_in_endpointAddress);
+	dbg("BulkOut endpoint is %d", port->bulk_out_endpointAddress);
+	dbg("Interrupt endpoint is %d", port->interrupt_in_endpointAddress);
+
+	/* initialize our wait queues */
+	init_waitqueue_head(&port_extra->wait);
+
+	/* remember to store port_extra and port0 back again at end !*/
+	qt2_set_port_private(port, port_extra);
+	qt2_set_port_private(serial->port[0], port0_extra);
+
+	return 0;
+}
+
+/* internal, private helper functions for the driver */
+
+/* Power up the FPGA in the box to get it working */
+static int qt2_boxpoweron(struct usb_serial *serial)
+{
+	int result;
+	__u8  Direcion;
+	unsigned int pipe;
+	Direcion = USBD_TRANSFER_DIRECTION_OUT;
+	pipe = usb_rcvctrlpipe(serial->dev, 0);
+	result = usb_control_msg(serial->dev, pipe, QT_SET_GET_DEVICE,
+				Direcion, QU2BOXPWRON, 0x00, NULL, 0x00,
+				5000);
+	return result;
+}
+
+/*
+ * qt2_boxsetQMCR Issue a QT_GET_SET_QMCR vendor-spcific request on the
+ * default control pipe. If successful return the number of bytes
written,
+ * otherwise return a negative error number of the problem.
+ */
+static int qt2_boxsetQMCR(struct usb_serial *serial, __u16 Uart_Number,
+			  __u8 QMCR_Value)
+{
+	int result;
+	__u16 PortSettings;
+
+	PortSettings = (__u16)(QMCR_Value);
+
+	dbg("%s(): Port = %d, PortSettings = 0x%x", __func__,
+			Uart_Number, PortSettings);
+
+	result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+				QT_GET_SET_QMCR, 0x40, PortSettings,
+				(__u16)Uart_Number, NULL, 0, 5000);
+	return result;
+}
+
+static int port_paranoia_check(struct usb_serial_port *port,
+			       const char *function)
+{
+	if (!port) {
+		dbg("%s - port == NULL", function);
+		return -1;
+	}
+	if (!port->serial) {
+		dbg("%s - port->serial == NULL\n", function);
+		return -1;
+	}
+	return 0;
+}
+
+static int serial_paranoia_check(struct usb_serial *serial,
+				 const char *function)
+{
+	if (!serial) {
+		dbg("%s - serial == NULL\n", function);
+		return -1;
+	}
+
+	if (!serial->type) {
+		dbg("%s - serial->type == NULL!", function);
+		return -1;
+	}
+
+	return 0;
+}
+
+static inline struct quatech2_port *qt2_get_port_private(struct
usb_serial_port
+		*port)
+{
+	return (struct quatech2_port *)usb_get_serial_port_data(port);
+}
+
+static inline void qt2_set_port_private(struct usb_serial_port *port,
+				       struct quatech2_port *data)
+{
+	usb_set_serial_port_data(port, (void *)data);
+}
+
+static int qt2_openboxchannel(struct usb_serial *serial, __u16
+		Uart_Number, struct qt2_status_data *status)
+{
+	int result;
+	__u16 length;
+	__u8  Direcion;
+	unsigned int pipe;
+	length = sizeof(struct qt2_status_data);
+	Direcion = USBD_TRANSFER_DIRECTION_IN;
+	pipe = usb_rcvctrlpipe(serial->dev, 0);
+	result = usb_control_msg(serial->dev, pipe, QT_OPEN_CLOSE_CHANNEL,
+			Direcion, 0x00, Uart_Number, status, length, 5000);
+	return result;
+}
+static int qt2_closeboxchannel(struct usb_serial *serial, __u16
Uart_Number)
+{
+	int result;
+	__u8  direcion;
+	unsigned int pipe;
+	direcion = USBD_TRANSFER_DIRECTION_OUT;
+	pipe = usb_sndctrlpipe(serial->dev, 0);
+	result = usb_control_msg(serial->dev, pipe, QT_OPEN_CLOSE_CHANNEL,
+		  direcion, 0, Uart_Number, NULL, 0, 5000);
+	return result;
+}
+
+/* qt2_conf_uart Issue a SET_UART vendor-spcific request on the default
+ * control pipe. If successful sets baud rate divisor and LCR value
+ */
+static int qt2_conf_uart(struct usb_serial *serial,  unsigned short
Uart_Number,
+		      unsigned short divisor, unsigned char LCR)
+{
+	int result;
+	unsigned short UartNumandLCR;
+
+	UartNumandLCR = (LCR << 8) + Uart_Number;
+
+	result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+				QT_GET_SET_UART, 0x40, divisor, UartNumandLCR,
+				NULL, 0, 300);
+	return result;
+}
+
+/*
+ * last things in file: stuff to register this driver into the generic
+ * USB serial framework.
+ */
+
+static struct usb_serial_driver quatech2_device = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "quatech_usb2",
+	},
+	.description = DRIVER_DESC,
+	.usb_driver = &quausb2_usb_driver,
+	.id_table = quausb2_id_table,
+	.num_ports = 8,
+	.open = qt2_open,
+	/*.close = qt_close,
+	.write = qt_write,
+	.write_room = qt_write_room,
+	.chars_in_buffer = qt_chars_in_buffer,
+	.throttle = qt_throttle,
+	.unthrottle = qt_unthrottle,*/
+	.calc_num_ports = qt2_calc_num_ports,
+	/*.ioctl = qt_ioctl,
+	.set_termios = qt_set_termios,
+	.break_ctl = qt_break,
+	.tiocmget = qt_tiocmget,
+	.tiocmset = qt_tiocmset,*/
+	.attach = qt2_attach,
+	.release = qt2_release,
+};
+
+static int __init quausb2_usb_init(void)
+{
+	int retval;
+
+	dbg("%s\n", __func__);
+
+	/* register with usb-serial */
+	retval = usb_serial_register(&quatech2_device);
+
+	if (retval)
+		goto failed_usb_serial_register;
+
+	printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
+			DRIVER_DESC "\n");
+
+	/* register with usb */
+
+	retval = usb_register(&quausb2_usb_driver);
+	if (retval == 0)
+		return 0;
+
+	/* if we're here, usb_register() failed */
+	usb_serial_deregister(&quatech2_device);
+failed_usb_serial_register:
+		return retval;
+}
+
+
+
+static void __exit quausb2_usb_exit(void)
+{
+	usb_deregister(&quausb2_usb_driver);
+	usb_serial_deregister(&quatech2_device);
+}
+
+module_init(quausb2_usb_init);
+module_exit(quausb2_usb_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux