usb: cp210x: Update to support CP2105 and multiple interface devices
authorPreston Fick <preston.fick@silabs.com>
Fri, 24 Feb 2012 19:42:39 +0000 (13:42 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 3 Mar 2012 00:19:16 +0000 (16:19 -0800)
This patch updates the cp210x driver to support CP210x multiple
interface devices devices from Silicon Labs. The existing driver
always sends control requests to interface 0, which is hardcoded in
the usb_control_msg function calls. This only allows for single
interface devices to be used, and causes a bug when using ports on an
interface other than 0 in the multiple interface devices.

Here are the changes included in this patch:
- Updated the device list to contain the Silicon Labs factory default
  VID/PID for multiple interface CP210x devices
- Created a cp210x_port_private struct created for each port on
  startup, this struct holds the interface number
- Added a cp210x_release function to clean up the cp210x_port_private
  memory created on startup
- Modified usb_get_config and usb_set_config to get a pointer to the
  cp210x_port_private struct, and use the interface number there in the
  usb_control_message wIndex param

Signed-off-by: Preston Fick <preston.fick@silabs.com>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/serial/cp210x.c

index 651b2aa..0310e2d 100644 (file)
@@ -49,6 +49,7 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
                unsigned int, unsigned int);
 static void cp210x_break_ctl(struct tty_struct *, int);
 static int cp210x_startup(struct usb_serial *);
+static void cp210x_release(struct usb_serial *);
 static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
 
 static bool debug;
@@ -121,6 +122,8 @@ static const struct usb_device_id id_table[] = {
        { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */
        { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */
        { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */
        { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */
        { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */
        { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */
@@ -149,6 +152,10 @@ static const struct usb_device_id id_table[] = {
 
 MODULE_DEVICE_TABLE(usb, id_table);
 
+struct cp210x_port_private {
+       __u8                    bInterfaceNumber;
+};
+
 static struct usb_driver cp210x_driver = {
        .name           = "cp210x",
        .probe          = usb_serial_probe,
@@ -172,6 +179,7 @@ static struct usb_serial_driver cp210x_device = {
        .tiocmget               = cp210x_tiocmget,
        .tiocmset               = cp210x_tiocmset,
        .attach                 = cp210x_startup,
+       .release                = cp210x_release,
        .dtr_rts                = cp210x_dtr_rts
 };
 
@@ -263,6 +271,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,
                unsigned int *data, int size)
 {
        struct usb_serial *serial = port->serial;
+       struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
        __le32 *buf;
        int result, i, length;
 
@@ -278,7 +287,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,
        /* Issue the request, attempting to read 'size' bytes */
        result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
                                request, REQTYPE_DEVICE_TO_HOST, 0x0000,
-                               0, buf, size, 300);
+                               port_priv->bInterfaceNumber, buf, size, 300);
 
        /* Convert data into an array of integers */
        for (i = 0; i < length; i++)
@@ -309,6 +318,7 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request,
                unsigned int *data, int size)
 {
        struct usb_serial *serial = port->serial;
+       struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
        __le32 *buf;
        int result, i, length;
 
@@ -330,12 +340,12 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request,
                result = usb_control_msg(serial->dev,
                                usb_sndctrlpipe(serial->dev, 0),
                                request, REQTYPE_HOST_TO_DEVICE, 0x0000,
-                               0, buf, size, 300);
+                               port_priv->bInterfaceNumber, buf, size, 300);
        } else {
                result = usb_control_msg(serial->dev,
                                usb_sndctrlpipe(serial->dev, 0),
                                request, REQTYPE_HOST_TO_DEVICE, data[0],
-                               0, NULL, 0, 300);
+                               port_priv->bInterfaceNumber, NULL, 0, 300);
        }
 
        kfree(buf);
@@ -845,11 +855,39 @@ static void cp210x_break_ctl (struct tty_struct *tty, int break_state)
 
 static int cp210x_startup(struct usb_serial *serial)
 {
+       struct cp210x_port_private *port_priv;
+       int i;
+
        /* cp210x buffers behave strangely unless device is reset */
        usb_reset_device(serial->dev);
+
+       for (i = 0; i < serial->num_ports; i++) {
+               port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL);
+               if (!port_priv)
+                       return -ENOMEM;
+
+               memset(port_priv, 0x00, sizeof(*port_priv));
+               port_priv->bInterfaceNumber =
+                   serial->interface->cur_altsetting->desc.bInterfaceNumber;
+
+               usb_set_serial_port_data(serial->port[i], port_priv);
+       }
+
        return 0;
 }
 
+static void cp210x_release(struct usb_serial *serial)
+{
+       struct cp210x_port_private *port_priv;
+       int i;
+
+       for (i = 0; i < serial->num_ports; i++) {
+               port_priv = usb_get_serial_port_data(serial->port[i]);
+               kfree(port_priv);
+               usb_set_serial_port_data(serial->port[i], NULL);
+       }
+}
+
 module_usb_serial_driver(cp210x_driver, serial_drivers);
 
 MODULE_DESCRIPTION(DRIVER_DESC);