/*
* usb-serial driver for Quatech USB 2 devices
*
* Copyright (C) 2012 Bill Pemberton (wfp5p@virginia.edu)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
*
* These devices all have only 1 bulk in and 1 bulk out that is shared
* for all serial ports.
*
*/
#include <asm/unaligned.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/serial.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/serial_reg.h>
#include <linux/uaccess.h>
/* default urb timeout for usb operations */
#define QT2_USB_TIMEOUT USB_CTRL_SET_TIMEOUT
#define QT_OPEN_CLOSE_CHANNEL 0xca
#define QT_SET_GET_DEVICE 0xc2
#define QT_SET_GET_REGISTER 0xc0
#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc
#define QT_SET_ATF 0xcd
#define QT_TRANSFER_IN 0xc0
#define QT_HW_FLOW_CONTROL_MASK 0xc5
#define QT_SW_FLOW_CONTROL_MASK 0xc6
#define QT2_BREAK_CONTROL 0xc8
#define QT2_GET_SET_UART 0xc1
#define QT2_FLUSH_DEVICE 0xc4
#define QT2_GET_SET_QMCR 0xe1
#define QT2_QMCR_RS232 0x40
#define QT2_QMCR_RS422 0x10
#define SERIAL_CRTSCTS ((UART_MCR_RTS << 8) | UART_MSR_CTS)
#define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR)
/* status bytes for the device */
#define QT2_CONTROL_BYTE 0x1b
#define QT2_LINE_STATUS 0x00 /* following 1 byte is line status */
#define QT2_MODEM_STATUS 0x01 /* following 1 byte is modem status */
#define QT2_XMIT_HOLD 0x02 /* following 2 bytes are ?? */
#define QT2_CHANGE_PORT 0x03 /* following 1 byte is port to change to */
#define QT2_REC_FLUSH 0x04 /* no following info */
#define QT2_XMIT_FLUSH 0x05 /* no following info */
#define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */
#define MAX_BAUD_RATE 921600
#define DEFAULT_BAUD_RATE 9600
#define QT2_READ_BUFFER_SIZE 512 /* size of read buffer */
#define QT2_WRITE_BUFFER_SIZE 512 /* size of write buffer */
#define QT2_WRITE_CONTROL_SIZE 5 /* control bytes used for a write */
#define DRIVER_DESC "Quatech 2nd gen USB to Serial Driver"
#define USB_VENDOR_ID_QUATECH 0x061d
#define QUATECH_SSU2_100 0xC120 /* RS232 single port */
#define QUATECH_DSU2_100 0xC140 /* RS232 dual port */
#define QUATECH_DSU2_400 0xC150 /* RS232/422/485 dual port */
#define QUATECH_QSU2_100 0xC160 /* RS232 four port */
#define QUATECH_QSU2_400 0xC170 /* RS232/422/485 four port */
#define QUATECH_ESU2_100 0xC1A0 /* RS232 eight port */
#define QUATECH_ESU2_400 0xC180 /* RS232/422/485 eight port */
struct qt2_device_detail {
int product_id;
int num_ports;
};
#define QT_DETAILS(prod, ports) \
.product_id = (prod), \
.num_ports = (ports)
static const struct qt2_device_detail qt2_device_details[] = {
{QT_DETAILS(QUATECH_SSU2_100, 1)},
{QT_DETAILS(QUATECH_DSU2_400, 2)},
{QT_DETAILS(QUATECH_DSU2_100, 2)},
{QT_DETAILS(QUATECH_QSU2_400, 4)},
{QT_DETAILS(QUATECH_QSU2_100, 4)},
{QT_DETAILS(QUATECH_ESU2_400, 8)},
{QT_DETAILS(QUATECH_ESU2_100, 8)},
{QT_DETAILS(0, 0)} /* Terminating entry */
};
static const struct usb_device_id id_table[] = {
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_SSU2_100)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_100)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_DSU2_400)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_100)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_QSU2_400)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_100)},
{USB_DEVICE(USB_VENDOR_ID_QUATECH, QUATECH_ESU2_400)},
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, id_table);
struct qt2_serial_private {
unsigned char current_port; /* current port for incoming data */
struct urb *read_urb; /* shared among all ports */
char *read_buffer;
};
struct qt2_port_private {
u8 device_port;
spinlock_t urb_lock;
bool urb_in_use;
struct urb *write_urb;
char *write_buffer;
spinlock_t lock;
u8 shadowLSR;
u8 shadowMSR;
struct usb_serial_port *port;
};
static void qt2_update_lsr(struct usb_serial_port *port, unsigned char *ch);
static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch);
static void qt2_write_bulk_callback(struct urb *urb);
static void qt2_read_bulk_callback(struct urb *urb);
static void qt2_release(struct usb_serial *serial)
{
struct qt2_serial_private *serial_priv;
serial_priv = usb_get_serial_data(serial);
usb_kill_urb(serial_priv->read_urb);
usb_free_urb(serial_priv->read_urb);
kfree(serial_priv->read_buffer);
kfree(serial_priv);
}
static inline int calc_baud_divisor(int baudrate)
{
int divisor, rem;
divisor = MAX_BAUD_RATE / baudrate;
rem = MAX_BAUD_RATE % baudrate;
/* Round to nearest divisor */
if (((rem * 2) >= baudrate) && (baudrate != 110))
divisor++;
return divisor;
}
static inline int qt2_set_port_config(struct usb_device *dev,
unsigned char port_number,
u16 baudrate, u16 lcr)
{
int divisor = calc_baud_divisor(baudrate);
u16 index = ((u16) (lcr << 8) | (u16) (port_number));
return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
QT2_GET_SET