fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / serial / cypress_m8.c
index 68067fe..6bc1f40 100644 (file)
@@ -48,7 +48,6 @@
 /* Code originates and was built up from ftdi_sio, belkin, pl2303 and others. */
 
 
-#include <linux/config.h>
 #include <linux/kernel.h>
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/moduleparam.h>
 #include <linux/spinlock.h>
 #include <linux/usb.h>
+#include <linux/usb/serial.h>
 #include <linux/serial.h>
 #include <linux/delay.h>
 #include <asm/uaccess.h>
 
-#include "usb-serial.h"
 #include "cypress_m8.h"
 
 
@@ -98,10 +97,16 @@ static struct usb_device_id id_table_cyphidcomrs232 [] = {
        { }                                             /* Terminating entry */
 };
 
+static struct usb_device_id id_table_nokiaca42v2 [] = {
+       { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) },
+       { }                                             /* Terminating entry */
+};
+
 static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB) },
        { USB_DEVICE(VENDOR_ID_DELORME, PRODUCT_ID_EARTHMATEUSB_LT20) },
        { USB_DEVICE(VENDOR_ID_CYPRESS, PRODUCT_ID_CYPHIDCOM) },
+       { USB_DEVICE(VENDOR_ID_DAZZLE, PRODUCT_ID_CA42) },
        { }                                             /* Terminating entry */
 };
 
@@ -124,6 +129,9 @@ struct cypress_private {
        int cmd_ctrl;                      /* always set this to 1 before issuing a command */
        struct cypress_buf *buf;           /* write buffer */
        int write_urb_in_use;              /* write urb in use indicator */
+       int write_urb_interval;            /* interval to use for write urb */
+       int read_urb_interval;             /* interval to use for read urb */
+       int comm_is_ok;                    /* true if communication is (still) ok */
        int termios_initialized;
        __u8 line_control;                 /* holds dtr / rts value */
        __u8 current_status;               /* received from last read - info on dsr,cts,cd,ri,etc */
@@ -135,7 +143,7 @@ struct cypress_private {
        wait_queue_head_t delta_msr_wait;  /* used for TIOCMIWAIT */
        char prev_status, diff_status;     /* used for TIOCMIWAIT */
        /* we pass a pointer to this as the arguement sent to cypress_set_termios old_termios */
-       struct termios tmp_termios;        /* stores the old termios settings */
+       struct ktermios tmp_termios;       /* stores the old termios settings */
 };
 
 /* write buffer structure */
@@ -149,6 +157,7 @@ struct cypress_buf {
 /* function prototypes for the Cypress USB to serial device */
 static int  cypress_earthmate_startup  (struct usb_serial *serial);
 static int  cypress_hidcom_startup     (struct usb_serial *serial);
+static int  cypress_ca42v2_startup     (struct usb_serial *serial);
 static void cypress_shutdown           (struct usb_serial *serial);
 static int  cypress_open               (struct usb_serial_port *port, struct file *filp);
 static void cypress_close              (struct usb_serial_port *port, struct file *filp);
@@ -156,14 +165,15 @@ static int  cypress_write         (struct usb_serial_port *port, const unsigned char *b
 static void cypress_send               (struct usb_serial_port *port);
 static int  cypress_write_room         (struct usb_serial_port *port);
 static int  cypress_ioctl              (struct usb_serial_port *port, struct file * file, unsigned int cmd, unsigned long arg);
-static void cypress_set_termios                (struct usb_serial_port *port, struct termios * old);
+static void cypress_set_termios                (struct usb_serial_port *port, struct ktermios * old);
 static int  cypress_tiocmget           (struct usb_serial_port *port, struct file *file);
 static int  cypress_tiocmset           (struct usb_serial_port *port, struct file *file, unsigned int set, unsigned int clear);
 static int  cypress_chars_in_buffer    (struct usb_serial_port *port);
 static void cypress_throttle           (struct usb_serial_port *port);
 static void cypress_unthrottle         (struct usb_serial_port *port);
-static void cypress_read_int_callback  (struct urb *urb, struct pt_regs *regs);
-static void cypress_write_int_callback (struct urb *urb, struct pt_regs *regs);
+static void cypress_set_dead           (struct usb_serial_port *port);
+static void cypress_read_int_callback  (struct urb *urb);
+static void cypress_write_int_callback (struct urb *urb);
 /* baud helper functions */
 static int      mask_to_rate           (unsigned mask);
 static unsigned  rate_to_mask          (int rate);
@@ -235,6 +245,34 @@ static struct usb_serial_driver cypress_hidcom_device = {
        .write_int_callback =           cypress_write_int_callback,
 };
 
+static struct usb_serial_driver cypress_ca42v2_device = {
+       .driver = {
+               .owner =                THIS_MODULE,
+                .name =                        "nokiaca42v2",
+       },
+       .description =                  "Nokia CA-42 V2 Adapter",
+       .id_table =                     id_table_nokiaca42v2,
+       .num_interrupt_in =             1,
+       .num_interrupt_out =            1,
+       .num_bulk_in =                  NUM_DONT_CARE,
+       .num_bulk_out =                 NUM_DONT_CARE,
+       .num_ports =                    1,
+       .attach =                       cypress_ca42v2_startup,
+       .shutdown =                     cypress_shutdown,
+       .open =                         cypress_open,
+       .close =                        cypress_close,
+       .write =                        cypress_write,
+       .write_room =                   cypress_write_room,
+       .ioctl =                        cypress_ioctl,
+       .set_termios =                  cypress_set_termios,
+       .tiocmget =                     cypress_tiocmget,
+       .tiocmset =                     cypress_tiocmset,
+       .chars_in_buffer =              cypress_chars_in_buffer,
+       .throttle =                     cypress_throttle,
+       .unthrottle =                   cypress_unthrottle,
+       .read_int_callback =            cypress_read_int_callback,
+       .write_int_callback =           cypress_write_int_callback,
+};
 
 /*****************************************************************************
  * Cypress serial helper functions
@@ -254,6 +292,9 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
        
        priv = usb_get_serial_port_data(port);
 
+       if (!priv->comm_is_ok)
+               return -ENODEV;
+
        switch(cypress_request_type) {
                case CYPRESS_SET_CONFIG:
 
@@ -286,6 +327,12 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
                                                    __FUNCTION__);
                                                new_baudrate = priv->baud_rate;
                                        }
+                               } else if (priv->chiptype == CT_CA42V2) {
+                                       if ( (new_baudrate = mask_to_rate(baud_mask)) == -1) {
+                                               err("%s - failed setting baud rate, unsupported speed",
+                                                   __FUNCTION__);
+                                               new_baudrate = priv->baud_rate;
+                                       }
                                } else if (priv->chiptype == CT_GENERIC) {
                                        if ( (new_baudrate = mask_to_rate(baud_mask)) == -1) {
                                                err("%s - failed setting baud rate, unsupported speed",
@@ -325,13 +372,12 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
                                if (tries++ >= 3)
                                        break;
 
-                               if (retval == EPIPE)
-                                       usb_clear_halt(port->serial->dev, 0x00);
-                       } while (retval != 8 && retval != ENODEV);
+                       } while (retval != 8 && retval != -ENODEV);
 
-                       if (retval != 8)
+                       if (retval != 8) {
                                err("%s - failed sending serial line settings - %d", __FUNCTION__, retval);
-                       else {
+                               cypress_set_dead(port);
+                       } else {
                                spin_lock_irqsave(&priv->lock, flags);
                                priv->baud_rate = new_baudrate;
                                priv->cbr_mask = baud_mask;
@@ -352,12 +398,11 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
                                if (tries++ >= 3)
                                        break;
 
-                               if (retval == EPIPE)
-                                       usb_clear_halt(port->serial->dev, 0x00);
-                       } while (retval != 5 && retval != ENODEV);
+                       } while (retval != 5 && retval != -ENODEV);
 
                        if (retval != 5) {
                                err("%s - failed to retrieve serial line settings - %d", __FUNCTION__, retval);
+                               cypress_set_dead(port);
                                return retval;
                        } else {
                                spin_lock_irqsave(&priv->lock, flags);
@@ -379,6 +424,24 @@ static int cypress_serial_control (struct usb_serial_port *port, unsigned baud_m
 } /* cypress_serial_control */
 
 
+static void cypress_set_dead(struct usb_serial_port *port)
+{
+       struct cypress_private *priv = usb_get_serial_port_data(port);
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       if (!priv->comm_is_ok) {
+               spin_unlock_irqrestore(&priv->lock, flags);
+               return;
+       }
+       priv->comm_is_ok = 0;
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       err("cypress_m8 suspending failing port %d - interval might be too short",
+           port->number);
+}
+
+
 /* given a baud mask, it will return integer baud on success */
 static int mask_to_rate (unsigned mask)
 {
@@ -432,14 +495,15 @@ static unsigned rate_to_mask (int rate)
 static int generic_startup (struct usb_serial *serial)
 {
        struct cypress_private *priv;
+       struct usb_serial_port *port = serial->port[0];
 
-       dbg("%s - port %d", __FUNCTION__, serial->port[0]->number);
+       dbg("%s - port %d", __FUNCTION__, port->number);
 
-       priv = kmalloc(sizeof (struct cypress_private), GFP_KERNEL);
+       priv = kzalloc(sizeof (struct cypress_private), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;
 
-       memset(priv, 0x00, sizeof (struct cypress_private));
+       priv->comm_is_ok = !0;
        spin_lock_init(&priv->lock);
        priv->buf = cypress_buf_alloc(CYPRESS_BUF_SIZE);
        if (priv->buf == NULL) {
@@ -450,13 +514,24 @@ static int generic_startup (struct usb_serial *serial)
        
        usb_reset_configuration (serial->dev);
        
-       interval = 1;
        priv->cmd_ctrl = 0;
        priv->line_control = 0;
        priv->termios_initialized = 0;
        priv->rx_flags = 0;
        priv->cbr_mask = B300;
-       usb_set_serial_port_data(serial->port[0], priv);
+       if (interval > 0) {
+               priv->write_urb_interval = interval;
+               priv->read_urb_interval = interval;
+               dbg("%s - port %d read & write intervals forced to %d",
+                   __FUNCTION__,port->number,interval);
+       } else {
+               priv->write_urb_interval = port->interrupt_out_urb->interval;
+               priv->read_urb_interval = port->interrupt_in_urb->interval;
+               dbg("%s - port %d intervals: read=%d write=%d",
+                   __FUNCTION__,port->number,
+                   priv->read_urb_interval,priv->write_urb_interval);
+       }
+       usb_set_serial_port_data(port, priv);
        
        return 0;
 }
@@ -500,6 +575,25 @@ static int cypress_hidcom_startup (struct usb_serial *serial)
 } /* cypress_hidcom_startup */
 
 
+static int cypress_ca42v2_startup (struct usb_serial *serial)
+{
+       struct cypress_private *priv;
+
+       dbg("%s", __FUNCTION__);
+
+       if (generic_startup(serial)) {
+               dbg("%s - Failed setting up port %d", __FUNCTION__,
+                               serial->port[0]->number);
+               return 1;
+       }
+
+       priv = usb_get_serial_port_data(serial->port[0]);
+       priv->chiptype = CT_CA42V2;
+
+       return 0;
+} /* cypress_ca42v2_startup */
+
+
 static void cypress_shutdown (struct usb_serial *serial)
 {
        struct cypress_private *priv;
@@ -527,6 +621,9 @@ static int cypress_open (struct usb_serial_port *port, struct file *filp)
 
        dbg("%s - port %d", __FUNCTION__, port->number);
 
+       if (!priv->comm_is_ok)
+               return -EIO;
+
        /* clear halts before open */
        usb_clear_halt(serial->dev, 0x81);
        usb_clear_halt(serial->dev, 0x02);
@@ -566,11 +663,12 @@ static int cypress_open (struct usb_serial_port *port, struct file *filp)
        usb_fill_int_urb(port->interrupt_in_urb, serial->dev,
                usb_rcvintpipe(serial->dev, port->interrupt_in_endpointAddress),
                port->interrupt_in_urb->transfer_buffer, port->interrupt_in_urb->transfer_buffer_length,
-               cypress_read_int_callback, port, interval);
+               cypress_read_int_callback, port, priv->read_urb_interval);
        result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
 
        if (result){
                dev_err(&port->dev, "%s - failed submitting read urb, error %d\n", __FUNCTION__, result);
+               cypress_set_dead(port);
        }
 
        return result;
@@ -675,6 +773,9 @@ static void cypress_send(struct usb_serial_port *port)
        struct cypress_private *priv = usb_get_serial_port_data(port);
        unsigned long flags;
        
+       if (!priv->comm_is_ok)
+               return;
+
        dbg("%s - port %d", __FUNCTION__, port->number);
        dbg("%s - interrupt out size is %d", __FUNCTION__, port->interrupt_out_size);
        
@@ -748,14 +849,16 @@ send:
        usb_serial_debug_data(debug, &port->dev, __FUNCTION__, port->interrupt_out_size,
                              port->interrupt_out_urb->transfer_buffer);
 
-       port->interrupt_out_urb->transfer_buffer_length = actual_size;
-       port->interrupt_out_urb->dev = port->serial->dev;
-       port->interrupt_out_urb->interval = interval;
+       usb_fill_int_urb(port->interrupt_out_urb, port->serial->dev,
+               usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress),
+               port->interrupt_out_buffer, port->interrupt_out_size,
+               cypress_write_int_callback, port, priv->write_urb_interval);
        result = usb_submit_urb (port->interrupt_out_urb, GFP_ATOMIC);
        if (result) {
                dev_err(&port->dev, "%s - failed submitting write urb, error %d\n", __FUNCTION__,
                        result);
                priv->write_urb_in_use = 0;
+               cypress_set_dead(port);
        }
 
        spin_lock_irqsave(&priv->lock, flags);
@@ -765,7 +868,7 @@ send:
        priv->bytes_out += count; /* do not count the line control and size bytes */
        spin_unlock_irqrestore(&priv->lock, flags);
 
-       schedule_work(&port->work);
+       usb_serial_port_softint(port);
 } /* cypress_send */
 
 
@@ -846,28 +949,13 @@ static int cypress_ioctl (struct usb_serial_port *port, struct file * file, unsi
 
        switch (cmd) {
                case TIOCGSERIAL:
-                       if (copy_to_user((void __user *)arg, port->tty->termios, sizeof(struct termios))) {
+                       if (copy_to_user((void __user *)arg, port->tty->termios, sizeof(struct ktermios))) {
                                return -EFAULT;
                        }
                        return (0);
                        break;
                case TIOCSSERIAL:
-                       if (copy_from_user(port->tty->termios, (void __user *)arg, sizeof(struct termios))) {
-                               return -EFAULT;
-                       }
-                       /* here we need to call cypress_set_termios to invoke the new settings */
-                       cypress_set_termios(port, &priv->tmp_termios);
-                       return (0);
-                       break;
-               /* these are called when setting baud rate from gpsd */
-               case TCGETS:
-                       if (copy_to_user((void __user *)arg, port->tty->termios, sizeof(struct termios))) {
-                               return -EFAULT;
-                       }
-                       return (0);
-                       break;
-               case TCSETS:
-                       if (copy_from_user(port->tty->termios, (void __user *)arg, sizeof(struct termios))) {
+                       if (copy_from_user(port->tty->termios, (void __user *)arg, sizeof(struct ktermios))) {
                                return -EFAULT;
                        }
                        /* here we need to call cypress_set_termios to invoke the new settings */
@@ -916,7 +1004,7 @@ static int cypress_ioctl (struct usb_serial_port *port, struct file * file, unsi
 
 
 static void cypress_set_termios (struct usb_serial_port *port,
-               struct termios *old_termios)
+               struct ktermios *old_termios)
 {
        struct cypress_private *priv = usb_get_serial_port_data(port);
        struct tty_struct *tty;
@@ -944,6 +1032,10 @@ static void cypress_set_termios (struct usb_serial_port *port,
                        *(tty->termios) = tty_std_termios;
                        tty->termios->c_cflag = B9600 | CS8 | CREAD | HUPCL |
                                CLOCAL;
+               } else if (priv->chiptype == CT_CA42V2) {
+                       *(tty->termios) = tty_std_termios;
+                       tty->termios->c_cflag = B9600 | CS8 | CREAD | HUPCL |
+                               CLOCAL;
                }
                priv->termios_initialized = 1;
        }
@@ -1152,18 +1244,23 @@ static void cypress_unthrottle (struct usb_serial_port *port)
        priv->rx_flags = 0;
        spin_unlock_irqrestore(&priv->lock, flags);
 
+       if (!priv->comm_is_ok)
+               return;
+
        if (actually_throttled) {
                port->interrupt_in_urb->dev = port->serial->dev;
 
                result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
-               if (result)
+               if (result) {
                        dev_err(&port->dev, "%s - failed submitting read urb, "
                                        "error %d\n", __FUNCTION__, result);
+                       cypress_set_dead(port);
+               }
        }
 }
 
 
-static void cypress_read_int_callback(struct urb *urb, struct pt_regs *regs)
+static void cypress_read_int_callback(struct urb *urb)
 {
        struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
        struct cypress_private *priv = usb_get_serial_port_data(port);
@@ -1178,9 +1275,22 @@ static void cypress_read_int_callback(struct urb *urb, struct pt_regs *regs)
 
        dbg("%s - port %d", __FUNCTION__, port->number);
 
-       if (urb->status) {
-               dbg("%s - nonzero read status received: %d", __FUNCTION__,
-                               urb->status);
+       switch (urb->status) {
+       case 0: /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               /* precursor to disconnect so just go away */
+               return;
+       case -EPIPE:
+               usb_clear_halt(port->serial->dev,0x81);
+               break;
+       default:
+               /* something ugly is going on... */
+               dev_err(&urb->dev->dev,"%s - unexpected nonzero read status received: %d\n",
+                       __FUNCTION__,urb->status);
+               cypress_set_dead(port);
                return;
        }
 
@@ -1281,25 +1391,27 @@ continue_read:
 
        /* Continue trying to always read... unless the port has closed. */
 
-       if (port->open_count > 0) {
+       if (port->open_count > 0 && priv->comm_is_ok) {
                usb_fill_int_urb(port->interrupt_in_urb, port->serial->dev,
                                usb_rcvintpipe(port->serial->dev,
                                        port->interrupt_in_endpointAddress),
                                port->interrupt_in_urb->transfer_buffer,
                                port->interrupt_in_urb->transfer_buffer_length,
-                               cypress_read_int_callback, port, interval);
+                               cypress_read_int_callback, port, priv->read_urb_interval);
                result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
-               if (result)
+               if (result) {
                        dev_err(&urb->dev->dev, "%s - failed resubmitting "
                                        "read urb, error %d\n", __FUNCTION__,
                                        result);
+                       cypress_set_dead(port);
+               }
        }
 
        return;
 } /* cypress_read_int_callback */
 
 
-static void cypress_write_int_callback(struct urb *urb, struct pt_regs *regs)
+static void cypress_write_int_callback(struct urb *urb)
 {
        struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
        struct cypress_private *priv = usb_get_serial_port_data(port);
@@ -1318,20 +1430,26 @@ static void cypress_write_int_callback(struct urb *urb, struct pt_regs *regs)
                        dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
                        priv->write_urb_in_use = 0;
                        return;
-               case -EPIPE: /* no break needed */
+               case -EPIPE: /* no break needed; clear halt and resubmit */
+                       if (!priv->comm_is_ok)
+                               break;
                        usb_clear_halt(port->serial->dev, 0x02);
-               default:
                        /* error in the urb, so we have to resubmit it */
-                       dbg("%s - Overflow in write", __FUNCTION__);
                        dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);
                        port->interrupt_out_urb->transfer_buffer_length = 1;
                        port->interrupt_out_urb->dev = port->serial->dev;
                        result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC);
-                       if (result)
-                               dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n",
-                                       __FUNCTION__, result);
-                       else
+                       if (!result)
                                return;
+                       dev_err(&urb->dev->dev, "%s - failed resubmitting write urb, error %d\n",
+                               __FUNCTION__, result);
+                       cypress_set_dead(port);
+                       break;
+               default:
+                       dev_err(&urb->dev->dev,"%s - unexpected nonzero write status received: %d\n",
+                               __FUNCTION__,urb->status);
+                       cypress_set_dead(port);
+                       break;
        }
        
        priv->write_urb_in_use = 0;
@@ -1360,7 +1478,7 @@ static struct cypress_buf *cypress_buf_alloc(unsigned int size)
        if (size == 0)
                return NULL;
 
-       cb = (struct cypress_buf *)kmalloc(sizeof(struct cypress_buf), GFP_KERNEL);
+       cb = kmalloc(sizeof(struct cypress_buf), GFP_KERNEL);
        if (cb == NULL)
                return NULL;
 
@@ -1542,19 +1660,23 @@ static int __init cypress_init(void)
        retval = usb_serial_register(&cypress_hidcom_device);
        if (retval)
                goto failed_hidcom_register;
+       retval = usb_serial_register(&cypress_ca42v2_device);
+       if (retval)
+               goto failed_ca42v2_register;
        retval = usb_register(&cypress_driver);
        if (retval)
                goto failed_usb_register;
 
        info(DRIVER_DESC " " DRIVER_VERSION);
        return 0;
+
 failed_usb_register:
-       usb_deregister(&cypress_driver);
-failed_hidcom_register:
+       usb_serial_deregister(&cypress_ca42v2_device);
+failed_ca42v2_register:
        usb_serial_deregister(&cypress_hidcom_device);
-failed_em_register:
+failed_hidcom_register:
        usb_serial_deregister(&cypress_earthmate_device);
-
+failed_em_register:
        return retval;
 }
 
@@ -1566,6 +1688,7 @@ static void __exit cypress_exit (void)
        usb_deregister (&cypress_driver);
        usb_serial_deregister (&cypress_earthmate_device);
        usb_serial_deregister (&cypress_hidcom_device);
+       usb_serial_deregister (&cypress_ca42v2_device);
 }