usb: serial: Add support for circuit-switched video telephony driver

circuit switched video telephony driver provides bridge functionality
between mdm9x15 and application in userspace to send and receive control
information and data. It uses generic serial driver which exposes tty
interface to userspace.

Change-Id: I567d3ea971f3fd2f36a071d2639ee2b43b36e233
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
diff --git a/drivers/usb/serial/csvt.c b/drivers/usb/serial/csvt.c
new file mode 100644
index 0000000..5bfb2dc
--- /dev/null
+++ b/drivers/usb/serial/csvt.c
@@ -0,0 +1,446 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/tty_flip.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/serial.h>
+#include <asm/unaligned.h>
+
+
+/* output control lines*/
+#define CSVT_CTRL_DTR		0x01
+#define CSVT_CTRL_RTS		0x02
+
+/* input control lines*/
+#define CSVT_CTRL_CTS		0x01
+#define CSVT_CTRL_DSR		0x02
+#define CSVT_CTRL_RI		0x04
+#define CSVT_CTRL_CD		0x08
+
+static int debug;
+module_param(debug, int, S_IRUGO | S_IWUSR);
+
+struct csvt_ctrl_dev {
+	struct mutex		dev_lock;
+
+	/* input control lines (DSR, CTS, CD, RI) */
+	unsigned int		cbits_tolocal;
+
+	/* output control lines (DTR, RTS) */
+	unsigned int		cbits_tomdm;
+};
+
+static const struct usb_device_id id_table[] = {
+	{ USB_DEVICE_AND_INTERFACE_INFO(0x05c6 , 0x904c, 0xff, 0x00, 0xff)},
+	{}, /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver csvt_driver = {
+	.name			= "qc_csvt",
+	.probe			= usb_serial_probe,
+	.disconnect		= usb_serial_disconnect,
+	.id_table		= id_table,
+	.suspend		= usb_serial_suspend,
+	.resume			= usb_serial_resume,
+	.supports_autosuspend	= true,
+};
+
+#define CSVT_IFC_NUM	4
+
+static int csvt_probe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+	struct usb_host_interface	*intf =
+		serial->interface->cur_altsetting;
+
+	pr_debug("%s:\n", __func__);
+
+	if (intf->desc.bInterfaceNumber != CSVT_IFC_NUM)
+		return -ENODEV;
+
+	usb_enable_autosuspend(serial->dev);
+
+	return 0;
+}
+
+static int csvt_ctrl_write_cmd(struct csvt_ctrl_dev	*dev,
+	struct usb_serial_port *port)
+{
+	struct usb_device	*udev = port->serial->dev;
+	struct usb_interface	*iface = port->serial->interface;
+	unsigned int		iface_num;
+	int			retval = 0;
+
+	retval = usb_autopm_get_interface(iface);
+	if (retval < 0) {
+		dev_err(&port->dev, "%s: Unable to resume interface: %d\n",
+			__func__, retval);
+		return retval;
+	}
+
+	dev_dbg(&port->dev, "%s: cbits to mdm 0x%x\n", __func__,
+		dev->cbits_tomdm);
+
+	iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
+
+	retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+		USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+		(USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
+		dev->cbits_tomdm,
+		iface_num,
+		NULL, 0, USB_CTRL_SET_TIMEOUT);
+	usb_autopm_put_interface(iface);
+
+	return retval;
+}
+
+static void csvt_ctrl_dtr_rts(struct usb_serial_port *port, int on)
+{
+	struct csvt_ctrl_dev	*dev = usb_get_serial_port_data(port);
+
+	if (!dev)
+		return;
+
+	dev_dbg(&port->dev, "%s", __func__);
+
+	mutex_lock(&dev->dev_lock);
+	if (on) {
+		dev->cbits_tomdm |= CSVT_CTRL_DTR;
+		dev->cbits_tomdm |= CSVT_CTRL_RTS;
+	} else {
+		dev->cbits_tomdm &= ~CSVT_CTRL_DTR;
+		dev->cbits_tomdm &= ~CSVT_CTRL_RTS;
+	}
+	mutex_unlock(&dev->dev_lock);
+
+	csvt_ctrl_write_cmd(dev, port);
+}
+
+static int get_serial_info(struct usb_serial_port *port,
+			   struct serial_struct __user *retinfo)
+{
+	struct serial_struct	tmp;
+
+	if (!retinfo)
+		return -EFAULT;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.line            = port->serial->minor;
+	tmp.port            = port->number;
+	tmp.baud_base       = tty_get_baud_rate(port->port.tty);
+	tmp.close_delay	    = port->port.close_delay / 10;
+	tmp.closing_wait    =
+		port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+				 ASYNC_CLOSING_WAIT_NONE :
+				 port->port.closing_wait / 10;
+
+	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+		return -EFAULT;
+	return 0;
+}
+
+static int set_serial_info(struct usb_serial_port *port,
+			   struct serial_struct __user *newinfo)
+{
+	struct serial_struct	new_serial;
+	unsigned int		closing_wait;
+	unsigned int		close_delay;
+	int			retval = 0;
+
+	if (copy_from_user(&new_serial, newinfo, sizeof(new_serial)))
+		return -EFAULT;
+
+	close_delay = new_serial.close_delay * 10;
+	closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+			ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10;
+
+	mutex_lock(&port->port.mutex);
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((close_delay != port->port.close_delay) ||
+		    (closing_wait != port->port.closing_wait))
+			retval = -EPERM;
+		else
+			retval = -EOPNOTSUPP;
+	} else {
+		port->port.close_delay  = close_delay;
+		port->port.closing_wait = closing_wait;
+	}
+
+	mutex_unlock(&port->port.mutex);
+	return retval;
+}
+
+static int csvt_ctrl_ioctl(struct tty_struct *tty, unsigned int cmd,
+	unsigned long arg)
+{
+	struct usb_serial_port	*port = tty->driver_data;
+
+	dev_dbg(&port->dev, "%s cmd 0x%04x", __func__, cmd);
+
+	switch (cmd) {
+	case TIOCGSERIAL:
+		return get_serial_info(port,
+				       (struct serial_struct __user *) arg);
+	case TIOCSSERIAL:
+		return set_serial_info(port,
+				       (struct serial_struct __user *) arg);
+	default:
+		break;
+	}
+
+	dev_err(&port->dev, "%s arg not supported", __func__);
+
+	return -ENOIOCTLCMD;
+}
+
+static int csvt_ctrl_tiocmget(struct tty_struct *tty)
+{
+	struct usb_serial_port	*port = tty->driver_data;
+	struct csvt_ctrl_dev	*dev = usb_get_serial_port_data(port);
+	unsigned int		control_state = 0;
+
+	if (!dev)
+		return -ENODEV;
+
+	mutex_lock(&dev->dev_lock);
+	control_state = (dev->cbits_tomdm & CSVT_CTRL_DTR ? TIOCM_DTR : 0) |
+		(dev->cbits_tomdm & CSVT_CTRL_RTS ? TIOCM_RTS : 0) |
+		(dev->cbits_tolocal & CSVT_CTRL_DSR ? TIOCM_DSR : 0) |
+		(dev->cbits_tolocal & CSVT_CTRL_RI ? TIOCM_RI : 0) |
+		(dev->cbits_tolocal & CSVT_CTRL_CD ? TIOCM_CD : 0) |
+		(dev->cbits_tolocal & CSVT_CTRL_CTS ? TIOCM_CTS : 0);
+	mutex_unlock(&dev->dev_lock);
+
+	dev_dbg(&port->dev, "%s -- %x", __func__, control_state);
+
+	return control_state;
+}
+
+static int csvt_ctrl_tiocmset(struct tty_struct *tty,
+			       unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port	*port = tty->driver_data;
+	struct csvt_ctrl_dev	*dev = usb_get_serial_port_data(port);
+
+	if (!dev)
+		return -ENODEV;
+
+	dev_dbg(&port->dev, "%s\n", __func__);
+
+	mutex_lock(&dev->dev_lock);
+	if (set & CSVT_CTRL_DTR)
+		dev->cbits_tomdm |= TIOCM_DTR;
+	if (set & CSVT_CTRL_RTS)
+		dev->cbits_tomdm |= TIOCM_RTS;
+
+	if (clear & CSVT_CTRL_DTR)
+		dev->cbits_tomdm &= ~TIOCM_DTR;
+	if (clear & CSVT_CTRL_RTS)
+		dev->cbits_tomdm &= ~TIOCM_RTS;
+	mutex_unlock(&dev->dev_lock);
+
+	return csvt_ctrl_write_cmd(dev, port);
+}
+
+static void csvt_ctrl_set_termios(struct tty_struct *tty,
+			  struct usb_serial_port *port,
+			  struct ktermios *old_termios)
+{
+	struct csvt_ctrl_dev	*dev = usb_get_serial_port_data(port);
+
+	if (!dev)
+		return;
+
+	dev_dbg(&port->dev, "%s", __func__);
+
+	/* Doesn't support option setting */
+	tty_termios_copy_hw(tty->termios, old_termios);
+
+	csvt_ctrl_write_cmd(dev, port);
+}
+
+static void csvt_ctrl_int_cb(struct urb *urb)
+{
+	int				status;
+	struct usb_cdc_notification	*ctrl;
+	struct usb_serial_port		*port = urb->context;
+	struct csvt_ctrl_dev		*dev;
+	unsigned int			ctrl_bits;
+	unsigned char			*data;
+
+	switch (urb->status) {
+	case 0:
+		/*success*/
+		break;
+	case -ESHUTDOWN:
+	case -ENOENT:
+	case -ECONNRESET:
+	case -EPROTO:
+		 /* unplug */
+		 return;
+	case -EPIPE:
+		dev_err(&port->dev, "%s: stall on int endpoint\n", __func__);
+		/* TBD : halt to be cleared in work */
+	case -EOVERFLOW:
+	default:
+		pr_debug_ratelimited("%s: non zero urb status = %d\n",
+					__func__, urb->status);
+		goto resubmit_int_urb;
+	}
+
+	dev = usb_get_serial_port_data(port);
+	if (!dev)
+		return;
+
+	ctrl = urb->transfer_buffer;
+	data = (unsigned char *)(ctrl + 1);
+
+	usb_serial_debug_data(debug, &port->dev, __func__,
+					urb->actual_length, data);
+
+	switch (ctrl->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		dev_dbg(&port->dev, "%s network\n", ctrl->wValue ?
+					"connected to" : "disconnected from");
+		break;
+	case USB_CDC_NOTIFY_SERIAL_STATE:
+		ctrl_bits = get_unaligned_le16(data);
+		dev_dbg(&port->dev, "serial state: %d\n", ctrl_bits);
+		dev->cbits_tolocal = ctrl_bits;
+		break;
+	default:
+		dev_err(&port->dev, "%s: unknown notification %d received:"
+			"index %d len %d data0 %d data1 %d",
+			__func__, ctrl->bNotificationType, ctrl->wIndex,
+			ctrl->wLength, data[0], data[1]);
+	}
+
+resubmit_int_urb:
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status)
+		dev_err(&port->dev, "%s: Error re-submitting Int URB %d\n",
+		__func__, status);
+
+}
+
+static int csvt_ctrl_open(struct tty_struct *tty,
+					struct usb_serial_port *port)
+{
+	int	retval;
+
+	dev_dbg(&port->dev, "%s port %d", __func__, port->number);
+
+	retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+	if (retval) {
+		dev_err(&port->dev, "usb_submit_urb(read int) failed\n");
+		return retval;
+	}
+
+	retval = usb_serial_generic_open(tty, port);
+	if (retval)
+		usb_kill_urb(port->interrupt_in_urb);
+
+	return retval;
+}
+
+static void csvt_ctrl_close(struct usb_serial_port *port)
+{
+	dev_dbg(&port->dev, "%s port %d", __func__, port->number);
+
+	usb_serial_generic_close(port);
+	usb_kill_urb(port->interrupt_in_urb);
+}
+
+static int csvt_ctrl_attach(struct usb_serial *serial)
+{
+	struct csvt_ctrl_dev	*dev;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	mutex_init(&dev->dev_lock);
+	usb_set_serial_port_data(serial->port[0], dev);
+
+	return 0;
+}
+
+static void csvt_ctrl_release(struct usb_serial *serial)
+{
+	struct usb_serial_port	*port = serial->port[0];
+	struct csvt_ctrl_dev	*dev = usb_get_serial_port_data(port);
+
+	dev_dbg(&port->dev, "%s", __func__);
+
+	kfree(dev);
+	usb_set_serial_port_data(port, NULL);
+}
+
+static struct usb_serial_driver csvt_device = {
+	.driver			= {
+		.owner	= THIS_MODULE,
+		.name	= "qc_csvt",
+	},
+	.description		= "qc_csvt",
+	.id_table		= id_table,
+	.usb_driver		= &csvt_driver,
+	.num_ports		= 1,
+	.open			= csvt_ctrl_open,
+	.close			= csvt_ctrl_close,
+	.probe			= csvt_probe,
+	.dtr_rts		= csvt_ctrl_dtr_rts,
+	.tiocmget		= csvt_ctrl_tiocmget,
+	.tiocmset		= csvt_ctrl_tiocmset,
+	.ioctl			= csvt_ctrl_ioctl,
+	.set_termios		= csvt_ctrl_set_termios,
+	.read_int_callback	= csvt_ctrl_int_cb,
+	.attach			= csvt_ctrl_attach,
+	.release		= csvt_ctrl_release,
+};
+
+static int __init csvt_init(void)
+{
+	int	retval;
+
+	retval = usb_serial_register(&csvt_device);
+	if (retval) {
+		err("%s: usb serial register failed\n", __func__);
+		return retval;
+	}
+
+	retval = usb_register(&csvt_driver);
+	if (retval) {
+		usb_serial_deregister(&csvt_device);
+		err("%s: usb register failed\n", __func__);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __exit csvt_exit(void)
+{
+	usb_deregister(&csvt_driver);
+	usb_serial_deregister(&csvt_device);
+}
+
+module_init(csvt_init);
+module_exit(csvt_exit);
+
+MODULE_LICENSE("GPL v2");