diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-09-18 07:53:32 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-09-18 07:57:09 +0200 |
commit | c05fecb1d57e7f4dd3244c7bfaf4374b02728238 (patch) | |
tree | 76398f7c2197d8ea8bef4f6f1c6f4df88348f690 | |
parent | usb: remove junk from store_remove_id (diff) | |
download | linux-c05fecb1d57e7f4dd3244c7bfaf4374b02728238.tar.xz linux-c05fecb1d57e7f4dd3244c7bfaf4374b02728238.zip |
USB: serial: add vizzini driver
Here's a driver for the Vizzini USB to serial device.
It looks to be copied from cdc-acm, and probably can be cleaned up a lot
more. Also, there's some odd "try to grab another interface" that is
probably wrong. And, if this really is a cdc-acm device, it probably
should just be a quirk of the cdc-acm device, but I can't figure that
out, and people have been using this driver for a long time now. So
merge it to let people use their hardware and clean it up over time.
Driver written by Rob Duncan but cleaned up and forward ported to the
latest kernel tree by me.
Cc: Rob Duncan <rob.duncan@exar.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/serial/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/serial/vizzini.c | 1363 |
3 files changed, 1372 insertions, 0 deletions
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index f604f707a058..a5c144694005 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -182,6 +182,14 @@ config USB_SERIAL_VISOR To compile this driver as a module, choose M here: the module will be called visor. +config USB_SERIAL_VIZZINI + tristate "USB Vizzini Serial Converter Driver" + help + Say Y here if you have a Vizzini USB to serial device. + + To compile this driver as a module, choose M here: the + module will be called vizzini. + config USB_SERIAL_IPAQ tristate "USB PocketPC PDA Driver" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 45871f9ad1e1..5fd21a01b009 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_USB_SERIAL_SYMBOL) += symbolserial.o obj-$(CONFIG_USB_SERIAL_WWAN) += usb_wwan.o obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o +obj-$(CONFIG_USB_SERIAL_VIZZINI) += vizzini.o obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o obj-$(CONFIG_USB_SERIAL_VIVOPAY_SERIAL) += vivopay-serial.o diff --git a/drivers/usb/serial/vizzini.c b/drivers/usb/serial/vizzini.c new file mode 100644 index 000000000000..2ac48fe3f4ca --- /dev/null +++ b/drivers/usb/serial/vizzini.c @@ -0,0 +1,1363 @@ +/* + * vizzini.c + * + * Copyright (c) 2011 Exar Corporation, Inc. + * + * ChangeLog: + * v0.76- Support for 3.0.0 (Ubuntu 11.10) (Removed all Kernel source + * compiler conditions and now the base is Kernel 3.0. Ravi Reddy) + * v0.75- Support for 2.6.38.8 (Ubuntu 11.04) - Added + * .usb_driver = &vizzini_driver. + * v0.74- Support for 2.6.35.22 (Ubuntu 10.10) - Added + * #include <linux/slab.h> to fix kmalloc/kfree error. + * v0.73- Fixed VZIOC_SET_REG (by Ravi Reddy). + * v0.72- Support for 2.6.32.21 (by Ravi Reddy, for Ubuntu 10.04). + * v0.71- Support for 2.6.31. + * v0.5 - Tentative support for compiling with the CentOS 5.1 + * kernel (2.6.18-53). + * v0.4 - First version. Lots of stuff lifted from + * cdc-acm.c (credits due to Armin Fuerst, Pavel Machek, + * Johannes Erdfelt, Vojtech Pavlik, David Kubicek) and + * and sierra.c (credit due to Kevin Lloyd). + */ + +/* + * 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, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This version of the Linux driver source contains a number of + abominable conditional compilation sections to manage the API + changes between kernel versions 2.6.18, 2.6.25, and the latest + (currently 2.6.27). At some point we'll hand a version of this + driver off to the mainline Linux source tree, and we'll strip all + these sections out. For now it makes it much easier to keep it all + in sync while the driver is being developed. */ + + +#define DRIVER_VERSION "v.0.76" +#define DRIVER_AUTHOR "Rob Duncan <rob.duncan@exar.com>" +#define DRIVER_DESC "USB Driver for Vizzini USB serial port" + +#undef VIZZINI_IWA + + +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/errno.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include <linux/serial.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <asm/unaligned.h> + +#include <linux/usb/cdc.h> +#ifndef CDC_DATA_INTERFACE_TYPE +#define CDC_DATA_INTERFACE_TYPE 0x0a +#endif +#ifndef USB_RT_ACM +#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) +#define ACM_CTRL_DTR 0x01 +#define ACM_CTRL_RTS 0x02 +#define ACM_CTRL_DCD 0x01 +#define ACM_CTRL_DSR 0x02 +#define ACM_CTRL_BRK 0x04 +#define ACM_CTRL_RI 0x08 +#define ACM_CTRL_FRAMING 0x10 +#define ACM_CTRL_PARITY 0x20 +#define ACM_CTRL_OVERRUN 0x40 +#endif + +#define XR_SET_REG 0 +#define XR_GETN_REG 1 + +#define UART_0_REG_BLOCK 0 +#define UART_1_REG_BLOCK 1 +#define UART_2_REG_BLOCK 2 +#define UART_3_REG_BLOCK 3 +#define URM_REG_BLOCK 4 +#define PRM_REG_BLOCK 5 +#define EPMERR_REG_BLOCK 6 +#define RAMCTL_REG_BLOCK 0x64 +#define TWI_ROM_REG_BLOCK 0x65 +#define EPLOCALS_REG_BLOCK 0x66 + +#define MEM_SHADOW_REG_SIZE_S 5 +#define MEM_SHADOW_REG_SIZE (1 << MEM_SHADOW_REG_SIZE_S) + +#define MEM_EP_LOCALS_SIZE_S 3 +#define MEM_EP_LOCALS_SIZE (1 << MEM_EP_LOCALS_SIZE_S) + +#define EP_WIDE_MODE 0x03 + + +#define UART_GPIO_MODE 0x01a + +#define UART_GPIO_MODE_SEL_M 0x7 +#define UART_GPIO_MODE_SEL_S 0 +#define UART_GPIO_MODE_SEL 0x007 + +#define UART_GPIO_MODE_SEL_GPIO (0x0 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_RTS_CTS (0x1 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_DTR_DSR (0x2 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_XCVR_EN_ACT (0x3 << UART_GPIO_MODE_SEL_S) +#define UART_GPIO_MODE_SEL_XCVR_EN_FLOW (0x4 << UART_GPIO_MODE_SEL_S) + +#define UART_GPIO_MODE_XCVR_EN_POL_M 0x1 +#define UART_GPIO_MODE_XCVR_EN_POL_S 3 +#define UART_GPIO_MODE_XCVR_EN_POL 0x008 + +#define UART_ENABLE 0x003 +#define UART_ENABLE_TX_M 0x1 +#define UART_ENABLE_TX_S 0 +#define UART_ENABLE_TX 0x001 +#define UART_ENABLE_RX_M 0x1 +#define UART_ENABLE_RX_S 1 +#define UART_ENABLE_RX 0x002 + +#define UART_CLOCK_DIVISOR_0 0x004 +#define UART_CLOCK_DIVISOR_1 0x005 +#define UART_CLOCK_DIVISOR_2 0x006 + +#define UART_CLOCK_DIVISOR_2_MSB_M 0x7 +#define UART_CLOCK_DIVISOR_2_MSB_S 0 +#define UART_CLOCK_DIVISOR_2_MSB 0x007 +#define UART_CLOCK_DIVISOR_2_DIAGMODE_M 0x1 +#define UART_CLOCK_DIVISOR_2_DIAGMODE_S 3 +#define UART_CLOCK_DIVISOR_2_DIAGMODE 0x008 + +#define UART_TX_CLOCK_MASK_0 0x007 +#define UART_TX_CLOCK_MASK_1 0x008 + +#define UART_RX_CLOCK_MASK_0 0x009 +#define UART_RX_CLOCK_MASK_1 0x00a + +#define UART_FORMAT 0x00b + +#define UART_FORMAT_SIZE_M 0xf +#define UART_FORMAT_SIZE_S 0 +#define UART_FORMAT_SIZE 0x00f + +#define UART_FORMAT_SIZE_7 (0x7 << UART_FORMAT_SIZE_S) +#define UART_FORMAT_SIZE_8 (0x8 << UART_FORMAT_SIZE_S) +#define UART_FORMAT_SIZE_9 (0x9 << UART_FORMAT_SIZE_S) + +#define UART_FORMAT_PARITY_M 0x7 +#define UART_FORMAT_PARITY_S 4 +#define UART_FORMAT_PARITY 0x070 + +#define UART_FORMAT_PARITY_NONE (0x0 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_ODD (0x1 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_EVEN (0x2 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_1 (0x3 << UART_FORMAT_PARITY_S) +#define UART_FORMAT_PARITY_0 (0x4 << UART_FORMAT_PARITY_S) + +#define UART_FORMAT_STOP_M 0x1 +#define UART_FORMAT_STOP_S 7 +#define UART_FORMAT_STOP 0x080 + +#define UART_FORMAT_STOP_1 (0x0 << UART_FORMAT_STOP_S) +#define UART_FORMAT_STOP_2 (0x1 << UART_FORMAT_STOP_S) + +#define UART_FORMAT_MODE_7N1 0 +#define UART_FORMAT_MODE_RES1 1 +#define UART_FORMAT_MODE_RES2 2 +#define UART_FORMAT_MODE_RES3 3 +#define UART_FORMAT_MODE_7N2 4 +#define UART_FORMAT_MODE_7P1 5 +#define UART_FORMAT_MODE_8N1 6 +#define UART_FORMAT_MODE_RES7 7 +#define UART_FORMAT_MODE_7P2 8 +#define UART_FORMAT_MODE_8N2 9 +#define UART_FORMAT_MODE_8P1 10 +#define UART_FORMAT_MODE_9N1 11 +#define UART_FORMAT_MODE_8P2 12 +#define UART_FORMAT_MODE_RESD 13 +#define UART_FORMAT_MODE_RESE 14 +#define UART_FORMAT_MODE_9N2 15 + +#define UART_FLOW 0x00c + +#define UART_FLOW_MODE_M 0x7 +#define UART_FLOW_MODE_S 0 +#define UART_FLOW_MODE 0x007 + +#define UART_FLOW_MODE_NONE (0x0 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_HW (0x1 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_SW (0x2 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_ADDR_MATCH (0x3 << UART_FLOW_MODE_S) +#define UART_FLOW_MODE_ADDR_MATCH_TX (0x4 << UART_FLOW_MODE_S) + +#define UART_FLOW_HALF_DUPLEX_M 0x1 +#define UART_FLOW_HALF_DUPLEX_S 3 +#define UART_FLOW_HALF_DUPLEX 0x008 + +#define UART_LOOPBACK_CTL 0x012 +#define UART_LOOPBACK_CTL_ENABLE_M 0x1 +#define UART_LOOPBACK_CTL_ENABLE_S 2 +#define UART_LOOPBACK_CTL_ENABLE 0x004 +#define UART_LOOPBACK_CTL_RX_SOURCE_M 0x3 +#define UART_LOOPBACK_CTL_RX_SOURCE_S 0 +#define UART_LOOPBACK_CTL_RX_SOURCE 0x003 +#define UART_LOOPBACK_CTL_RX_UART0 (0x0 << UART_LOOPBACK_CTL_RX_SOURCE_S) +#define UART_LOOPBACK_CTL_RX_UART1 (0x1 << UART_LOOPBACK_CTL_RX_SOURCE_S) +#define UART_LOOPBACK_CTL_RX_UART2 (0x2 << UART_LOOPBACK_CTL_RX_SOURCE_S) +#define UART_LOOPBACK_CTL_RX_UART3 (0x3 << UART_LOOPBACK_CTL_RX_SOURCE_S) + +#define UART_CHANNEL_NUM 0x00d + +#define UART_XON_CHAR 0x010 +#define UART_XOFF_CHAR 0x011 + +#define UART_GPIO_SET 0x01d +#define UART_GPIO_CLR 0x01e +#define UART_GPIO_STATUS 0x01f + +#define URM_ENABLE_BASE 0x010 +#define URM_ENABLE_0 0x010 +#define URM_ENABLE_0_TX_M 0x1 +#define URM_ENABLE_0_TX_S 0 +#define URM_ENABLE_0_TX 0x001 +#define URM_ENABLE_0_RX_M 0x1 +#define URM_ENABLE_0_RX_S 1 +#define URM_ENABLE_0_RX 0x002 + +#define URM_RX_FIFO_RESET_0 0x018 +#define URM_RX_FIFO_RESET_1 0x019 +#define URM_RX_FIFO_RESET_2 0x01a +#define URM_RX_FIFO_RESET_3 0x01b +#define URM_TX_FIFO_RESET_0 0x01c +#define URM_TX_FIFO_RESET_1 0x01d +#define URM_TX_FIFO_RESET_2 0x01e +#define URM_TX_FIFO_RESET_3 0x01f + + +#define RAMCTL_REGS_TXFIFO_0_LEVEL 0x000 +#define RAMCTL_REGS_TXFIFO_1_LEVEL 0x001 +#define RAMCTL_REGS_TXFIFO_2_LEVEL 0x002 +#define RAMCTL_REGS_TXFIFO_3_LEVEL 0x003 +#define RAMCTL_REGS_RXFIFO_0_LEVEL 0x004 + +#define RAMCTL_REGS_RXFIFO_0_LEVEL_LEVEL_M 0x7ff +#define RAMCTL_REGS_RXFIFO_0_LEVEL_LEVEL_S 0 +#define RAMCTL_REGS_RXFIFO_0_LEVEL_LEVEL 0x7ff +#define RAMCTL_REGS_RXFIFO_0_LEVEL_STALE_M 0x1 +#define RAMCTL_REGS_RXFIFO_0_LEVEL_STALE_S 11 +#define RAMCTL_REGS_RXFIFO_0_LEVEL_STALE 0x800 + +#define RAMCTL_REGS_RXFIFO_1_LEVEL 0x005 +#define RAMCTL_REGS_RXFIFO_2_LEVEL 0x006 +#define RAMCTL_REGS_RXFIFO_3_LEVEL 0x007 + +#define RAMCTL_BUFFER_PARITY 0x1 +#define RAMCTL_BUFFER_BREAK 0x2 +#define RAMCTL_BUFFER_FRAME 0x4 +#define RAMCTL_BUFFER_OVERRUN 0x8 + +#define N_IN_URB 4 +#define N_OUT_URB 4 +#define IN_BUFLEN 4096 + +static struct usb_device_id id_table[] = { + { USB_DEVICE(0x04e2, 0x1410) }, + { USB_DEVICE(0x04e2, 0x1412) }, + { USB_DEVICE(0x04e2, 0x1414) }, + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct vizzini_serial_private { + struct usb_interface *data_interface; +}; + +struct vizzini_port_private { + spinlock_t lock; + int outstanding_urbs; + + struct urb *in_urbs[N_IN_URB]; + char *in_buffer[N_IN_URB]; + + int ctrlin; + int ctrlout; + int clocal; + + int block; + int preciseflags; /* USB: wide mode, TTY: flags per character */ + int trans9; /* USB: wide mode, serial 9N1 */ + unsigned int baud_base; /* setserial: used to hack in non-standard baud rates */ + int have_extra_byte; + int extra_byte; + + int bcd_device; + +#ifdef VIZZINI_IWA + int iwa; +#endif +}; + + +static int vizzini_rev_a(struct usb_serial_port *port) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + return portdata->bcd_device == 0; +} + +static int acm_ctrl_msg(struct usb_serial_port *port, int request, + int value, void *buf, int len) +{ + struct usb_serial *serial = port->serial; + int retval = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, + USB_RT_ACM, + value, + serial->interface->cur_altsetting->desc.bInterfaceNumber, + buf, + len, + 5000); + dev_dbg(&port->dev, "acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d\n", request, value, len, retval); + return retval < 0 ? retval : 0; +} + +#define acm_set_control(port, control) \ + acm_ctrl_msg(port, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0) +#define acm_set_line(port, line) \ + acm_ctrl_msg(port, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line)) +#define acm_send_break(port, ms) \ + acm_ctrl_msg(port, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) + +static int vizzini_set_reg(struct usb_serial_port *port, int block, int regnum, int value) +{ + struct usb_serial *serial = port->serial; + int result; + + result = usb_control_msg(serial->dev, /* usb device */ + usb_sndctrlpipe(serial->dev, 0), /* endpoint pipe */ + XR_SET_REG, /* request */ + USB_DIR_OUT | USB_TYPE_VENDOR, /* request_type */ + value, /* request value */ + regnum | (block << 8), /* index */ + NULL, /* data */ + 0, /* size */ + 5000); /* timeout */ + + return result; +} + +static void vizzini_disable(struct usb_serial_port *port) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + int block = portdata->block; + + vizzini_set_reg(port, block, UART_ENABLE, 0); + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, 0); +} + +static void vizzini_enable(struct usb_serial_port *port) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + int block = portdata->block; + + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, URM_ENABLE_0_TX); + vizzini_set_reg(port, block, UART_ENABLE, UART_ENABLE_TX | UART_ENABLE_RX); + vizzini_set_reg(port, URM_REG_BLOCK, URM_ENABLE_BASE + block, URM_ENABLE_0_TX | URM_ENABLE_0_RX); +} + +struct vizzini_baud_rate { + unsigned int tx; + unsigned int rx0; + unsigned int rx1; +}; + +static struct vizzini_baud_rate vizzini_baud_rates[] = { + { 0x000, 0x000, 0x000 }, + { 0x000, 0x000, 0x000 }, + { 0x100, 0x000, 0x100 }, + { 0x020, 0x400, 0x020 }, + { 0x010, 0x100, 0x010 }, + { 0x208, 0x040, 0x208 }, + { 0x104, 0x820, 0x108 }, + { 0x844, 0x210, 0x884 }, + { 0x444, 0x110, 0x444 }, + { 0x122, 0x888, 0x224 }, + { 0x912, 0x448, 0x924 }, + { 0x492, 0x248, 0x492 }, + { 0x252, 0x928, 0x292 }, + { 0X94A, 0X4A4, 0XA52 }, + { 0X52A, 0XAA4, 0X54A }, + { 0XAAA, 0x954, 0X4AA }, + { 0XAAA, 0x554, 0XAAA }, + { 0x555, 0XAD4, 0X5AA }, + { 0XB55, 0XAB4, 0X55A }, + { 0X6B5, 0X5AC, 0XB56 }, + { 0X5B5, 0XD6C, 0X6D6 }, + { 0XB6D, 0XB6A, 0XDB6 }, + { 0X76D, 0X6DA, 0XBB6 }, + { 0XEDD, 0XDDA, 0X76E }, + { 0XDDD, 0XBBA, 0XEEE }, + { 0X7BB, 0XF7A, 0XDDE }, + { 0XF7B, 0XEF6, 0X7DE }, + { 0XDF7, 0XBF6, 0XF7E }, + { 0X7F7, 0XFEE, 0XEFE }, + { 0XFDF, 0XFBE, 0X7FE }, + { 0XF7F, 0XEFE, 0XFFE }, + { 0XFFF, 0XFFE, 0XFFD }, +}; + +static int vizzini_set_baud_rate(struct usb_serial_port *port, unsigned int rate) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + int block = portdata->block; + unsigned int divisor = 48000000 / rate; + unsigned int i = ((32 * 48000000) / rate) & 0x1f; + unsigned int tx_mask = vizzini_baud_rates[i].tx; + unsigned int rx_mask = (divisor & 1) ? vizzini_baud_rates[i].rx1 : vizzini_baud_rates[i].rx0; + + dev_dbg(&port->dev, "Setting baud rate to %d: i=%u div=%u tx=%03x rx=%03x\n", rate, i, divisor, tx_mask, rx_mask); + + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_0, (divisor >> 0) & 0xff); + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_1, (divisor >> 8) & 0xff); + vizzini_set_reg(port, block, UART_CLOCK_DIVISOR_2, (divisor >> 16) & 0xff); + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_0, (tx_mask >> 0) & 0xff); + vizzini_set_reg(port, block, UART_TX_CLOCK_MASK_1, (tx_mask >> 8) & 0xff); + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_0, (rx_mask >> 0) & 0xff); + vizzini_set_reg(port, block, UART_RX_CLOCK_MASK_1, (rx_mask >> 8) & 0xff); + + return -EINVAL; +} + +static void vizzini_set_termios(struct tty_struct *tty_param, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + unsigned int cflag, block; + speed_t rate; + unsigned int format_size, format_parity, format_stop, flow, gpio_mode; + struct tty_struct *tty = port->port.tty; + + cflag = tty->termios->c_cflag; + + portdata->clocal = ((cflag & CLOCAL) != 0); + + block = portdata->block; + + vizzini_disable(port); + + if ((cflag & CSIZE) == CS7) { + format_size = UART_FORMAT_SIZE_7; + } else if ((cflag & CSIZE) == CS5) { + /* Enabling 5-bit mode is really 9-bit mode! */ + format_size = UART_FORMAT_SIZE_9; + } else { + format_size = UART_FORMAT_SIZE_8; + } + portdata->trans9 = (format_size == UART_FORMAT_SIZE_9); + + if (cflag & PARENB) { + if (cflag & PARODD) { + if (cflag & CMSPAR) + format_parity = UART_FORMAT_PARITY_1; + else + format_parity = UART_FORMAT_PARITY_ODD; + } else { + if (cflag & CMSPAR) + format_parity = UART_FORMAT_PARITY_0; + else + format_parity = UART_FORMAT_PARITY_EVEN; + } + } else { + format_parity = UART_FORMAT_PARITY_NONE; + } + + if (cflag & CSTOPB) + format_stop = UART_FORMAT_STOP_2; + else + format_stop = UART_FORMAT_STOP_1; + +#ifdef VIZZINI_IWA + if (format_size == UART_FORMAT_SIZE_8) { + portdata->iwa = format_parity; + if (portdata->iwa != UART_FORMAT_PARITY_NONE) { + format_size = UART_FORMAT_SIZE_9; + format_parity = UART_FORMAT_PARITY_NONE; + } + } else { + portdata->iwa = UART_FORMAT_PARITY_NONE; + } +#endif + vizzini_set_reg(port, block, UART_FORMAT, format_size | format_parity | format_stop); + + if (cflag & CRTSCTS) { + flow = UART_FLOW_MODE_HW; + gpio_mode = UART_GPIO_MODE_SEL_RTS_CTS; + } else if (I_IXOFF(tty) || I_IXON(tty)) { + unsigned char start_char = START_CHAR(tty); + unsigned char stop_char = STOP_CHAR(tty); + + flow = UART_FLOW_MODE_SW; + gpio_mode = UART_GPIO_MODE_SEL_GPIO; + + vizzini_set_reg(port, block, UART_XON_CHAR, start_char); + vizzini_set_reg(port, block, UART_XOFF_CHAR, stop_char); + } else { + flow = UART_FLOW_MODE_NONE; + gpio_mode = UART_GPIO_MODE_SEL_GPIO; + } + + vizzini_set_reg(port, block, UART_FLOW, flow); + vizzini_set_reg(port, block, UART_GPIO_MODE, gpio_mode); + + if (portdata->trans9) { + /* Turn on wide mode if we're 9-bit transparent. */ + vizzini_set_reg(port, EPLOCALS_REG_BLOCK, (block * MEM_EP_LOCALS_SIZE) + EP_WIDE_MODE, 1); +#ifdef VIZZINI_IWA + } else if (portdata->iwa != UART_FORMAT_PARITY_NONE) { + vizzini_set_reg(port, EPLOCALS_REG_BLOCK, (block * MEM_EP_LOCALS_SIZE) + EP_WIDE_MODE, 1); +#endif + } else if (!portdata->preciseflags) { + /* Turn off wide mode unless we have precise flags. */ + vizzini_set_reg(port, EPLOCALS_REG_BLOCK, (block * MEM_EP_LOCALS_SIZE) + EP_WIDE_MODE, 0); + } + + rate = tty_get_baud_rate(tty); + if (rate) + vizzini_set_baud_rate(port, rate); + + vizzini_enable(port); +} + +static void vizzini_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "BREAK %d\n", break_state); + if (break_state) + acm_send_break(port, 0x10); + else + acm_send_break(port, 0x000); +} + +static int vizzini_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + + return (portdata->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) | + (portdata->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) | + (portdata->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | + (portdata->ctrlin & ACM_CTRL_RI ? TIOCM_RI : 0) | + (portdata->ctrlin & ACM_CTRL_DCD ? TIOCM_CD : 0) | + TIOCM_CTS; +} + +static int vizzini_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + unsigned int newctrl; + + newctrl = portdata->ctrlout; + set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); + + newctrl = (newctrl & ~clear) | set; + + if (portdata->ctrlout == newctrl) + return 0; + return acm_set_control(port, portdata->ctrlout = newctrl); +} + +static int vizzini_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + struct serial_struct ss; + + dev_dbg(&port->dev, "%s %08x\n", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + if (!arg) + return -EFAULT; + memset(&ss, 0, sizeof(ss)); + ss.baud_base = portdata->baud_base; + if (copy_to_user((void __user *)arg, &ss, sizeof(ss))) + return -EFAULT; + break; + + case TIOCSSERIAL: + if (!arg) + return -EFAULT; + if (copy_from_user(&ss, (void __user *)arg, sizeof(ss))) + return -EFAULT; + portdata->baud_base = ss.baud_base; + dev_dbg(&port->dev, "baud_base=%d\n", portdata->baud_base); + + vizzini_disable(port); + if (portdata->baud_base) + vizzini_set_baud_rate(port, portdata->baud_base); + vizzini_enable(port); + break; + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +#ifdef VIZZINI_IWA +static const int vizzini_parity[] = { + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 +}; +#endif + +static void vizzini_out_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + int status = urb->status; + unsigned long flags; + + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); + + if (status) + dev_dbg(&port->dev, "%s - nonzero write bulk status received: %d\n", __func__, status); + + spin_lock_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + spin_unlock_irqrestore(&portdata->lock, flags); + + usb_serial_port_softint(port); +} + +static int vizzini_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + unsigned long flags; + + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); + + /* try to give a good number back based on if we have any free urbs at + * this point in time */ + spin_lock_irqsave(&portdata->lock, flags); + if (portdata->outstanding_urbs > N_OUT_URB * 2 / 3) { + spin_unlock_irqrestore(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + spin_unlock_irqrestore(&portdata->lock, flags); + + return 2048; +} + +static int vizzini_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int bufsize = count; + unsigned long flags; + unsigned char *buffer; + struct urb *urb; + int status; + + portdata = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count); + + spin_lock_irqsave(&portdata->lock, flags); + if (portdata->outstanding_urbs > N_OUT_URB) { + spin_unlock_irqrestore(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - write limit hit\n", __func__); + return 0; + } + portdata->outstanding_urbs++; + spin_unlock_irqrestore(&portdata->lock, flags); + +#ifdef VIZZINI_IWA + if (portdata->iwa != UART_FORMAT_PARITY_NONE) + bufsize = count * 2; +#endif + buffer = kmalloc(bufsize, GFP_ATOMIC); + + if (!buffer) { + dev_err(&port->dev, "out of memory\n"); + count = -ENOMEM; + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "no more free urbs\n"); + count = -ENOMEM; + goto error_no_urb; + } + +#ifdef VIZZINI_IWA + if (portdata->iwa != UART_FORMAT_PARITY_NONE) { + int i; + char *b = buffer; + for (i = 0; i < count; ++i) { + int c, p = 0; + c = buf[i]; + switch (portdata->iwa) { + case UART_FORMAT_PARITY_ODD: + p = !vizzini_parity[c]; + break; + case UART_FORMAT_PARITY_EVEN: + p = vizzini_parity[c]; + break; + case UART_FORMAT_PARITY_1: + p = 1; + break; + case UART_FORMAT_PARITY_0: + p = 0; + break; + } + *b++ = c; + *b++ = p; + } + } else +#endif + memcpy(buffer, buf, count); + + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + buffer, bufsize, vizzini_out_callback, port); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed with status = %d\n", __func__, status); + count = status; + goto error; + } + + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb(urb); + + return count; +error: + usb_free_urb(urb); +error_no_urb: + kfree(buffer); +error_no_buffer: + spin_lock_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + spin_unlock_irqrestore(&portdata->lock, flags); + return count; +} + +static void vizzini_in_callback(struct urb *urb) +{ + int endpoint = usb_pipeendpoint(urb->pipe); + struct usb_serial_port *port = urb->context; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + struct tty_struct *tty = port->port.tty; + int preciseflags = portdata->preciseflags; + char *transfer_buffer = urb->transfer_buffer; + int length, room, have_extra_byte; + int err; + + if (urb->status) { + dev_dbg(&port->dev, "%s: nonzero status: %d on endpoint %02x.\n", __func__, urb->status, endpoint); + return; + } + +#ifdef VIZZINI_IWA + if (portdata->iwa != UART_FORMAT_PARITY_NONE) + preciseflags = true; +#endif + + length = urb->actual_length; + if (length == 0) { + dev_dbg(&port->dev, "%s: empty read urb received\n", __func__); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_err(&port->dev, "resubmit read urb failed. (%d)\n", err); + return; + } + + length = length + (portdata->have_extra_byte ? 1 : 0); + have_extra_byte = (preciseflags && (length & 1)); + length = (preciseflags) ? (length / 2) : length; + + room = tty_buffer_request_room(tty, length); + if (room != length) + dev_dbg(&port->dev, "Not enough room in TTY buf, dropped %d chars.\n", length - room); + + if (room) { + if (preciseflags) { + char *dp = transfer_buffer; + int i, ch, ch_flags; + + for (i = 0; i < room; ++i) { + char tty_flag; + + if (i == 0) { + if (portdata->have_extra_byte) + ch = portdata->extra_byte; + else + ch = *dp++; + } else { + ch = *dp++; + } + ch_flags = *dp++; + +#ifdef VIZZINI_IWA + { + int p; + switch (portdata->iwa) { + case UART_FORMAT_PARITY_ODD: + p = !vizzini_parity[ch]; + break; + case UART_FORMAT_PARITY_EVEN: + p = vizzini_parity[ch]; + break; + case UART_FORMAT_PARITY_1: + p = 1; + break; + case UART_FORMAT_PARITY_0: + p = 0; + break; + default: + p = 0; + break; + } + ch_flags ^= p; + } +#endif + if (ch_flags & RAMCTL_BUFFER_PARITY) + tty_flag = TTY_PARITY; + else if (ch_flags & RAMCTL_BUFFER_BREAK) + tty_flag = TTY_BREAK; + else if (ch_flags & RAMCTL_BUFFER_FRAME) + tty_flag = TTY_FRAME; + else if (ch_flags & RAMCTL_BUFFER_OVERRUN) + tty_flag = TTY_OVERRUN; + else + tty_flag = TTY_NORMAL; + + tty_insert_flip_char(tty, ch, tty_flag); + } + } else { + tty_insert_flip_string(tty, transfer_buffer, room); + } + + tty_flip_buffer_push(tty); + } + + portdata->have_extra_byte = have_extra_byte; + if (have_extra_byte) + portdata->extra_byte = transfer_buffer[urb->actual_length - 1]; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_err(&port->dev, "resubmit read urb failed. (%d)\n", err); +} + +static void vizzini_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct vizzini_port_private *portdata = usb_get_serial_port_data(port); + struct tty_struct *tty = port->port.tty; + + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned char *data; + int newctrl; + int status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&port->dev, "urb shutting down with status: %d\n", urb->status); + return; + default: + dev_dbg(&port->dev, "nonzero urb status received: %d\n", urb->status); + goto exit; + } + + data = (unsigned char *)(dr + 1); + switch (dr->bNotificationType) { + + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&port->dev, "%s network\n", dr->wValue ? "connected to" : "disconnected from"); + break; + + case USB_CDC_NOTIFY_SERIAL_STATE: + newctrl = le16_to_cpu(get_unaligned((__le16 *)data)); + + if (!portdata->clocal && (portdata->ctrlin & ~newctrl & ACM_CTRL_DCD)) { + dev_dbg(&port->dev, "calling hangup\n"); + tty_hangup(tty); + } + + portdata->ctrlin = newctrl; + + dev_dbg(&port->dev, "input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c\n", + portdata->ctrlin & ACM_CTRL_DCD ? '+' : '-', + portdata->ctrlin & ACM_CTRL_DSR ? '+' : '-', + portdata->ctrlin & ACM_CTRL_BRK ? '+' : '-', + portdata->ctrlin & ACM_CTRL_RI ? '+' : '-', + portdata->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', + portdata->ctrlin & ACM_CTRL_PARITY ? '+' : '-', + portdata->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-'); + break; + + default: + dev_dbg(&port->dev, "unknown notification %d received: index %d len %d data0 %d data1 %d\n", + dr->bNotificationType, dr->wIndex, + dr->wLength, data[0], data[1]); + break; + } +exit: + dev_dbg(&port->dev, "Resubmitting interrupt IN urb %p\n", urb); + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(&port->dev, "usb_submit_urb failed with result %d", status); +} + +static int vizzini_open(struct tty_struct *tty_param, struct usb_serial_port *port) +{ + struct vizzini_port_private *portdata; + struct usb_serial *serial = port->serial; + struct tty_struct *tty = port->port.tty; + int i; + struct urb *urb; + int result; + + portdata = usb_get_serial_port_data(port); + + acm_set_control(port, portdata->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS); + + /* Reset low level data toggle and start reading from endpoints */ + for (i = 0; i < N_IN_URB; i++) { + dev_dbg(&port->dev, "%s urb %d\n", __func__, i); + + urb = portdata->in_urbs[i]; + if (!urb) + continue; + if (urb->dev != serial->dev) { + dev_dbg(&port->dev, "%s: dev %p != %p\n", __func__, + urb->dev, serial->dev); + continue; + } + + /* + * make sure endpoint data toggle is synchronized with the + * device + */ + /* dev_dbg(&port->dev, "%s clearing halt on %x\n", __func__, urb->pipe); */ + /* usb_clear_halt(urb->dev, urb->pipe); */ + + dev_dbg(&port->dev, "%s submitting urb %p\n", __func__, urb); + result = usb_submit_urb(urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "submit urb %d failed (%d) %d\n", + i, result, urb->transfer_buffer_length); + } + } + + tty->low_latency = 1; + + /* start up the interrupt endpoint if we have one */ + if (port->interrupt_in_urb) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, "submit irq_in urb failed %d\n", + result); + } + return 0; +} + +static void vizzini_close(struct usb_serial_port *port) +{ + int i; + struct usb_serial *serial = port->serial; + struct vizzini_port_private *portdata; + struct tty_struct *tty = port->port.tty; + + portdata = usb_get_serial_port_data(port); + + acm_set_control(port, portdata->ctrlout = 0); + + if (serial->dev) { + /* Stop reading/writing urbs */ + for (i = 0; i < N_IN_URB; i++) + usb_kill_urb(portdata->in_urbs[i]); + } + + usb_kill_urb(port->interrupt_in_urb); + + tty = NULL; /* FIXME */ +} + +static int vizzini_attach(struct usb_serial *serial) +{ + struct vizzini_serial_private *serial_priv = usb_get_serial_data(serial); + struct usb_interface *interface = serial_priv->data_interface; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_endpoint_descriptor *bulk_in_endpoint = NULL; + struct usb_endpoint_descriptor *bulk_out_endpoint = NULL; + + struct usb_serial_port *port; + struct vizzini_port_private *portdata; + struct urb *urb; + int i, j; + + /* Assume that there's exactly one serial port. */ + port = serial->port[0]; + + /* The usb_serial is now fully set up, but we want to make a + * couple of modifications. Namely, it was configured based + * upon the control interface and not the data interface, so + * it has no notion of the bulk in and out endpoints. So we + * essentially do some of the same allocations and + * configurations that the usb-serial core would have done if + * it had not made any faulty assumptions about the + * endpoints. */ + + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) + bulk_in_endpoint = endpoint; + + if (usb_endpoint_is_bulk_out(endpoint)) + bulk_out_endpoint = endpoint; + } + + if (!bulk_out_endpoint || !bulk_in_endpoint) { + dev_dbg(&port->dev, "Missing endpoint!\n"); + return -EINVAL; + } + + port->bulk_out_endpointAddress = bulk_out_endpoint->bEndpointAddress; + port->bulk_in_endpointAddress = bulk_in_endpoint->bEndpointAddress; + + portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); + if (!portdata) { + dev_dbg(&port->dev, "%s: kmalloc for vizzini_port_private (%d) failed!.\n", + __func__, i); + return -ENOMEM; + } + spin_lock_init(&portdata->lock); + for (j = 0; j < N_IN_URB; j++) { + portdata->in_buffer[j] = kmalloc(IN_BUFLEN, GFP_KERNEL); + if (!portdata->in_buffer[j]) { + for (--j; j >= 0; j--) + kfree(portdata->in_buffer[j]); + kfree(portdata); + return -ENOMEM; + } + } + + /* Bulk OUT endpoints 0x1..0x4 map to register blocks 0..3 */ + portdata->block = port->bulk_out_endpointAddress - 1; + + usb_set_serial_port_data(port, portdata); + + portdata->bcd_device = le16_to_cpu(serial->dev->descriptor.bcdDevice); + if (vizzini_rev_a(port)) + dev_info(&port->dev, "Adapting to revA silicon\n"); + + /* initialize the in urbs */ + for (j = 0; j < N_IN_URB; ++j) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) { + dev_dbg(&port->dev, "%s: alloc for in port failed.\n", __func__); + continue; + } + /* Fill URB using supplied data. */ + dev_dbg(&port->dev, "Filling URB %p, EP=%d buf=%p len=%d\n", urb, port->bulk_in_endpointAddress, portdata->in_buffer[j], IN_BUFLEN); + usb_fill_bulk_urb(urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), + portdata->in_buffer[j], IN_BUFLEN, + vizzini_in_callback, port); + portdata->in_urbs[j] = urb; + } + + return 0; +} + +static void vizzini_serial_disconnect(struct usb_serial *serial) +{ + struct usb_serial_port *port; + struct vizzini_port_private *portdata; + int i, j; + + dev_dbg(&serial->dev->dev, "%s %p\n", __func__, serial); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!port) + continue; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + + for (j = 0; j < N_IN_URB; j++) { + usb_kill_urb(portdata->in_urbs[j]); + usb_free_urb(portdata->in_urbs[j]); + } + } +} + +static void vizzini_serial_release(struct usb_serial *serial) +{ + struct usb_serial_port *port; + struct vizzini_port_private *portdata; + int i, j; + + dev_dbg(&serial->dev->dev, "%s %p\n", __func__, serial); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!port) + continue; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + + for (j = 0; j < N_IN_URB; j++) + kfree(portdata->in_buffer[j]); + + kfree(portdata); + usb_set_serial_port_data(port, NULL); + } +} + +static int vizzini_calc_num_ports(struct usb_serial *serial) +{ + return 1; +} + +static int vizzini_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct usb_interface *intf = serial->interface; + unsigned char *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct usb_cdc_union_desc *union_header = NULL; + struct usb_cdc_country_functional_desc *cfd = NULL; + int call_interface_num = -1; + int data_interface_num; + struct usb_interface *control_interface; + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl; + struct usb_endpoint_descriptor *epread; + struct usb_endpoint_descriptor *epwrite; + struct vizzini_serial_private *serial_priv; + + if (!buffer) { + dev_err(&intf->dev, "Weird descriptor references\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev, "Seeking extra descriptors on endpoint\n"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + dev_err(&intf->dev, "Zero length descriptor references\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + dev_err(&intf->dev, "More than one union descriptor, skipping ...\n"); + goto next_desc; + } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + case USB_CDC_COUNTRY_TYPE: /* export through sysfs */ + cfd = (struct usb_cdc_country_functional_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_interface_num = buffer[4]; + break; + default: + /* there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: type %02x, length %d\n", buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + if (!union_header) { + if (call_interface_num > 0) { + dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); + control_interface = intf; + } else { + dev_dbg(&intf->dev, "No union descriptor, giving up\n"); + return -ENODEV; + } + } else { + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0)); + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev, "no interfaces\n"); + return -ENODEV; + } + } + + if (data_interface_num != call_interface_num) + dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); + + /* workaround for switched interfaces */ + if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) { + if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) { + struct usb_interface *t; + + t = control_interface; + control_interface = data_interface; + data_interface = t; + } else { + return -EINVAL; + } + } + + /* Accept probe requests only for the control interface */ + if (intf != control_interface) + return -ENODEV; + + if (usb_interface_claimed(data_interface)) { /* valid in this context */ + dev_dbg(&intf->dev, "The data interface isn't available\n"); + return -EBUSY; + } + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2) + return -EINVAL; + + epctrl = &control_interface->cur_altsetting->endpoint[0].desc; + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + if (!usb_endpoint_dir_in(epread)) { + struct usb_endpoint_descriptor *t; + t = epread; + epread = epwrite; + epwrite = t; + } + + /* The documentation suggests that we allocate private storage + * with the attach() entry point, but we can't allow the data + * interface to remain unclaimed until then; so we need + * somewhere to save the claimed interface now. */ + serial_priv = kzalloc(sizeof(struct vizzini_serial_private), + GFP_KERNEL); + if (!serial_priv) + goto alloc_fail; + usb_set_serial_data(serial, serial_priv); + + //usb_driver_claim_interface(&vizzini_driver, data_interface, NULL); + + /* Don't set the data interface private data. When we + * disconnect we test this field against NULL to discover + * whether we're dealing with the control or data + * interface. */ + serial_priv->data_interface = data_interface; + + return 0; + +alloc_fail: + return -ENOMEM; +} + +static struct usb_serial_driver vizzini_device = { + .driver = { + .owner = THIS_MODULE, + .name = "vizzini", + }, + .description = "Vizzini USB serial port", + .id_table = id_table, + .calc_num_ports = vizzini_calc_num_ports, + .probe = vizzini_probe, + .open = vizzini_open, + .close = vizzini_close, + .write = vizzini_write, + .write_room = vizzini_write_room, + .ioctl = vizzini_ioctl, + .set_termios = vizzini_set_termios, + .break_ctl = vizzini_break_ctl, + .tiocmget = vizzini_tiocmget, + .tiocmset = vizzini_tiocmset, + .attach = vizzini_attach, + .disconnect = vizzini_serial_disconnect, + .release = vizzini_serial_release, + .read_int_callback = vizzini_int_callback, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &vizzini_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); |