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 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 " +#define DRIVER_DESC "USB Driver for Vizzini USB serial port" + +#undef VIZZINI_IWA + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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");