]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - drivers/usb/serial/ssu100.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[net-next-2.6.git] / drivers / usb / serial / ssu100.c
index 819de4740388a13e408068b4349a920f01771a79..68c18fdfc6da320978f9212325fd3f1ecf2ec64c 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/serial.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
+#include <linux/serial_reg.h>
 #include <linux/uaccess.h>
 
 #define QT_OPEN_CLOSE_CHANNEL       0xca
 #define QT_HW_FLOW_CONTROL_MASK     0xc5
 #define QT_SW_FLOW_CONTROL_MASK     0xc6
 
-#define MODEM_CTL_REGISTER         0x04
-#define MODEM_STATUS_REGISTER      0x06
-
-
-#define SERIAL_LSR_OE       0x02
-#define SERIAL_LSR_PE       0x04
-#define SERIAL_LSR_FE       0x08
-#define SERIAL_LSR_BI       0x10
-
-#define SERIAL_LSR_TEMT     0x40
-
-#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_CRTSCTS ((SERIAL_MCR_RTS << 8) | SERIAL_MSR_CTS)
+#define  SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS)
 
-#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_EVEN_PARITY         (UART_LCR_PARITY | UART_LCR_EPAR)
 
 #define  MAX_BAUD_RATE              460800
 
@@ -99,10 +75,12 @@ static struct usb_driver ssu100_driver = {
 };
 
 struct ssu100_port_private {
+       spinlock_t status_lock;
        u8 shadowLSR;
        u8 shadowMSR;
        wait_queue_head_t delta_msr_wait; /* Used for TIOCMIWAIT */
        unsigned short max_packet_size;
+       struct async_icount icount;
 };
 
 static void ssu100_release(struct usb_serial *serial)
@@ -150,9 +128,10 @@ static inline int ssu100_getregister(struct usb_device *dev,
 
 static inline int ssu100_setregister(struct usb_device *dev,
                                     unsigned short uart,
+                                    unsigned short reg,
                                     u16 data)
 {
-       u16 value = (data << 8) | MODEM_CTL_REGISTER;
+       u16 value = (data << 8) | reg;
 
        return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
                               QT_SET_GET_REGISTER, 0x40, value, uart,
@@ -178,11 +157,11 @@ static inline int update_mctrl(struct usb_device *dev, unsigned int set,
        clear &= ~set;  /* 'set' takes precedence over 'clear' */
        urb_value = 0;
        if (set & TIOCM_DTR)
-               urb_value |= SERIAL_MCR_DTR;
+               urb_value |= UART_MCR_DTR;
        if (set & TIOCM_RTS)
-               urb_value |= SERIAL_MCR_RTS;
+               urb_value |= UART_MCR_RTS;
 
-       result = ssu100_setregister(dev, 0, urb_value);
+       result = ssu100_setregister(dev, 0, UART_MCR, urb_value);
        if (result < 0)
                dbg("%s Error from MODEM_CTRL urb", __func__);
 
@@ -264,24 +243,24 @@ static void ssu100_set_termios(struct tty_struct *tty,
 
        if (cflag & PARENB) {
                if (cflag & PARODD)
-                       urb_value |= SERIAL_ODD_PARITY;
+                       urb_value |= UART_LCR_PARITY;
                else
                        urb_value |= SERIAL_EVEN_PARITY;
        }
 
        switch (cflag & CSIZE) {
        case CS5:
-               urb_value |= SERIAL_5_DATA;
+               urb_value |= UART_LCR_WLEN5;
                break;
        case CS6:
-               urb_value |= SERIAL_6_DATA;
+               urb_value |= UART_LCR_WLEN6;
                break;
        case CS7:
-               urb_value |= SERIAL_7_DATA;
+               urb_value |= UART_LCR_WLEN7;
                break;
        default:
        case CS8:
-               urb_value |= SERIAL_8_DATA;
+               urb_value |= UART_LCR_WLEN8;
                break;
        }
 
@@ -333,6 +312,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port)
        struct ssu100_port_private *priv = usb_get_serial_port_data(port);
        u8 *data;
        int result;
+       unsigned long flags;
 
        dbg("%s - port %d", __func__, port->number);
 
@@ -350,11 +330,10 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port)
                return result;
        }
 
-       priv->shadowLSR = data[0]  & (SERIAL_LSR_OE | SERIAL_LSR_PE |
-                                     SERIAL_LSR_FE | SERIAL_LSR_BI);
-
-       priv->shadowMSR = data[1]  & (SERIAL_MSR_CTS | SERIAL_MSR_DSR |
-                                     SERIAL_MSR_RI | SERIAL_MSR_CD);
+       spin_lock_irqsave(&priv->status_lock, flags);
+       priv->shadowLSR = data[0];
+       priv->shadowMSR = data[1];
+       spin_unlock_irqrestore(&priv->status_lock, flags);
 
        kfree(data);
 
@@ -398,11 +377,51 @@ static int get_serial_info(struct usb_serial_port *port,
        return 0;
 }
 
+static int wait_modem_info(struct usb_serial_port *port, unsigned int arg)
+{
+       struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+       struct async_icount prev, cur;
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->status_lock, flags);
+       prev = priv->icount;
+       spin_unlock_irqrestore(&priv->status_lock, flags);
+
+       while (1) {
+               wait_event_interruptible(priv->delta_msr_wait,
+                                        ((priv->icount.rng != prev.rng) ||
+                                         (priv->icount.dsr != prev.dsr) ||
+                                         (priv->icount.dcd != prev.dcd) ||
+                                         (priv->icount.cts != prev.cts)));
+
+               if (signal_pending(current))
+                       return -ERESTARTSYS;
+
+               spin_lock_irqsave(&priv->status_lock, flags);
+               cur = priv->icount;
+               spin_unlock_irqrestore(&priv->status_lock, flags);
+
+               if ((prev.rng == cur.rng) &&
+                   (prev.dsr == cur.dsr) &&
+                   (prev.dcd == cur.dcd) &&
+                   (prev.cts == cur.cts))
+                       return -EIO;
+
+               if ((arg & TIOCM_RNG && (prev.rng != cur.rng)) ||
+                   (arg & TIOCM_DSR && (prev.dsr != cur.dsr)) ||
+                   (arg & TIOCM_CD  && (prev.dcd != cur.dcd)) ||
+                   (arg & TIOCM_CTS && (prev.cts != cur.cts)))
+                       return 0;
+       }
+       return 0;
+}
+
 static int ssu100_ioctl(struct tty_struct *tty, struct file *file,
                    unsigned int cmd, unsigned long arg)
 {
        struct usb_serial_port *port = tty->driver_data;
        struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+       void __user *user_arg = (void __user *)arg;
 
        dbg("%s cmd 0x%04x", __func__, cmd);
 
@@ -412,28 +431,28 @@ static int ssu100_ioctl(struct tty_struct *tty, struct file *file,
                                       (struct serial_struct __user *) arg);
 
        case TIOCMIWAIT:
-               while (priv != NULL) {
-                       u8 prevMSR = priv->shadowMSR & SERIAL_MSR_MASK;
-                       interruptible_sleep_on(&priv->delta_msr_wait);
-                       /* see if a signal did it */
-                       if (signal_pending(current))
-                               return -ERESTARTSYS;
-                       else {
-                               u8 diff = (priv->shadowMSR & SERIAL_MSR_MASK) ^ prevMSR;
-                               if (!diff)
-                                       return -EIO; /* no change => error */
-
-                               /* Return 0 if caller wanted to know about
-                                  these bits */
-
-                               if (((arg & TIOCM_RNG) && (diff & SERIAL_MSR_RI)) ||
-                                   ((arg & TIOCM_DSR) && (diff & SERIAL_MSR_DSR)) ||
-                                   ((arg & TIOCM_CD) && (diff & SERIAL_MSR_CD)) ||
-                                   ((arg & TIOCM_CTS) && (diff & SERIAL_MSR_CTS)))
-                                       return 0;
-                       }
-               }
+               return wait_modem_info(port, arg);
+
+       case TIOCGICOUNT:
+       {
+               struct serial_icounter_struct icount;
+               struct async_icount cnow = priv->icount;
+               memset(&icount, 0, sizeof(icount));
+               icount.cts = cnow.cts;
+               icount.dsr = cnow.dsr;
+               icount.rng = cnow.rng;
+               icount.dcd = cnow.dcd;
+               icount.rx = cnow.rx;
+               icount.tx = cnow.tx;
+               icount.frame = cnow.frame;
+               icount.overrun = cnow.overrun;
+               icount.parity = cnow.parity;
+               icount.brk = cnow.brk;
+               icount.buf_overrun = cnow.buf_overrun;
+               if (copy_to_user(user_arg, &icount, sizeof(icount)))
+                       return -EFAULT;
                return 0;
+       }
 
        default:
                break;
@@ -455,6 +474,7 @@ static void ssu100_set_max_packet_size(struct usb_serial_port *port)
 
        unsigned num_endpoints;
        int i;
+       unsigned long flags;
 
        num_endpoints = interface->cur_altsetting->desc.bNumEndpoints;
        dev_info(&udev->dev, "Number of endpoints %d\n", num_endpoints);
@@ -466,7 +486,9 @@ static void ssu100_set_max_packet_size(struct usb_serial_port *port)
        }
 
        /* set max packet size based on descriptor */
+       spin_lock_irqsave(&priv->status_lock, flags);
        priv->max_packet_size = ep_desc->wMaxPacketSize;
+       spin_unlock_irqrestore(&priv->status_lock, flags);
 
        dev_info(&udev->dev, "Setting MaxPacketSize %d\n", priv->max_packet_size);
 }
@@ -485,9 +507,9 @@ static int ssu100_attach(struct usb_serial *serial)
                return -ENOMEM;
        }
 
+       spin_lock_init(&priv->status_lock);
        init_waitqueue_head(&priv->delta_msr_wait);
        usb_set_serial_port_data(port, priv);
-
        ssu100_set_max_packet_size(port);
 
        return ssu100_initdevice(serial->dev);
@@ -506,20 +528,20 @@ static int ssu100_tiocmget(struct tty_struct *tty, struct file *file)
        if (!d)
                return -ENOMEM;
 
-       r = ssu100_getregister(dev, 0, MODEM_CTL_REGISTER, d);
+       r = ssu100_getregister(dev, 0, UART_MCR, d);
        if (r < 0)
                goto mget_out;
 
-       r = ssu100_getregister(dev, 0, MODEM_STATUS_REGISTER, d+1);
+       r = ssu100_getregister(dev, 0, UART_MSR, d+1);
        if (r < 0)
                goto mget_out;
 
-       r = (d[0] & SERIAL_MCR_DTR ? TIOCM_DTR : 0) |
-               (d[0] & SERIAL_MCR_RTS ? TIOCM_RTS : 0) |
-               (d[1] & SERIAL_MSR_CTS ? TIOCM_CTS : 0) |
-               (d[1] & SERIAL_MSR_CD ? TIOCM_CAR : 0) |
-               (d[1] & SERIAL_MSR_RI ? TIOCM_RI : 0) |
-               (d[1] & SERIAL_MSR_DSR ? TIOCM_DSR : 0);
+       r = (d[0] & UART_MCR_DTR ? TIOCM_DTR : 0) |
+               (d[0] & UART_MCR_RTS ? TIOCM_RTS : 0) |
+               (d[1] & UART_MSR_CTS ? TIOCM_CTS : 0) |
+               (d[1] & UART_MSR_DCD ? TIOCM_CAR : 0) |
+               (d[1] & UART_MSR_RI ? TIOCM_RI : 0) |
+               (d[1] & UART_MSR_DSR ? TIOCM_DSR : 0);
 
 mget_out:
        kfree(d);
@@ -546,7 +568,7 @@ static void ssu100_dtr_rts(struct usb_serial_port *port, int on)
        if (!port->serial->disconnected) {
                /* Disable flow control */
                if (!on &&
-                   ssu100_setregister(dev, 0, 0) < 0)
+                   ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
                        dev_err(&port->dev, "error from flowcontrol urb\n");
                /* drop RTS and DTR */
                if (on)
@@ -557,34 +579,88 @@ static void ssu100_dtr_rts(struct usb_serial_port *port, int on)
        mutex_unlock(&port->serial->disc_mutex);
 }
 
+static void ssu100_update_msr(struct usb_serial_port *port, u8 msr)
+{
+       struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->status_lock, flags);
+       priv->shadowMSR = msr;
+       spin_unlock_irqrestore(&priv->status_lock, flags);
+
+       if (msr & UART_MSR_ANY_DELTA) {
+               /* update input line counters */
+               if (msr & UART_MSR_DCTS)
+                       priv->icount.cts++;
+               if (msr & UART_MSR_DDSR)
+                       priv->icount.dsr++;
+               if (msr & UART_MSR_DDCD)
+                       priv->icount.dcd++;
+               if (msr & UART_MSR_TERI)
+                       priv->icount.rng++;
+               wake_up_interruptible(&priv->delta_msr_wait);
+       }
+}
+
+static void ssu100_update_lsr(struct usb_serial_port *port, u8 lsr,
+                             char *tty_flag)
+{
+       struct ssu100_port_private *priv = usb_get_serial_port_data(port);
+       unsigned long flags;
+
+       spin_lock_irqsave(&priv->status_lock, flags);
+       priv->shadowLSR = lsr;
+       spin_unlock_irqrestore(&priv->status_lock, flags);
+
+       *tty_flag = TTY_NORMAL;
+       if (lsr & UART_LSR_BRK_ERROR_BITS) {
+               /* we always want to update icount, but we only want to
+                * update tty_flag for one case */
+               if (lsr & UART_LSR_BI) {
+                       priv->icount.brk++;
+                       *tty_flag = TTY_BREAK;
+                       usb_serial_handle_break(port);
+               }
+               if (lsr & UART_LSR_PE) {
+                       priv->icount.parity++;
+                       if (*tty_flag == TTY_NORMAL)
+                               *tty_flag = TTY_PARITY;
+               }
+               if (lsr & UART_LSR_FE) {
+                       priv->icount.frame++;
+                       if (*tty_flag == TTY_NORMAL)
+                               *tty_flag = TTY_FRAME;
+               }
+               if (lsr & UART_LSR_OE){
+                       priv->icount.overrun++;
+                       if (*tty_flag == TTY_NORMAL)
+                               *tty_flag = TTY_OVERRUN;
+               }
+       }
+
+}
+
 static int ssu100_process_packet(struct tty_struct *tty,
                                 struct usb_serial_port *port,
                                 struct ssu100_port_private *priv,
                                 char *packet, int len)
 {
        int i;
-       char flag;
+       char flag = TTY_NORMAL;
        char *ch;
 
        dbg("%s - port %d", __func__, port->number);
 
-       if (len < 4) {
-               dbg("%s - malformed packet", __func__);
-               return 0;
-       }
-
-       if ((packet[0] == 0x1b) && (packet[1] == 0x1b) &&
+       if ((len >= 4) &&
+           (packet[0] == 0x1b) && (packet[1] == 0x1b) &&
            ((packet[2] == 0x00) || (packet[2] == 0x01))) {
-               if (packet[2] == 0x00)
-                       priv->shadowLSR = packet[3] & (SERIAL_LSR_OE |
-                                                      SERIAL_LSR_PE |
-                                                      SERIAL_LSR_FE |
-                                                      SERIAL_LSR_BI);
-
-               if (packet[2] == 0x01) {
-                       priv->shadowMSR = packet[3];
-                       wake_up_interruptible(&priv->delta_msr_wait);
+               if (packet[2] == 0x00) {
+                       ssu100_update_lsr(port, packet[3], &flag);
+                       if (flag == TTY_OVERRUN)
+                               tty_insert_flip_char(tty, 0, TTY_OVERRUN);
                }
+               if (packet[2] == 0x01)
+                       ssu100_update_msr(port, packet[3]);
 
                len -= 4;
                ch = packet + 4;
@@ -631,7 +707,6 @@ static void ssu100_process_read_urb(struct urb *urb)
        tty_kref_put(tty);
 }
 
-
 static struct usb_serial_driver ssu100_device = {
        .driver = {
                .owner = THIS_MODULE,
@@ -653,6 +728,7 @@ static struct usb_serial_driver ssu100_device = {
        .tiocmset            = ssu100_tiocmset,
        .ioctl               = ssu100_ioctl,
        .set_termios         = ssu100_set_termios,
+       .disconnect          = usb_serial_generic_disconnect,
 };
 
 static int __init ssu100_init(void)