usb: rmnet : Add support for QTI transport

Add support for USB-QTI(Qualcomm Tethering Interface)
interface through a user space device file mechanism. QTI
can now read and write QMI control packets from user space.

Change-Id: I199bcc8943576566b0b7a9589d248e04273e88da
Signed-off-by: Bar Weiner <bweiner@codeaurora.org>
diff --git a/arch/arm/mach-msm/include/mach/usb_gadget_xport.h b/arch/arm/mach-msm/include/mach/usb_gadget_xport.h
index 9cd4f3f..a183f0e 100644
--- a/arch/arm/mach-msm/include/mach/usb_gadget_xport.h
+++ b/arch/arm/mach-msm/include/mach/usb_gadget_xport.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2013, The Linux Foundation. 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
@@ -19,6 +19,7 @@
 	USB_GADGET_XPORT_TTY,
 	USB_GADGET_XPORT_SDIO,
 	USB_GADGET_XPORT_SMD,
+	USB_GADGET_XPORT_QTI,
 	USB_GADGET_XPORT_BAM,
 	USB_GADGET_XPORT_BAM2BAM,
 	USB_GADGET_XPORT_BAM2BAM_IPA,
@@ -38,6 +39,8 @@
 		return "SDIO";
 	case USB_GADGET_XPORT_SMD:
 		return "SMD";
+	case USB_GADGET_XPORT_QTI:
+		return "QTI";
 	case USB_GADGET_XPORT_BAM:
 		return "BAM";
 	case USB_GADGET_XPORT_BAM2BAM:
@@ -63,6 +66,8 @@
 		return USB_GADGET_XPORT_SDIO;
 	if (!strncasecmp("SMD", name, XPORT_STR_LEN))
 		return USB_GADGET_XPORT_SMD;
+	if (!strncasecmp("QTI", name, XPORT_STR_LEN))
+		return USB_GADGET_XPORT_QTI;
 	if (!strncasecmp("BAM", name, XPORT_STR_LEN))
 		return USB_GADGET_XPORT_BAM;
 	if (!strncasecmp("BAM2BAM", name, XPORT_STR_LEN))
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
index 6df0c96..76d75ea 100644
--- a/drivers/usb/gadget/android.c
+++ b/drivers/usb/gadget/android.c
@@ -60,6 +60,7 @@
 #include "u_smd.c"
 #include "u_bam.c"
 #include "u_rmnet_ctrl_smd.c"
+#include "u_rmnet_ctrl_qti.c"
 #include "u_ctrl_hsic.c"
 #include "u_data_hsic.c"
 #include "u_ctrl_hsuart.c"
diff --git a/drivers/usb/gadget/f_rmnet.c b/drivers/usb/gadget/f_rmnet.c
index 4a455b6..2722b70 100644
--- a/drivers/usb/gadget/f_rmnet.c
+++ b/drivers/usb/gadget/f_rmnet.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2011-2013, The Linux Foundation. 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
@@ -55,6 +55,7 @@
 #define NR_RMNET_PORTS	3
 static unsigned int nr_rmnet_ports;
 static unsigned int no_ctrl_smd_ports;
+static unsigned int no_ctrl_qti_ports;
 static unsigned int no_ctrl_hsic_ports;
 static unsigned int no_ctrl_hsuart_ports;
 static unsigned int no_data_bam_ports;
@@ -402,6 +403,14 @@
 			return ret;
 		}
 		break;
+	case USB_GADGET_XPORT_QTI:
+		ret = gqti_ctrl_connect(&dev->port);
+		if (ret) {
+			pr_err("%s: gqti_ctrl_connect failed: err:%d\n",
+					__func__, ret);
+			return ret;
+		}
+		break;
 	case USB_GADGET_XPORT_HSIC:
 		ret = ghsic_ctrl_connect(&dev->port, port_num);
 		if (ret) {
@@ -436,7 +445,10 @@
 		if (ret) {
 			pr_err("%s: gbam_connect failed: err:%d\n",
 					__func__, ret);
-			gsmd_ctrl_disconnect(&dev->port, port_num);
+			if (cxport == USB_GADGET_XPORT_QTI)
+				gqti_ctrl_disconnect(&dev->port);
+			else
+				gsmd_ctrl_disconnect(&dev->port, port_num);
 			return ret;
 		}
 		break;
@@ -484,6 +496,9 @@
 	case USB_GADGET_XPORT_SMD:
 		gsmd_ctrl_disconnect(&dev->port, port_num);
 		break;
+	case USB_GADGET_XPORT_QTI:
+		gqti_ctrl_disconnect(&dev->port);
+		break;
 	case USB_GADGET_XPORT_HSIC:
 		ghsic_ctrl_disconnect(&dev->port, port_num);
 		break;
@@ -1172,6 +1187,7 @@
 
 	nr_rmnet_ports = 0;
 	no_ctrl_smd_ports = 0;
+	no_ctrl_qti_ports = 0;
 	no_data_bam_ports = 0;
 	no_data_bam2bam_ports = 0;
 	no_ctrl_hsic_ports = 0;
@@ -1217,6 +1233,10 @@
 		rmnet_port->ctrl_xport_num = no_ctrl_smd_ports;
 		no_ctrl_smd_ports++;
 		break;
+	case USB_GADGET_XPORT_QTI:
+		rmnet_port->ctrl_xport_num = no_ctrl_qti_ports;
+		no_ctrl_qti_ports++;
+		break;
 	case USB_GADGET_XPORT_HSIC:
 		rmnet_port->ctrl_xport_num = no_ctrl_hsic_ports;
 		no_ctrl_hsic_ports++;
@@ -1270,6 +1290,7 @@
 
 	nr_rmnet_ports = 0;
 	no_ctrl_smd_ports = 0;
+	no_ctrl_qti_ports = 0;
 	no_data_bam_ports = 0;
 	no_ctrl_hsic_ports = 0;
 	no_data_hsic_ports = 0;
diff --git a/drivers/usb/gadget/u_rmnet.h b/drivers/usb/gadget/u_rmnet.h
index 81ba223..cea9369 100644
--- a/drivers/usb/gadget/u_rmnet.h
+++ b/drivers/usb/gadget/u_rmnet.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2013, The Linux Foundation. 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
@@ -55,5 +55,7 @@
 int gsmd_ctrl_connect(struct grmnet *gr, int port_num);
 void gsmd_ctrl_disconnect(struct grmnet *gr, u8 port_num);
 int gsmd_ctrl_setup(unsigned int count);
+int gqti_ctrl_connect(struct grmnet *gr);
+void gqti_ctrl_disconnect(struct grmnet *gr);
 
 #endif /* __U_RMNET_H*/
diff --git a/drivers/usb/gadget/u_rmnet_ctrl_qti.c b/drivers/usb/gadget/u_rmnet_ctrl_qti.c
new file mode 100644
index 0000000..e92978f
--- /dev/null
+++ b/drivers/usb/gadget/u_rmnet_ctrl_qti.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. 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/wait.h>
+#include <linux/poll.h>
+#include <linux/usb/rmnet_ctrl_qti.h>
+
+#include "u_rmnet.h"
+
+struct rmnet_ctrl_qti_port {
+	struct grmnet		*port_usb;
+
+	bool		is_open;
+
+	atomic_t	connected;
+	atomic_t	line_state;
+
+	atomic_t	open_excl;
+	atomic_t	read_excl;
+	atomic_t	write_excl;
+	atomic_t	ioctl_excl;
+
+	wait_queue_head_t read_wq;
+
+	struct list_head	cpkt_req_q;
+
+	spinlock_t			lock;
+};
+static struct rmnet_ctrl_qti_port *ctrl_port;
+
+static inline int rmnet_ctrl_lock(atomic_t *excl)
+{
+	if (atomic_inc_return(excl) == 1) {
+		return 0;
+	} else {
+		atomic_dec(excl);
+		return -EBUSY;
+	}
+}
+
+static inline void rmnet_ctrl_unlock(atomic_t *excl)
+{
+	atomic_dec(excl);
+}
+
+static void rmnet_ctrl_queue_notify(struct rmnet_ctrl_qti_port *port)
+{
+	unsigned long		flags;
+	struct rmnet_ctrl_pkt	*cpkt = NULL;
+
+	pr_debug("%s: Queue empty packet for QTI", __func__);
+
+	spin_lock_irqsave(&port->lock, flags);
+	if (!port->is_open) {
+		pr_err("%s: rmnet ctrl file handler %p is not open",
+			   __func__, port);
+		spin_unlock_irqrestore(&port->lock, flags);
+		return;
+	}
+
+	cpkt = alloc_rmnet_ctrl_pkt(0, GFP_ATOMIC);
+	if (!cpkt) {
+		pr_err("%s: Unable to allocate reset function pkt\n", __func__);
+		spin_unlock_irqrestore(&port->lock, flags);
+		return;
+	}
+
+	list_add_tail(&cpkt->list, &port->cpkt_req_q);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	pr_debug("%s: Wake up read queue", __func__);
+	wake_up(&port->read_wq);
+}
+
+static int grmnet_ctrl_qti_send_cpkt_tomodem(u8 portno,
+	void *buf, size_t len)
+{
+	unsigned long		flags;
+	struct rmnet_ctrl_qti_port	*port = ctrl_port;
+	struct rmnet_ctrl_pkt *cpkt;
+
+	if (len > MAX_QTI_PKT_SIZE) {
+		pr_err("given pkt size too big:%d > max_pkt_size:%d\n",
+				len, MAX_QTI_PKT_SIZE);
+		return -EINVAL;
+	}
+
+	cpkt = alloc_rmnet_ctrl_pkt(len, GFP_ATOMIC);
+	if (IS_ERR(cpkt)) {
+		pr_err("%s: Unable to allocate ctrl pkt\n", __func__);
+		return -ENOMEM;
+	}
+
+	memcpy(cpkt->buf, buf, len);
+	cpkt->len = len;
+
+	pr_debug("%s: Add to cpkt_req_q packet with len = %d\n", __func__, len);
+	spin_lock_irqsave(&port->lock, flags);
+
+	/* drop cpkt if port is not open */
+	if (!port->is_open) {
+		pr_err("rmnet file handler %p is not open", port);
+		spin_unlock_irqrestore(&port->lock, flags);
+		free_rmnet_ctrl_pkt(cpkt);
+		return 0;
+	}
+
+	list_add_tail(&cpkt->list, &port->cpkt_req_q);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	/* wakeup read thread */
+	pr_debug("%s: Wake up read queue", __func__);
+	wake_up(&port->read_wq);
+
+	return 0;
+}
+
+static void
+gqti_ctrl_notify_modem(void *gptr, u8 portno, int val)
+{
+	struct rmnet_ctrl_qti_port	*port = ctrl_port;
+
+	atomic_set(&port->line_state, val);
+
+	/* send 0 len pkt to qti to notify state change */
+	rmnet_ctrl_queue_notify(port);
+}
+
+int gqti_ctrl_connect(struct grmnet *gr)
+{
+	struct rmnet_ctrl_qti_port	*port;
+	unsigned long		flags;
+
+	pr_debug("%s: grmnet:%p\n", __func__, gr);
+
+	if (!gr) {
+		pr_err("%s: grmnet port is null\n", __func__);
+		return -ENODEV;
+	}
+
+	port = ctrl_port;
+
+	spin_lock_irqsave(&port->lock, flags);
+	port->port_usb = gr;
+	gr->send_encap_cmd = grmnet_ctrl_qti_send_cpkt_tomodem;
+	gr->notify_modem = gqti_ctrl_notify_modem;
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	atomic_set(&port->connected, 1);
+	wake_up(&port->read_wq);
+
+	if (port && port->port_usb && port->port_usb->connect)
+		port->port_usb->connect(port->port_usb);
+
+	return 0;
+}
+
+void gqti_ctrl_disconnect(struct grmnet *gr)
+{
+	struct rmnet_ctrl_qti_port	*port = ctrl_port;
+	unsigned long		flags;
+	struct rmnet_ctrl_pkt	*cpkt;
+
+	pr_debug("%s: grmnet:%p\n", __func__, gr);
+
+	if (!gr) {
+		pr_err("%s: grmnet port is null\n", __func__);
+		return;
+	}
+
+	if (port && port->port_usb && port->port_usb->disconnect)
+		port->port_usb->disconnect(port->port_usb);
+
+	atomic_set(&port->connected, 0);
+	atomic_set(&port->line_state, 0);
+	spin_lock_irqsave(&port->lock, flags);
+	port->port_usb = 0;
+	gr->send_encap_cmd = 0;
+	gr->notify_modem = 0;
+
+	while (!list_empty(&port->cpkt_req_q)) {
+		cpkt = list_first_entry(&port->cpkt_req_q,
+					struct rmnet_ctrl_pkt, list);
+
+		list_del(&cpkt->list);
+		free_rmnet_ctrl_pkt(cpkt);
+	}
+
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	/* send 0 len pkt to qti to notify state change */
+	rmnet_ctrl_queue_notify(port);
+}
+
+static int rmnet_ctrl_open(struct inode *ip, struct file *fp)
+{
+	unsigned long		flags;
+
+	pr_debug("Open rmnet_ctrl_qti device file\n");
+
+	if (rmnet_ctrl_lock(&ctrl_port->open_excl)) {
+		pr_debug("Already opened\n");
+		return -EBUSY;
+	}
+
+	fp->private_data = ctrl_port;
+
+	spin_lock_irqsave(&ctrl_port->lock, flags);
+	ctrl_port->is_open = true;
+	spin_unlock_irqrestore(&ctrl_port->lock, flags);
+
+	return 0;
+}
+
+static int rmnet_ctrl_release(struct inode *ip, struct file *fp)
+{
+	unsigned long		flags;
+	struct rmnet_ctrl_qti_port *port = fp->private_data;
+
+	pr_debug("Close rmnet control file");
+
+	spin_lock_irqsave(&port->lock, flags);
+	port->is_open = false;
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	rmnet_ctrl_unlock(&port->open_excl);
+
+	return 0;
+}
+
+static ssize_t
+rmnet_ctrl_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	struct rmnet_ctrl_qti_port *port = fp->private_data;
+	struct rmnet_ctrl_pkt *cpkt = NULL;
+	unsigned long flags;
+	int ret = 0;
+
+	pr_debug("%s: Enter(%d)\n", __func__, count);
+
+	if (count > MAX_QTI_PKT_SIZE) {
+		pr_err("Buffer size is too big %d, should be at most %d\n",
+			count, MAX_QTI_PKT_SIZE);
+		return -EINVAL;
+	}
+
+	if (rmnet_ctrl_lock(&port->read_excl)) {
+		pr_err("Previous reading is not finished yet\n");
+		return -EBUSY;
+	}
+
+	/* block until online */
+	while (!(atomic_read(&port->connected))) {
+		pr_debug("Not connected. Wait.\n");
+		ret = wait_event_interruptible(port->read_wq,
+			atomic_read(&port->connected));
+		if (ret < 0) {
+			rmnet_ctrl_unlock(&port->read_excl);
+			if (ret == -ERESTARTSYS)
+				return -ERESTARTSYS;
+			else
+				return -EINTR;
+		}
+	}
+
+	/* block until a new packet is available */
+	do {
+		spin_lock_irqsave(&port->lock, flags);
+		if (!list_empty(&port->cpkt_req_q))
+			break;
+		spin_unlock_irqrestore(&port->lock, flags);
+
+		pr_debug("%s: Requests list is empty. Wait.\n", __func__);
+		ret = wait_event_interruptible(port->read_wq,
+					!list_empty(&port->cpkt_req_q));
+		if (ret < 0) {
+			pr_debug("Waiting failed\n");
+			rmnet_ctrl_unlock(&port->read_excl);
+			return -ERESTARTSYS;
+		}
+	} while (1);
+
+	cpkt = list_first_entry(&port->cpkt_req_q, struct rmnet_ctrl_pkt,
+							list);
+	list_del(&cpkt->list);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	if (cpkt->len > count) {
+		pr_err("cpkt size too big:%d > buf size:%d\n",
+				cpkt->len, count);
+		rmnet_ctrl_unlock(&port->read_excl);
+		free_rmnet_ctrl_pkt(cpkt);
+		return -ENOMEM;
+	}
+
+	pr_debug("%s: cpkt size:%d\n", __func__, cpkt->len);
+
+
+	rmnet_ctrl_unlock(&port->read_excl);
+
+	ret = copy_to_user(buf, cpkt->buf, cpkt->len);
+	if (ret) {
+		pr_err("copy_to_user failed: err %d\n", ret);
+		ret = -EFAULT;
+	} else {
+		pr_debug("%s: copied %d bytes to user\n", __func__, cpkt->len);
+		ret = cpkt->len;
+	}
+
+	free_rmnet_ctrl_pkt(cpkt);
+
+	return ret;
+}
+
+static ssize_t
+rmnet_ctrl_write(struct file *fp, const char __user *buf, size_t count,
+		   loff_t *pos)
+{
+	struct rmnet_ctrl_qti_port *port = fp->private_data;
+	void *kbuf;
+	unsigned long flags;
+	int ret = 0;
+
+	pr_debug("%s: Enter(%d)", __func__, count);
+
+	if (!count) {
+		pr_debug("zero length ctrl pkt\n");
+		return -EINVAL;
+	}
+
+	if (count > MAX_QTI_PKT_SIZE) {
+		pr_debug("given pkt size too big:%d > max_pkt_size:%d\n",
+				count, MAX_QTI_PKT_SIZE);
+		return -EINVAL;
+	}
+
+	if (rmnet_ctrl_lock(&port->write_excl)) {
+		pr_err("Previous writing not finished yet\n");
+		return -EBUSY;
+	}
+
+	if (!atomic_read(&port->connected)) {
+		pr_debug("USB cable not connected\n");
+		rmnet_ctrl_unlock(&port->write_excl);
+		return -EPIPE;
+	}
+
+	kbuf = kmalloc(count, GFP_KERNEL);
+	if (!kbuf) {
+		pr_err("failed to allocate ctrl pkt\n");
+		rmnet_ctrl_unlock(&port->write_excl);
+		return -ENOMEM;
+	}
+	ret = copy_from_user(kbuf, buf, count);
+	if (ret) {
+		pr_err("copy_from_user failed err:%d\n", ret);
+		kfree(kbuf);
+		rmnet_ctrl_unlock(&port->write_excl);
+		return -EFAULT;
+	}
+
+	spin_lock_irqsave(&port->lock, flags);
+	if (port->port_usb && port->port_usb->send_cpkt_response) {
+		ret = port->port_usb->send_cpkt_response(port->port_usb,
+							kbuf, count);
+		if (ret) {
+			pr_err("failed to send ctrl packet. error=%d\n", ret);
+			spin_unlock_irqrestore(&port->lock, flags);
+			kfree(kbuf);
+			rmnet_ctrl_unlock(&port->write_excl);
+			return ret;
+		}
+	} else {
+		pr_err("send_cpkt_response callback is NULL\n");
+		spin_unlock_irqrestore(&port->lock, flags);
+		kfree(kbuf);
+		rmnet_ctrl_unlock(&port->write_excl);
+		return -EINVAL;
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	kfree(kbuf);
+	rmnet_ctrl_unlock(&port->write_excl);
+
+	pr_debug("%s: Exit(%d)", __func__, count);
+
+	return count;
+
+}
+
+static long rmnet_ctrl_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
+{
+	struct rmnet_ctrl_qti_port *port = fp->private_data;
+	int val, ret = 0;
+
+	pr_debug("%s: Received command %d", __func__, cmd);
+
+	if (rmnet_ctrl_lock(&port->ioctl_excl))
+		return -EBUSY;
+
+	switch (cmd) {
+	case FRMNET_CTRL_GET_LINE_STATE:
+		val = atomic_read(&port->line_state);
+		ret = copy_to_user((void __user *)arg, &val, sizeof(val));
+		if (ret) {
+			pr_err("copying to user space failed");
+			ret = -EFAULT;
+		}
+		pr_debug("%s: Sent line_state: %d", __func__,
+				 atomic_read(&port->line_state));
+		break;
+	default:
+		pr_err("wrong parameter");
+		ret = -EINVAL;
+	}
+
+	rmnet_ctrl_unlock(&port->ioctl_excl);
+
+	return ret;
+}
+
+static unsigned int rmnet_ctrl_poll(struct file *file, poll_table *wait)
+{
+	struct rmnet_ctrl_qti_port *port = file->private_data;
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	if (!port) {
+		pr_err("%s on a NULL device\n", __func__);
+		return POLLERR;
+	}
+
+	poll_wait(file, &port->read_wq, wait);
+
+	spin_lock_irqsave(&port->lock, flags);
+	if (!list_empty(&port->cpkt_req_q)) {
+		mask |= POLLIN | POLLRDNORM;
+		pr_debug("%s sets POLLIN for rmnet_ctrl_qti_port\n", __func__);
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	return mask;
+}
+
+/* file operations for rmnet device /dev/rmnet_ctrl */
+static const struct file_operations rmnet_ctrl_fops = {
+	.owner = THIS_MODULE,
+	.open = rmnet_ctrl_open,
+	.release = rmnet_ctrl_release,
+	.read = rmnet_ctrl_read,
+	.write = rmnet_ctrl_write,
+	.unlocked_ioctl = rmnet_ctrl_ioctl,
+	.poll = rmnet_ctrl_poll,
+};
+
+static struct miscdevice rmnet_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "rmnet_ctrl",
+	.fops = &rmnet_ctrl_fops,
+};
+
+static int __init gqti_ctrl_init(void)
+{
+	int ret;
+	struct rmnet_ctrl_qti_port *port = NULL;
+
+	port = kzalloc(sizeof(struct rmnet_ctrl_qti_port), GFP_KERNEL);
+	if (!port) {
+		pr_err("Failed to allocate rmnet control device\n");
+		return -ENOMEM;
+	}
+
+	INIT_LIST_HEAD(&port->cpkt_req_q);
+	spin_lock_init(&port->lock);
+
+	atomic_set(&port->open_excl, 0);
+	atomic_set(&port->read_excl, 0);
+	atomic_set(&port->write_excl, 0);
+	atomic_set(&port->ioctl_excl, 0);
+	atomic_set(&port->connected, 0);
+	atomic_set(&port->line_state, 0);
+
+	init_waitqueue_head(&port->read_wq);
+
+	ctrl_port = port;
+
+	ret = misc_register(&rmnet_device);
+	if (ret) {
+		pr_err("rmnet control driver failed to register");
+		goto fail_init;
+	}
+
+	return ret;
+
+fail_init:
+	kfree(port);
+	ctrl_port = NULL;
+	return ret;
+}
+module_init(gqti_ctrl_init);
+
+static void __exit gqti_ctrl_cleanup(void)
+{
+	misc_deregister(&rmnet_device);
+
+	kfree(ctrl_port);
+	ctrl_port = NULL;
+}
+module_exit(gqti_ctrl_cleanup);
diff --git a/include/linux/usb/rmnet_ctrl_qti.h b/include/linux/usb/rmnet_ctrl_qti.h
new file mode 100644
index 0000000..5038396
--- /dev/null
+++ b/include/linux/usb/rmnet_ctrl_qti.h
@@ -0,0 +1,12 @@
+#ifndef __LINUX_USB_U_RMNET_CTRL_QTI_H
+#define __LINUX_USB_U_RMNET_CTRL_QTI_H
+
+#include <linux/ioctl.h>
+
+#define MAX_QTI_PKT_SIZE 2048
+
+#define FRMNET_CTRL_IOCTL_MAGIC	'r'
+#define FRMNET_CTRL_GET_LINE_STATE	_IOR(FRMNET_CTRL_IOCTL_MAGIC, 2, int)
+
+
+#endif /* __LINUX_USB_U_RMNET_CTRL_QTI_H */