| /* |
| * 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 |
| */ |
| |
| #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"); |