]> git.karo-electronics.de Git - linux-beck.git/blobdiff - drivers/usb/serial/ftdi_sio.c
ft232: support the ASYNC_LOW_LATENCY flag
[linux-beck.git] / drivers / usb / serial / ftdi_sio.c
index 8100f1d25904cfc1787018e0da31fe9500f411a5..21c053c31b9ef2086c972416d92d48491b809df0 100644 (file)
@@ -56,6 +56,7 @@ static __u16 vendor = FTDI_VID;
 static __u16 product;
 
 struct ftdi_private {
+       struct kref kref;
        ftdi_chip_type_t chip_type;
                                /* type of device, either SIO or FT8U232AM */
        int baud_base;          /* baud base clock for divisor setting */
@@ -88,6 +89,7 @@ struct ftdi_private {
        int force_rtscts;       /* if non-zero, force RTS-CTS to always
                                   be enabled */
 
+       unsigned int latency;           /* latency setting in use */
        spinlock_t tx_lock;     /* spinlock for transmit state */
        unsigned long tx_bytes;
        unsigned long tx_outstanding_bytes;
@@ -669,6 +671,8 @@ static struct usb_device_id id_table_combined [] = {
        { USB_DEVICE(ADI_VID, ADI_GNICE_PID),
                .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) },
+       { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID),
+               .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk },
        { },                                    /* Optional parameter entry */
        { }                                     /* Terminating entry */
 };
@@ -716,8 +720,8 @@ static int  ftdi_sio_port_probe(struct usb_serial_port *port);
 static int  ftdi_sio_port_remove(struct usb_serial_port *port);
 static int  ftdi_open(struct tty_struct *tty,
                        struct usb_serial_port *port, struct file *filp);
-static void ftdi_close(struct tty_struct *tty,
-                       struct usb_serial_port *port, struct file *filp);
+static void ftdi_close(struct usb_serial_port *port);
+static void ftdi_dtr_rts(struct usb_serial_port *port, int on);
 static int  ftdi_write(struct tty_struct *tty, struct usb_serial_port *port,
                        const unsigned char *buf, int count);
 static int  ftdi_write_room(struct tty_struct *tty);
@@ -755,6 +759,7 @@ static struct usb_serial_driver ftdi_sio_device = {
        .port_remove =          ftdi_sio_port_remove,
        .open =                 ftdi_open,
        .close =                ftdi_close,
+       .dtr_rts =              ftdi_dtr_rts,
        .throttle =             ftdi_throttle,
        .unthrottle =           ftdi_unthrottle,
        .write =                ftdi_write,
@@ -1034,7 +1039,54 @@ static int change_speed(struct tty_struct *tty, struct usb_serial_port *port)
        return rv;
 }
 
+static int write_latency_timer(struct usb_serial_port *port)
+{
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+       struct usb_device *udev = port->serial->dev;
+       char buf[1];
+       int rv = 0;
+       int l = priv->latency;
+
+       if (priv->flags & ASYNC_LOW_LATENCY)
+               l = 1;
+
+       dbg("%s: setting latency timer = %i", __func__, l);
 
+       rv = usb_control_msg(udev,
+                            usb_sndctrlpipe(udev, 0),
+                            FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
+                            FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
+                            l, priv->interface,
+                            buf, 0, WDR_TIMEOUT);
+
+       if (rv < 0)
+               dev_err(&port->dev, "Unable to write latency timer: %i\n", rv);
+       return rv;
+}
+
+static int read_latency_timer(struct usb_serial_port *port)
+{
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+       struct usb_device *udev = port->serial->dev;
+       unsigned short latency = 0;
+       int rv = 0;
+
+
+       dbg("%s", __func__);
+
+       rv = usb_control_msg(udev,
+                            usb_rcvctrlpipe(udev, 0),
+                            FTDI_SIO_GET_LATENCY_TIMER_REQUEST,
+                            FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE,
+                            0, priv->interface,
+                            (char *) &latency, 1, WDR_TIMEOUT);
+
+       if (rv < 0) {
+               dev_err(&port->dev, "Unable to read latency timer: %i\n", rv);
+               return -EIO;
+       }
+       return latency;
+}
 
 static int get_serial_info(struct usb_serial_port *port,
                                struct serial_struct __user *retinfo)
@@ -1094,6 +1146,7 @@ static int set_serial_info(struct tty_struct *tty,
        priv->custom_divisor = new_serial.custom_divisor;
 
        tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+       write_latency_timer(port);
 
 check_and_exit:
        if ((old_priv.flags & ASYNC_SPD_MASK) !=
@@ -1189,27 +1242,13 @@ static ssize_t show_latency_timer(struct device *dev,
 {
        struct usb_serial_port *port = to_usb_serial_port(dev);
        struct ftdi_private *priv = usb_get_serial_port_data(port);
-       struct usb_device *udev = port->serial->dev;
-       unsigned short latency = 0;
-       int rv = 0;
-
-
-       dbg("%s", __func__);
-
-       rv = usb_control_msg(udev,
-                            usb_rcvctrlpipe(udev, 0),
-                            FTDI_SIO_GET_LATENCY_TIMER_REQUEST,
-                            FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE,
-                            0, priv->interface,
-                            (char *) &latency, 1, WDR_TIMEOUT);
-
-       if (rv < 0) {
-               dev_err(dev, "Unable to read latency timer: %i\n", rv);
-               return -EIO;
-       }
-       return sprintf(buf, "%i\n", latency);
+       if (priv->flags & ASYNC_LOW_LATENCY)
+               return sprintf(buf, "1\n");
+       else
+               return sprintf(buf, "%i\n", priv->latency);
 }
 
+
 /* Write a new value of the latency timer, in units of milliseconds. */
 static ssize_t store_latency_timer(struct device *dev,
                        struct device_attribute *attr, const char *valbuf,
@@ -1217,25 +1256,13 @@ static ssize_t store_latency_timer(struct device *dev,
 {
        struct usb_serial_port *port = to_usb_serial_port(dev);
        struct ftdi_private *priv = usb_get_serial_port_data(port);
-       struct usb_device *udev = port->serial->dev;
-       char buf[1];
        int v = simple_strtoul(valbuf, NULL, 10);
        int rv = 0;
 
-       dbg("%s: setting latency timer = %i", __func__, v);
-
-       rv = usb_control_msg(udev,
-                            usb_sndctrlpipe(udev, 0),
-                            FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
-                            FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
-                            v, priv->interface,
-                            buf, 0, WDR_TIMEOUT);
-
-       if (rv < 0) {
-               dev_err(dev, "Unable to write latency timer: %i\n", rv);
+       priv->latency = v;
+       rv = write_latency_timer(port);
+       if (rv < 0)
                return -EIO;
-       }
-
        return count;
 }
 
@@ -1352,6 +1379,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
                return -ENOMEM;
        }
 
+       kref_init(&priv->kref);
        spin_lock_init(&priv->rx_lock);
        spin_lock_init(&priv->tx_lock);
        init_waitqueue_head(&priv->delta_msr_wait);
@@ -1388,6 +1416,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port)
        usb_set_serial_port_data(port, priv);
 
        ftdi_determine_type(port);
+       read_latency_timer(port);
        create_sysfs_attrs(port);
        return 0;
 }
@@ -1468,6 +1497,13 @@ static void ftdi_shutdown(struct usb_serial *serial)
        dbg("%s", __func__);
 }
 
+static void ftdi_sio_priv_release(struct kref *k)
+{
+       struct ftdi_private *priv = container_of(k, struct ftdi_private, kref);
+
+       kfree(priv);
+}
+
 static int ftdi_sio_port_remove(struct usb_serial_port *port)
 {
        struct ftdi_private *priv = usb_get_serial_port_data(port);
@@ -1476,14 +1512,7 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port)
 
        remove_sysfs_attrs(port);
 
-       /* all open ports are closed at this point
-        *    (by usbserial.c:__serial_close, which calls ftdi_close)
-        */
-
-       if (priv) {
-               usb_set_serial_port_data(port, NULL);
-               kfree(priv);
-       }
+       kref_put(&priv->kref, ftdi_sio_priv_release);
 
        return 0;
 }
@@ -1510,6 +1539,8 @@ static int ftdi_open(struct tty_struct *tty,
        if (tty)
                tty->low_latency = (priv->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
 
+       write_latency_timer(port);
+
        /* No error checking for this (will get errors later anyway) */
        /* See ftdi_sio.h for description of what is reset */
        usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
@@ -1547,12 +1578,37 @@ static int ftdi_open(struct tty_struct *tty,
                dev_err(&port->dev,
                        "%s - failed submitting read urb, error %d\n",
                        __func__, result);
-
+       else
+               kref_get(&priv->kref);
 
        return result;
 } /* ftdi_open */
 
 
+static void ftdi_dtr_rts(struct usb_serial_port *port, int on)
+{
+       struct ftdi_private *priv = usb_get_serial_port_data(port);
+       char buf[1];
+
+       mutex_lock(&port->serial->disc_mutex);
+       if (!port->serial->disconnected) {
+               /* Disable flow control */
+               if (!on && usb_control_msg(port->serial->dev,
+                           usb_sndctrlpipe(port->serial->dev, 0),
+                           FTDI_SIO_SET_FLOW_CTRL_REQUEST,
+                           FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
+                           0, priv->interface, buf, 0,
+                           WDR_TIMEOUT) < 0) {
+                           dev_err(&port->dev, "error from flowcontrol urb\n");
+               }
+               /* drop RTS and DTR */
+               if (on)
+                       set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+               else
+                       clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+       }
+       mutex_unlock(&port->serial->disc_mutex);
+}
 
 /*
  * usbserial:__serial_close  only calls ftdi_close if the point is open
@@ -1562,38 +1618,19 @@ static int ftdi_open(struct tty_struct *tty,
  *
  */
 
-static void ftdi_close(struct tty_struct *tty,
-                       struct usb_serial_port *port, struct file *filp)
+static void ftdi_close(struct usb_serial_port *port)
 { /* ftdi_close */
-       unsigned int c_cflag = tty->termios->c_cflag;
        struct ftdi_private *priv = usb_get_serial_port_data(port);
-       char buf[1];
 
        dbg("%s", __func__);
 
-       mutex_lock(&port->serial->disc_mutex);
-       if (c_cflag & HUPCL && !port->serial->disconnected) {
-               /* Disable flow control */
-               if (usb_control_msg(port->serial->dev,
-                                   usb_sndctrlpipe(port->serial->dev, 0),
-                                   FTDI_SIO_SET_FLOW_CTRL_REQUEST,
-                                   FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
-                                   0, priv->interface, buf, 0,
-                                   WDR_TIMEOUT) < 0) {
-                       dev_err(&port->dev, "error from flowcontrol urb\n");
-               }
-
-               /* drop RTS and DTR */
-               clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
-       } /* Note change no line if hupcl is off */
-       mutex_unlock(&port->serial->disc_mutex);
 
        /* cancel any scheduled reading */
-       cancel_delayed_work(&priv->rx_work);
-       flush_scheduled_work();
+       cancel_delayed_work_sync(&priv->rx_work);
 
        /* shutdown our bulk read */
        usb_kill_urb(port->read_urb);
+       kref_put(&priv->kref, ftdi_sio_priv_release);
 } /* ftdi_close */