mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 02:21:36 +00:00
d9a807461f
Here is the big USB pull request for 3.7-rc1
There are lots of gadget driver changes (including copying a bunch of
files into the drivers/staging/ccg/ directory so that the other gadget
drivers can be fixed up properly without breaking that driver), and we
remove the old obsolete ub.c driver from the tree. There are also the
usual XHCI set of updates, and other various driver changes and updates.
We also are trying hard to remove the old dbg() macro, but the final
bits of that removal will be coming in through the networking tree
before we can delete it for good.
All of these patches have been in the linux-next tree.
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.19 (GNU/Linux)
iEYEABECAAYFAlBp3+AACgkQMUfUDdst+ym5vwCfe93FyJyXn/RDkGz7iBemvWFd
vrwAoIxjaOa4/yWZWcgrWc5bP4aO3ssc
=jYDr
-----END PGP SIGNATURE-----
Merge tag 'usb-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB changes from Greg Kroah-Hartman:
"Here is the big USB pull request for 3.7-rc1
There are lots of gadget driver changes (including copying a bunch of
files into the drivers/staging/ccg/ directory so that the other gadget
drivers can be fixed up properly without breaking that driver), and we
remove the old obsolete ub.c driver from the tree.
There are also the usual XHCI set of updates, and other various driver
changes and updates. We also are trying hard to remove the old dbg()
macro, but the final bits of that removal will be coming in through
the networking tree before we can delete it for good.
All of these patches have been in the linux-next tree.
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>"
Fix up several annoying - but fairly mindless - conflicts due to the
termios structure having moved into the tty device, and often clashing
with dbg -> dev_dbg conversion.
* tag 'usb-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (339 commits)
USB: ezusb: move ezusb.c from drivers/usb/serial to drivers/usb/misc
USB: uas: fix gcc warning
USB: uas: fix locking
USB: Fix race condition when removing host controllers
USB: uas: add locking
USB: uas: fix abort
USB: uas: remove aborted field, replace with status bit.
USB: uas: fix task management
USB: uas: keep track of command urbs
xhci: Intel Panther Point BEI quirk.
powerpc/usb: remove checking PHY_CLK_VALID for UTMI PHY
USB: ftdi_sio: add TIAO USB Multi-Protocol Adapter (TUMPA) support
Revert "usb : Add sysfs files to control port power."
USB: serial: remove vizzini driver
usb: host: xhci: Fix Null pointer dereferencing with 71c731a
for non-x86 systems
Increase XHCI suspend timeout to 16ms
USB: ohci-at91: fix null pointer in ohci_hcd_at91_overcurrent_irq
USB: sierra_ms: don't keep unused variable
fsl/usb: Add support for USB controller version 2.4
USB: qcaux: add Pantech vendor class match
...
916 lines
26 KiB
C
916 lines
26 KiB
C
/*
|
|
* Ours Technology Inc. OTi-6858 USB to serial adapter driver.
|
|
*
|
|
* Copyleft (C) 2007 Kees Lemmens (adapted for kernel 2.6.20)
|
|
* Copyright (C) 2006 Tomasz Michal Lukaszewski (FIXME: add e-mail)
|
|
* Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
|
|
* Copyright (C) 2003 IBM Corp.
|
|
*
|
|
* Many thanks to the authors of pl2303 driver: all functions in this file
|
|
* are heavily based on pl2303 code, buffering code is a 1-to-1 copy.
|
|
*
|
|
* Warning! You use this driver on your own risk! The only official
|
|
* description of this device I have is datasheet from manufacturer,
|
|
* and it doesn't contain almost any information needed to write a driver.
|
|
* Almost all knowlegde used while writing this driver was gathered by:
|
|
* - analyzing traffic between device and the M$ Windows 2000 driver,
|
|
* - trying different bit combinations and checking pin states
|
|
* with a voltmeter,
|
|
* - receiving malformed frames and producing buffer overflows
|
|
* to learn how errors are reported,
|
|
* So, THIS CODE CAN DESTROY OTi-6858 AND ANY OTHER DEVICES, THAT ARE
|
|
* CONNECTED TO IT!
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License.
|
|
*
|
|
* See Documentation/usb/usb-serial.txt for more information on using this
|
|
* driver
|
|
*
|
|
* TODO:
|
|
* - implement correct flushing for ioctls and oti6858_close()
|
|
* - check how errors (rx overflow, parity error, framing error) are reported
|
|
* - implement oti6858_break_ctl()
|
|
* - implement more ioctls
|
|
* - test/implement flow control
|
|
* - allow setting custom baud rates
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_driver.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/serial.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/kfifo.h>
|
|
#include "oti6858.h"
|
|
|
|
#define OTI6858_DESCRIPTION \
|
|
"Ours Technology Inc. OTi-6858 USB to serial adapter driver"
|
|
#define OTI6858_AUTHOR "Tomasz Michal Lukaszewski <FIXME@FIXME>"
|
|
#define OTI6858_VERSION "0.2"
|
|
|
|
static const struct usb_device_id id_table[] = {
|
|
{ USB_DEVICE(OTI6858_VENDOR_ID, OTI6858_PRODUCT_ID) },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, id_table);
|
|
|
|
/* requests */
|
|
#define OTI6858_REQ_GET_STATUS (USB_DIR_IN | USB_TYPE_VENDOR | 0x00)
|
|
#define OTI6858_REQ_T_GET_STATUS 0x01
|
|
|
|
#define OTI6858_REQ_SET_LINE (USB_DIR_OUT | USB_TYPE_VENDOR | 0x00)
|
|
#define OTI6858_REQ_T_SET_LINE 0x00
|
|
|
|
#define OTI6858_REQ_CHECK_TXBUFF (USB_DIR_IN | USB_TYPE_VENDOR | 0x01)
|
|
#define OTI6858_REQ_T_CHECK_TXBUFF 0x00
|
|
|
|
/* format of the control packet */
|
|
struct oti6858_control_pkt {
|
|
__le16 divisor; /* baud rate = 96000000 / (16 * divisor), LE */
|
|
#define OTI6858_MAX_BAUD_RATE 3000000
|
|
u8 frame_fmt;
|
|
#define FMT_STOP_BITS_MASK 0xc0
|
|
#define FMT_STOP_BITS_1 0x00
|
|
#define FMT_STOP_BITS_2 0x40 /* 1.5 stop bits if FMT_DATA_BITS_5 */
|
|
#define FMT_PARITY_MASK 0x38
|
|
#define FMT_PARITY_NONE 0x00
|
|
#define FMT_PARITY_ODD 0x08
|
|
#define FMT_PARITY_EVEN 0x18
|
|
#define FMT_PARITY_MARK 0x28
|
|
#define FMT_PARITY_SPACE 0x38
|
|
#define FMT_DATA_BITS_MASK 0x03
|
|
#define FMT_DATA_BITS_5 0x00
|
|
#define FMT_DATA_BITS_6 0x01
|
|
#define FMT_DATA_BITS_7 0x02
|
|
#define FMT_DATA_BITS_8 0x03
|
|
u8 something; /* always equals 0x43 */
|
|
u8 control; /* settings of flow control lines */
|
|
#define CONTROL_MASK 0x0c
|
|
#define CONTROL_DTR_HIGH 0x08
|
|
#define CONTROL_RTS_HIGH 0x04
|
|
u8 tx_status;
|
|
#define TX_BUFFER_EMPTIED 0x09
|
|
u8 pin_state;
|
|
#define PIN_MASK 0x3f
|
|
#define PIN_RTS 0x20 /* output pin */
|
|
#define PIN_CTS 0x10 /* input pin, active low */
|
|
#define PIN_DSR 0x08 /* input pin, active low */
|
|
#define PIN_DTR 0x04 /* output pin */
|
|
#define PIN_RI 0x02 /* input pin, active low */
|
|
#define PIN_DCD 0x01 /* input pin, active low */
|
|
u8 rx_bytes_avail; /* number of bytes in rx buffer */;
|
|
};
|
|
|
|
#define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt)
|
|
#define OTI6858_CTRL_EQUALS_PENDING(a, priv) \
|
|
(((a)->divisor == (priv)->pending_setup.divisor) \
|
|
&& ((a)->control == (priv)->pending_setup.control) \
|
|
&& ((a)->frame_fmt == (priv)->pending_setup.frame_fmt))
|
|
|
|
/* function prototypes */
|
|
static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port);
|
|
static void oti6858_close(struct usb_serial_port *port);
|
|
static void oti6858_set_termios(struct tty_struct *tty,
|
|
struct usb_serial_port *port, struct ktermios *old);
|
|
static void oti6858_init_termios(struct tty_struct *tty);
|
|
static int oti6858_ioctl(struct tty_struct *tty,
|
|
unsigned int cmd, unsigned long arg);
|
|
static void oti6858_read_int_callback(struct urb *urb);
|
|
static void oti6858_read_bulk_callback(struct urb *urb);
|
|
static void oti6858_write_bulk_callback(struct urb *urb);
|
|
static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port,
|
|
const unsigned char *buf, int count);
|
|
static int oti6858_write_room(struct tty_struct *tty);
|
|
static int oti6858_chars_in_buffer(struct tty_struct *tty);
|
|
static int oti6858_tiocmget(struct tty_struct *tty);
|
|
static int oti6858_tiocmset(struct tty_struct *tty,
|
|
unsigned int set, unsigned int clear);
|
|
static int oti6858_startup(struct usb_serial *serial);
|
|
static void oti6858_release(struct usb_serial *serial);
|
|
|
|
/* device info */
|
|
static struct usb_serial_driver oti6858_device = {
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "oti6858",
|
|
},
|
|
.id_table = id_table,
|
|
.num_ports = 1,
|
|
.open = oti6858_open,
|
|
.close = oti6858_close,
|
|
.write = oti6858_write,
|
|
.ioctl = oti6858_ioctl,
|
|
.set_termios = oti6858_set_termios,
|
|
.init_termios = oti6858_init_termios,
|
|
.tiocmget = oti6858_tiocmget,
|
|
.tiocmset = oti6858_tiocmset,
|
|
.read_bulk_callback = oti6858_read_bulk_callback,
|
|
.read_int_callback = oti6858_read_int_callback,
|
|
.write_bulk_callback = oti6858_write_bulk_callback,
|
|
.write_room = oti6858_write_room,
|
|
.chars_in_buffer = oti6858_chars_in_buffer,
|
|
.attach = oti6858_startup,
|
|
.release = oti6858_release,
|
|
};
|
|
|
|
static struct usb_serial_driver * const serial_drivers[] = {
|
|
&oti6858_device, NULL
|
|
};
|
|
|
|
struct oti6858_private {
|
|
spinlock_t lock;
|
|
|
|
struct oti6858_control_pkt status;
|
|
|
|
struct {
|
|
u8 read_urb_in_use;
|
|
u8 write_urb_in_use;
|
|
} flags;
|
|
struct delayed_work delayed_write_work;
|
|
|
|
struct {
|
|
__le16 divisor;
|
|
u8 frame_fmt;
|
|
u8 control;
|
|
} pending_setup;
|
|
u8 transient;
|
|
u8 setup_done;
|
|
struct delayed_work delayed_setup_work;
|
|
|
|
wait_queue_head_t intr_wait;
|
|
struct usb_serial_port *port; /* USB port with which associated */
|
|
};
|
|
|
|
static void setup_line(struct work_struct *work)
|
|
{
|
|
struct oti6858_private *priv = container_of(work,
|
|
struct oti6858_private, delayed_setup_work.work);
|
|
struct usb_serial_port *port = priv->port;
|
|
struct oti6858_control_pkt *new_setup;
|
|
unsigned long flags;
|
|
int result;
|
|
|
|
new_setup = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL);
|
|
if (new_setup == NULL) {
|
|
dev_err(&port->dev, "%s(): out of memory!\n", __func__);
|
|
/* we will try again */
|
|
schedule_delayed_work(&priv->delayed_setup_work,
|
|
msecs_to_jiffies(2));
|
|
return;
|
|
}
|
|
|
|
result = usb_control_msg(port->serial->dev,
|
|
usb_rcvctrlpipe(port->serial->dev, 0),
|
|
OTI6858_REQ_T_GET_STATUS,
|
|
OTI6858_REQ_GET_STATUS,
|
|
0, 0,
|
|
new_setup, OTI6858_CTRL_PKT_SIZE,
|
|
100);
|
|
|
|
if (result != OTI6858_CTRL_PKT_SIZE) {
|
|
dev_err(&port->dev, "%s(): error reading status\n", __func__);
|
|
kfree(new_setup);
|
|
/* we will try again */
|
|
schedule_delayed_work(&priv->delayed_setup_work,
|
|
msecs_to_jiffies(2));
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (!OTI6858_CTRL_EQUALS_PENDING(new_setup, priv)) {
|
|
new_setup->divisor = priv->pending_setup.divisor;
|
|
new_setup->control = priv->pending_setup.control;
|
|
new_setup->frame_fmt = priv->pending_setup.frame_fmt;
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
result = usb_control_msg(port->serial->dev,
|
|
usb_sndctrlpipe(port->serial->dev, 0),
|
|
OTI6858_REQ_T_SET_LINE,
|
|
OTI6858_REQ_SET_LINE,
|
|
0, 0,
|
|
new_setup, OTI6858_CTRL_PKT_SIZE,
|
|
100);
|
|
} else {
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
result = 0;
|
|
}
|
|
kfree(new_setup);
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (result != OTI6858_CTRL_PKT_SIZE)
|
|
priv->transient = 0;
|
|
priv->setup_done = 1;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
|
|
result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
|
|
if (result != 0) {
|
|
dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
|
|
__func__, result);
|
|
}
|
|
}
|
|
|
|
static void send_data(struct work_struct *work)
|
|
{
|
|
struct oti6858_private *priv = container_of(work,
|
|
struct oti6858_private, delayed_write_work.work);
|
|
struct usb_serial_port *port = priv->port;
|
|
int count = 0, result;
|
|
unsigned long flags;
|
|
u8 *allow;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (priv->flags.write_urb_in_use) {
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
schedule_delayed_work(&priv->delayed_write_work,
|
|
msecs_to_jiffies(2));
|
|
return;
|
|
}
|
|
priv->flags.write_urb_in_use = 1;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
count = kfifo_len(&port->write_fifo);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
if (count > port->bulk_out_size)
|
|
count = port->bulk_out_size;
|
|
|
|
if (count != 0) {
|
|
allow = kmalloc(1, GFP_KERNEL);
|
|
if (!allow) {
|
|
dev_err_console(port, "%s(): kmalloc failed\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
result = usb_control_msg(port->serial->dev,
|
|
usb_rcvctrlpipe(port->serial->dev, 0),
|
|
OTI6858_REQ_T_CHECK_TXBUFF,
|
|
OTI6858_REQ_CHECK_TXBUFF,
|
|
count, 0, allow, 1, 100);
|
|
if (result != 1 || *allow != 0)
|
|
count = 0;
|
|
kfree(allow);
|
|
}
|
|
|
|
if (count == 0) {
|
|
priv->flags.write_urb_in_use = 0;
|
|
|
|
dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
|
|
result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
|
|
if (result != 0) {
|
|
dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
|
|
__func__, result);
|
|
}
|
|
return;
|
|
}
|
|
|
|
count = kfifo_out_locked(&port->write_fifo,
|
|
port->write_urb->transfer_buffer,
|
|
count, &port->lock);
|
|
port->write_urb->transfer_buffer_length = count;
|
|
result = usb_submit_urb(port->write_urb, GFP_NOIO);
|
|
if (result != 0) {
|
|
dev_err_console(port, "%s(): usb_submit_urb() failed with error %d\n",
|
|
__func__, result);
|
|
priv->flags.write_urb_in_use = 0;
|
|
}
|
|
|
|
usb_serial_port_softint(port);
|
|
}
|
|
|
|
static int oti6858_startup(struct usb_serial *serial)
|
|
{
|
|
struct usb_serial_port *port = serial->port[0];
|
|
struct oti6858_private *priv;
|
|
int i;
|
|
|
|
for (i = 0; i < serial->num_ports; ++i) {
|
|
priv = kzalloc(sizeof(struct oti6858_private), GFP_KERNEL);
|
|
if (!priv)
|
|
break;
|
|
|
|
spin_lock_init(&priv->lock);
|
|
init_waitqueue_head(&priv->intr_wait);
|
|
/* INIT_WORK(&priv->setup_work, setup_line, serial->port[i]); */
|
|
/* INIT_WORK(&priv->write_work, send_data, serial->port[i]); */
|
|
priv->port = port;
|
|
INIT_DELAYED_WORK(&priv->delayed_setup_work, setup_line);
|
|
INIT_DELAYED_WORK(&priv->delayed_write_work, send_data);
|
|
|
|
usb_set_serial_port_data(serial->port[i], priv);
|
|
}
|
|
if (i == serial->num_ports)
|
|
return 0;
|
|
|
|
for (--i; i >= 0; --i) {
|
|
priv = usb_get_serial_port_data(serial->port[i]);
|
|
kfree(priv);
|
|
usb_set_serial_port_data(serial->port[i], NULL);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int oti6858_write(struct tty_struct *tty, struct usb_serial_port *port,
|
|
const unsigned char *buf, int count)
|
|
{
|
|
if (!count)
|
|
return count;
|
|
|
|
count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int oti6858_write_room(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
int room = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
room = kfifo_avail(&port->write_fifo);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
return room;
|
|
}
|
|
|
|
static int oti6858_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
int chars = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
chars = kfifo_len(&port->write_fifo);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
return chars;
|
|
}
|
|
|
|
static void oti6858_init_termios(struct tty_struct *tty)
|
|
{
|
|
tty->termios = tty_std_termios;
|
|
tty->termios.c_cflag = B38400 | CS8 | CREAD | HUPCL | CLOCAL;
|
|
tty->termios.c_ispeed = 38400;
|
|
tty->termios.c_ospeed = 38400;
|
|
}
|
|
|
|
static void oti6858_set_termios(struct tty_struct *tty,
|
|
struct usb_serial_port *port, struct ktermios *old_termios)
|
|
{
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
unsigned long flags;
|
|
unsigned int cflag;
|
|
u8 frame_fmt, control;
|
|
__le16 divisor;
|
|
int br;
|
|
|
|
if (!tty)
|
|
return;
|
|
|
|
cflag = tty->termios.c_cflag;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
divisor = priv->pending_setup.divisor;
|
|
frame_fmt = priv->pending_setup.frame_fmt;
|
|
control = priv->pending_setup.control;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
frame_fmt &= ~FMT_DATA_BITS_MASK;
|
|
switch (cflag & CSIZE) {
|
|
case CS5:
|
|
frame_fmt |= FMT_DATA_BITS_5;
|
|
break;
|
|
case CS6:
|
|
frame_fmt |= FMT_DATA_BITS_6;
|
|
break;
|
|
case CS7:
|
|
frame_fmt |= FMT_DATA_BITS_7;
|
|
break;
|
|
default:
|
|
case CS8:
|
|
frame_fmt |= FMT_DATA_BITS_8;
|
|
break;
|
|
}
|
|
|
|
/* manufacturer claims that this device can work with baud rates
|
|
* up to 3 Mbps; I've tested it only on 115200 bps, so I can't
|
|
* guarantee that any other baud rate will work (especially
|
|
* the higher ones)
|
|
*/
|
|
br = tty_get_baud_rate(tty);
|
|
if (br == 0) {
|
|
divisor = 0;
|
|
} else {
|
|
int real_br;
|
|
int new_divisor;
|
|
br = min(br, OTI6858_MAX_BAUD_RATE);
|
|
|
|
new_divisor = (96000000 + 8 * br) / (16 * br);
|
|
real_br = 96000000 / (16 * new_divisor);
|
|
divisor = cpu_to_le16(new_divisor);
|
|
tty_encode_baud_rate(tty, real_br, real_br);
|
|
}
|
|
|
|
frame_fmt &= ~FMT_STOP_BITS_MASK;
|
|
if ((cflag & CSTOPB) != 0)
|
|
frame_fmt |= FMT_STOP_BITS_2;
|
|
else
|
|
frame_fmt |= FMT_STOP_BITS_1;
|
|
|
|
frame_fmt &= ~FMT_PARITY_MASK;
|
|
if ((cflag & PARENB) != 0) {
|
|
if ((cflag & PARODD) != 0)
|
|
frame_fmt |= FMT_PARITY_ODD;
|
|
else
|
|
frame_fmt |= FMT_PARITY_EVEN;
|
|
} else {
|
|
frame_fmt |= FMT_PARITY_NONE;
|
|
}
|
|
|
|
control &= ~CONTROL_MASK;
|
|
if ((cflag & CRTSCTS) != 0)
|
|
control |= (CONTROL_DTR_HIGH | CONTROL_RTS_HIGH);
|
|
|
|
/* change control lines if we are switching to or from B0 */
|
|
/* FIXME:
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
control = priv->line_control;
|
|
if ((cflag & CBAUD) == B0)
|
|
priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS);
|
|
else
|
|
priv->line_control |= (CONTROL_DTR | CONTROL_RTS);
|
|
if (control != priv->line_control) {
|
|
control = priv->line_control;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
set_control_lines(serial->dev, control);
|
|
} else {
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
*/
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (divisor != priv->pending_setup.divisor
|
|
|| control != priv->pending_setup.control
|
|
|| frame_fmt != priv->pending_setup.frame_fmt) {
|
|
priv->pending_setup.divisor = divisor;
|
|
priv->pending_setup.control = control;
|
|
priv->pending_setup.frame_fmt = frame_fmt;
|
|
}
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
static int oti6858_open(struct tty_struct *tty, struct usb_serial_port *port)
|
|
{
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
struct ktermios tmp_termios;
|
|
struct usb_serial *serial = port->serial;
|
|
struct oti6858_control_pkt *buf;
|
|
unsigned long flags;
|
|
int result;
|
|
|
|
usb_clear_halt(serial->dev, port->write_urb->pipe);
|
|
usb_clear_halt(serial->dev, port->read_urb->pipe);
|
|
|
|
buf = kmalloc(OTI6858_CTRL_PKT_SIZE, GFP_KERNEL);
|
|
if (buf == NULL) {
|
|
dev_err(&port->dev, "%s(): out of memory!\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
|
|
OTI6858_REQ_T_GET_STATUS,
|
|
OTI6858_REQ_GET_STATUS,
|
|
0, 0,
|
|
buf, OTI6858_CTRL_PKT_SIZE,
|
|
100);
|
|
if (result != OTI6858_CTRL_PKT_SIZE) {
|
|
/* assume default (after power-on reset) values */
|
|
buf->divisor = cpu_to_le16(0x009c); /* 38400 bps */
|
|
buf->frame_fmt = 0x03; /* 8N1 */
|
|
buf->something = 0x43;
|
|
buf->control = 0x4c; /* DTR, RTS */
|
|
buf->tx_status = 0x00;
|
|
buf->pin_state = 0x5b; /* RTS, CTS, DSR, DTR, RI, DCD */
|
|
buf->rx_bytes_avail = 0x00;
|
|
}
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
memcpy(&priv->status, buf, OTI6858_CTRL_PKT_SIZE);
|
|
priv->pending_setup.divisor = buf->divisor;
|
|
priv->pending_setup.frame_fmt = buf->frame_fmt;
|
|
priv->pending_setup.control = buf->control;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
kfree(buf);
|
|
|
|
dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
|
|
result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
|
|
if (result != 0) {
|
|
dev_err(&port->dev, "%s(): usb_submit_urb() failed with error %d\n",
|
|
__func__, result);
|
|
oti6858_close(port);
|
|
return result;
|
|
}
|
|
|
|
/* setup termios */
|
|
if (tty)
|
|
oti6858_set_termios(tty, port, &tmp_termios);
|
|
port->port.drain_delay = 256; /* FIXME: check the FIFO length */
|
|
return 0;
|
|
}
|
|
|
|
static void oti6858_close(struct usb_serial_port *port)
|
|
{
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
/* clear out any remaining data in the buffer */
|
|
kfifo_reset_out(&port->write_fifo);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
dev_dbg(&port->dev, "%s(): after buf_clear()\n", __func__);
|
|
|
|
/* cancel scheduled setup */
|
|
cancel_delayed_work_sync(&priv->delayed_setup_work);
|
|
cancel_delayed_work_sync(&priv->delayed_write_work);
|
|
|
|
/* shutdown our urbs */
|
|
dev_dbg(&port->dev, "%s(): shutting down urbs\n", __func__);
|
|
usb_kill_urb(port->write_urb);
|
|
usb_kill_urb(port->read_urb);
|
|
usb_kill_urb(port->interrupt_in_urb);
|
|
}
|
|
|
|
static int oti6858_tiocmset(struct tty_struct *tty,
|
|
unsigned int set, unsigned int clear)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
unsigned long flags;
|
|
u8 control;
|
|
|
|
dev_dbg(&port->dev, "%s(set = 0x%08x, clear = 0x%08x)\n",
|
|
__func__, set, clear);
|
|
|
|
/* FIXME: check if this is correct (active high/low) */
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
control = priv->pending_setup.control;
|
|
if ((set & TIOCM_RTS) != 0)
|
|
control |= CONTROL_RTS_HIGH;
|
|
if ((set & TIOCM_DTR) != 0)
|
|
control |= CONTROL_DTR_HIGH;
|
|
if ((clear & TIOCM_RTS) != 0)
|
|
control &= ~CONTROL_RTS_HIGH;
|
|
if ((clear & TIOCM_DTR) != 0)
|
|
control &= ~CONTROL_DTR_HIGH;
|
|
|
|
if (control != priv->pending_setup.control)
|
|
priv->pending_setup.control = control;
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int oti6858_tiocmget(struct tty_struct *tty)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
unsigned long flags;
|
|
unsigned pin_state;
|
|
unsigned result = 0;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
pin_state = priv->status.pin_state & PIN_MASK;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
/* FIXME: check if this is correct (active high/low) */
|
|
if ((pin_state & PIN_RTS) != 0)
|
|
result |= TIOCM_RTS;
|
|
if ((pin_state & PIN_CTS) != 0)
|
|
result |= TIOCM_CTS;
|
|
if ((pin_state & PIN_DSR) != 0)
|
|
result |= TIOCM_DSR;
|
|
if ((pin_state & PIN_DTR) != 0)
|
|
result |= TIOCM_DTR;
|
|
if ((pin_state & PIN_RI) != 0)
|
|
result |= TIOCM_RI;
|
|
if ((pin_state & PIN_DCD) != 0)
|
|
result |= TIOCM_CD;
|
|
|
|
dev_dbg(&port->dev, "%s() = 0x%08x\n", __func__, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int wait_modem_info(struct usb_serial_port *port, unsigned int arg)
|
|
{
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
unsigned long flags;
|
|
unsigned int prev, status;
|
|
unsigned int changed;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
prev = priv->status.pin_state;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
while (1) {
|
|
wait_event_interruptible(priv->intr_wait,
|
|
priv->status.pin_state != prev);
|
|
if (signal_pending(current))
|
|
return -ERESTARTSYS;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
status = priv->status.pin_state & PIN_MASK;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
changed = prev ^ status;
|
|
/* FIXME: check if this is correct (active high/low) */
|
|
if (((arg & TIOCM_RNG) && (changed & PIN_RI)) ||
|
|
((arg & TIOCM_DSR) && (changed & PIN_DSR)) ||
|
|
((arg & TIOCM_CD) && (changed & PIN_DCD)) ||
|
|
((arg & TIOCM_CTS) && (changed & PIN_CTS)))
|
|
return 0;
|
|
prev = status;
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
return 0;
|
|
}
|
|
|
|
static int oti6858_ioctl(struct tty_struct *tty,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct usb_serial_port *port = tty->driver_data;
|
|
|
|
dev_dbg(&port->dev, "%s(cmd = 0x%04x, arg = 0x%08lx)\n", __func__, cmd, arg);
|
|
|
|
switch (cmd) {
|
|
case TIOCMIWAIT:
|
|
dev_dbg(&port->dev, "%s(): TIOCMIWAIT\n", __func__);
|
|
return wait_modem_info(port, arg);
|
|
default:
|
|
dev_dbg(&port->dev, "%s(): 0x%04x not supported\n", __func__, cmd);
|
|
break;
|
|
}
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
|
|
|
|
static void oti6858_release(struct usb_serial *serial)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < serial->num_ports; ++i)
|
|
kfree(usb_get_serial_port_data(serial->port[i]));
|
|
}
|
|
|
|
static void oti6858_read_int_callback(struct urb *urb)
|
|
{
|
|
struct usb_serial_port *port = urb->context;
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
int transient = 0, can_recv = 0, resubmit = 1;
|
|
int status = urb->status;
|
|
|
|
switch (status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n",
|
|
__func__, status);
|
|
return;
|
|
default:
|
|
dev_dbg(&urb->dev->dev, "%s(): nonzero urb status received: %d\n",
|
|
__func__, status);
|
|
break;
|
|
}
|
|
|
|
if (status == 0 && urb->actual_length == OTI6858_CTRL_PKT_SIZE) {
|
|
struct oti6858_control_pkt *xs = urb->transfer_buffer;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
if (!priv->transient) {
|
|
if (!OTI6858_CTRL_EQUALS_PENDING(xs, priv)) {
|
|
if (xs->rx_bytes_avail == 0) {
|
|
priv->transient = 4;
|
|
priv->setup_done = 0;
|
|
resubmit = 0;
|
|
dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__);
|
|
schedule_delayed_work(&priv->delayed_setup_work, 0);
|
|
}
|
|
}
|
|
} else {
|
|
if (OTI6858_CTRL_EQUALS_PENDING(xs, priv)) {
|
|
priv->transient = 0;
|
|
} else if (!priv->setup_done) {
|
|
resubmit = 0;
|
|
} else if (--priv->transient == 0) {
|
|
if (xs->rx_bytes_avail == 0) {
|
|
priv->transient = 4;
|
|
priv->setup_done = 0;
|
|
resubmit = 0;
|
|
dev_dbg(&port->dev, "%s(): scheduling setup_line()\n", __func__);
|
|
schedule_delayed_work(&priv->delayed_setup_work, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!priv->transient) {
|
|
if (xs->pin_state != priv->status.pin_state)
|
|
wake_up_interruptible(&priv->intr_wait);
|
|
memcpy(&priv->status, xs, OTI6858_CTRL_PKT_SIZE);
|
|
}
|
|
|
|
if (!priv->transient && xs->rx_bytes_avail != 0) {
|
|
can_recv = xs->rx_bytes_avail;
|
|
priv->flags.read_urb_in_use = 1;
|
|
}
|
|
|
|
transient = priv->transient;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
if (can_recv) {
|
|
int result;
|
|
|
|
result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
|
|
if (result != 0) {
|
|
priv->flags.read_urb_in_use = 0;
|
|
dev_err(&port->dev, "%s(): usb_submit_urb() failed,"
|
|
" error %d\n", __func__, result);
|
|
} else {
|
|
resubmit = 0;
|
|
}
|
|
} else if (!transient) {
|
|
unsigned long flags;
|
|
int count;
|
|
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
count = kfifo_len(&port->write_fifo);
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
if (priv->flags.write_urb_in_use == 0 && count != 0) {
|
|
schedule_delayed_work(&priv->delayed_write_work, 0);
|
|
resubmit = 0;
|
|
}
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
if (resubmit) {
|
|
int result;
|
|
|
|
/* dev_dbg(&urb->dev->dev, "%s(): submitting interrupt urb\n", __func__); */
|
|
result = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (result != 0) {
|
|
dev_err(&urb->dev->dev,
|
|
"%s(): usb_submit_urb() failed with"
|
|
" error %d\n", __func__, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void oti6858_read_bulk_callback(struct urb *urb)
|
|
{
|
|
struct usb_serial_port *port = urb->context;
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
struct tty_struct *tty;
|
|
unsigned char *data = urb->transfer_buffer;
|
|
unsigned long flags;
|
|
int status = urb->status;
|
|
int result;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
priv->flags.read_urb_in_use = 0;
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
if (status != 0) {
|
|
dev_dbg(&urb->dev->dev, "%s(): unable to handle the error, exiting\n", __func__);
|
|
return;
|
|
}
|
|
|
|
tty = tty_port_tty_get(&port->port);
|
|
if (tty != NULL && urb->actual_length > 0) {
|
|
tty_insert_flip_string(tty, data, urb->actual_length);
|
|
tty_flip_buffer_push(tty);
|
|
}
|
|
tty_kref_put(tty);
|
|
|
|
/* schedule the interrupt urb */
|
|
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
|
|
if (result != 0 && result != -EPERM) {
|
|
dev_err(&port->dev, "%s(): usb_submit_urb() failed,"
|
|
" error %d\n", __func__, result);
|
|
}
|
|
}
|
|
|
|
static void oti6858_write_bulk_callback(struct urb *urb)
|
|
{
|
|
struct usb_serial_port *port = urb->context;
|
|
struct oti6858_private *priv = usb_get_serial_port_data(port);
|
|
int status = urb->status;
|
|
int result;
|
|
|
|
switch (status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(&urb->dev->dev, "%s(): urb shutting down with status: %d\n", __func__, status);
|
|
priv->flags.write_urb_in_use = 0;
|
|
return;
|
|
default:
|
|
/* error in the urb, so we have to resubmit it */
|
|
dev_dbg(&urb->dev->dev, "%s(): nonzero write bulk status received: %d\n", __func__, status);
|
|
dev_dbg(&urb->dev->dev, "%s(): overflow in write\n", __func__);
|
|
|
|
port->write_urb->transfer_buffer_length = 1;
|
|
result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
|
|
if (result) {
|
|
dev_err_console(port, "%s(): usb_submit_urb() failed,"
|
|
" error %d\n", __func__, result);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
priv->flags.write_urb_in_use = 0;
|
|
|
|
/* schedule the interrupt urb if we are still open */
|
|
dev_dbg(&port->dev, "%s(): submitting interrupt urb\n", __func__);
|
|
result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
|
|
if (result != 0) {
|
|
dev_err(&port->dev, "%s(): failed submitting int urb,"
|
|
" error %d\n", __func__, result);
|
|
}
|
|
}
|
|
|
|
module_usb_serial_driver(serial_drivers, id_table);
|
|
|
|
MODULE_DESCRIPTION(OTI6858_DESCRIPTION);
|
|
MODULE_AUTHOR(OTI6858_AUTHOR);
|
|
MODULE_VERSION(OTI6858_VERSION);
|
|
MODULE_LICENSE("GPL");
|