|
@@ -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");
|