|
@@ -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
|
|
@@ -27,36 +28,11 @@
|
|
|
#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)
|