msm: add hsic_tty driver

hsic_tty driver is using for bypass diag packet to modem
with userspace application.

Change-Id: I412691016a208df41a38a6e98292a5223024166a
diff --git a/arch/arm/mach-msm/hsic_tty.c b/arch/arm/mach-msm/hsic_tty.c
new file mode 100644
index 0000000..22147ce
--- /dev/null
+++ b/arch/arm/mach-msm/hsic_tty.c
@@ -0,0 +1,770 @@
+/* arch/arm/mach-msm/hsic_tty.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#include <mach/usb_bridge.h>
+
+#define MAX_HSIC_TTYS 2
+#define MAX_TTY_BUF_SIZE 2048
+
+static DEFINE_MUTEX(hsic_tty_lock);
+
+static uint hsic_tty_modem_wait = 60;
+module_param_named(modem_wait, hsic_tty_modem_wait,
+		uint, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static uint lge_ds_modem_wait = 20;
+module_param_named(ds_modem_wait, lge_ds_modem_wait,
+		uint, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define DATA_BRIDGE_NAME_MAX_LEN		20
+
+#define HSIC_TTY_DATA_RMNET_RX_Q_SIZE		50
+#define HSIC_TTY_DATA_RMNET_TX_Q_SIZE		300
+#define HSIC_TTY_DATA_SERIAL_RX_Q_SIZE		2
+#define HSIC_TTY_DATA_SERIAL_TX_Q_SIZE		2
+#define HSIC_TTY_DATA_RX_REQ_SIZE		2048
+#define HSIC_TTY_DATA_TX_INTR_THRESHOLD		20
+
+static unsigned int hsic_tty_data_rmnet_tx_q_size =
+	HSIC_TTY_DATA_RMNET_TX_Q_SIZE;
+module_param(hsic_tty_data_rmnet_tx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_rmnet_rx_q_size =
+	HSIC_TTY_DATA_RMNET_RX_Q_SIZE;
+module_param(hsic_tty_data_rmnet_rx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_serial_tx_q_size =
+	HSIC_TTY_DATA_SERIAL_TX_Q_SIZE;
+module_param(hsic_tty_data_serial_tx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_serial_rx_q_size =
+	HSIC_TTY_DATA_SERIAL_RX_Q_SIZE;
+module_param(hsic_tty_data_serial_rx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_rx_req_size = HSIC_TTY_DATA_RX_REQ_SIZE;
+module_param(hsic_tty_data_rx_req_size, uint, S_IRUGO | S_IWUSR);
+
+unsigned int hsic_tty_data_tx_intr_thld = HSIC_TTY_DATA_TX_INTR_THRESHOLD;
+module_param(hsic_tty_data_tx_intr_thld, uint, S_IRUGO | S_IWUSR);
+
+/*flow ctrl*/
+#define HSIC_TTY_DATA_FLOW_CTRL_EN_THRESHOLD	500
+#define HSIC_TTY_DATA_FLOW_CTRL_DISABLE		300
+#define HSIC_TTY_DATA_FLOW_CTRL_SUPPORT		1
+#define HSIC_TTY_DATA_PENDLIMIT_WITH_BRIDGE	500
+
+static unsigned int hsic_tty_data_fctrl_support =
+	HSIC_TTY_DATA_FLOW_CTRL_SUPPORT;
+module_param(hsic_tty_data_fctrl_support, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_fctrl_en_thld =
+	HSIC_TTY_DATA_FLOW_CTRL_EN_THRESHOLD;
+module_param(hsic_tty_data_fctrl_en_thld, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_fctrl_dis_thld =
+	HSIC_TTY_DATA_FLOW_CTRL_DISABLE;
+module_param(hsic_tty_data_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int hsic_tty_data_pend_limit_with_bridge =
+	HSIC_TTY_DATA_PENDLIMIT_WITH_BRIDGE;
+module_param(hsic_tty_data_pend_limit_with_bridge, uint, S_IRUGO | S_IWUSR);
+
+#define CH_OPENED 0
+#define CH_READY 1
+
+struct hsic_tty_info {
+	struct tty_struct *tty;
+	struct wake_lock wake_lock;
+	int open_count;
+	struct timer_list buf_req_timer;
+	struct completion ch_allocated;
+	struct platform_driver driver;
+	int in_reset;
+	int in_reset_updated;
+	int is_open;
+
+	wait_queue_head_t ch_opened_wait_queue;
+	spinlock_t reset_lock;
+	struct hsic_config *hsic;
+
+	/* gadget */
+	atomic_t connected;
+
+	/* data transfer queues */
+	unsigned int tx_q_size;
+	struct list_head tx_idle;
+	struct sk_buff_head tx_skb_q;
+	spinlock_t tx_lock;
+
+	unsigned int rx_q_size;
+	struct list_head rx_idle;
+	struct sk_buff_head rx_skb_q;
+	spinlock_t rx_lock;
+
+	/* work */
+	struct workqueue_struct *wq;
+	struct work_struct connect_w;
+	struct work_struct disconnect_w;
+	struct work_struct write_tomdm_w;
+	struct work_struct write_tohost_w;
+
+	struct bridge brdg;
+
+	/*bridge status */
+	unsigned long bridge_sts;
+
+	/*counters */
+	unsigned long to_modem;
+	unsigned long to_host;
+	unsigned int rx_throttled_cnt;
+	unsigned int rx_unthrottled_cnt;
+	unsigned int tx_throttled_cnt;
+	unsigned int tx_unthrottled_cnt;
+	unsigned int tomodem_drp_cnt;
+	unsigned int unthrottled_pnd_skbs;
+};
+
+/**
+ * HSIC port configuration.
+ *
+ * @tty_dev_index   Index into hsic_tty[]
+ * @port_name       Name of the HSIC port
+ * @dev_name        Name of the TTY Device (if NULL, @port_name is used)
+ * @edge            HSIC edge
+ */
+struct hsic_config {
+	uint32_t tty_dev_index;
+	const char *port_name;
+	const char *dev_name;
+};
+
+static struct hsic_config hsic_configs[] = {
+	{0, "dun_data_hsic0", NULL},
+	//{1, "rmnet_data_hsic0", NULL},
+};
+
+static struct hsic_tty_info hsic_tty[MAX_HSIC_TTYS];
+
+static int is_in_reset(struct hsic_tty_info *info)
+{
+	return info->in_reset;
+}
+
+static void buf_req_retry(unsigned long param)
+{
+	struct hsic_tty_info *info = (struct hsic_tty_info *)param;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->reset_lock, flags);
+	if (info->is_open) {
+		spin_unlock_irqrestore(&info->reset_lock, flags);
+		queue_work(info->wq, &info->write_tohost_w);
+		return;
+	}
+	spin_unlock_irqrestore(&info->reset_lock, flags);
+}
+
+static void hsic_tty_data_write_tohost(struct work_struct *w)
+{
+	struct hsic_tty_info *info =
+		container_of(w, struct hsic_tty_info, write_tohost_w);
+	struct tty_struct *tty = info->tty;
+	struct sk_buff *skb;
+	unsigned char *ptr;
+	unsigned long flags;
+	int avail;
+
+	pr_debug("%s\n", __func__);
+
+	if (!info)
+		return;
+
+	spin_lock_irqsave(&info->tx_lock, flags);
+	for (;;) {
+		if (is_in_reset(info)) {
+			/* signal TTY clients using TTY_BREAK */
+			tty_insert_flip_char(tty, 0x00, TTY_BREAK);
+			tty_flip_buffer_push(tty);
+			break;
+		}
+
+		skb = __skb_dequeue(&info->tx_skb_q);
+		if (!skb)
+			break;
+
+		avail = skb->len;
+		if (avail == 0)
+			break;
+
+		avail = tty_prepare_flip_string(tty, &ptr, avail);
+		if (avail <= 0) {
+			if (!timer_pending(&info->buf_req_timer)) {
+				init_timer(&info->buf_req_timer);
+				info->buf_req_timer.expires = jiffies +
+				    ((30 * HZ) / 1000);
+				info->buf_req_timer.function = buf_req_retry;
+				info->buf_req_timer.data = (unsigned long)info;
+				add_timer(&info->buf_req_timer);
+			}
+			spin_unlock_irqrestore(&info->tx_lock, flags);
+			return;
+		}
+
+		memcpy(ptr, skb->data, avail);
+		dev_kfree_skb_any(skb);
+
+		wake_lock_timeout(&info->wake_lock, HZ / 2);
+		tty_flip_buffer_push(tty);
+
+		info->to_host++;
+	}
+
+	/* XXX only when writable and necessary */
+	tty_wakeup(tty);
+	spin_unlock_irqrestore(&info->tx_lock, flags);
+}
+
+static int hsic_tty_data_receive(void *p, void *data, size_t len)
+{
+	struct hsic_tty_info *info = p;
+	unsigned long flags;
+	struct sk_buff *skb = data;
+
+	if (!info || !atomic_read(&info->connected)) {
+		dev_kfree_skb_any(skb);
+		return -ENOTCONN;
+	}
+
+	pr_debug("%s: p:%p#%d skb_len:%d\n", __func__,
+		 info, info->tty->index, skb->len);
+
+	spin_lock_irqsave(&info->tx_lock, flags);
+	__skb_queue_tail(&info->tx_skb_q, skb);
+
+	if (hsic_tty_data_fctrl_support &&
+	    info->tx_skb_q.qlen >= hsic_tty_data_fctrl_en_thld) {
+		set_bit(RX_THROTTLED, &info->brdg.flags);
+		info->rx_throttled_cnt++;
+		pr_debug("%s: flow ctrl enabled: tx skbq len: %u\n",
+			 __func__, info->tx_skb_q.qlen);
+		spin_unlock_irqrestore(&info->tx_lock, flags);
+		queue_work(info->wq, &info->write_tohost_w);
+		return -EBUSY;
+	}
+
+	spin_unlock_irqrestore(&info->tx_lock, flags);
+
+	queue_work(info->wq, &info->write_tohost_w);
+
+	return 0;
+}
+
+static void hsic_tty_data_write_tomdm(struct work_struct *w)
+{
+	struct hsic_tty_info *info =
+		container_of(w, struct hsic_tty_info, write_tomdm_w);
+	struct sk_buff *skb;
+	unsigned long flags;
+	int ret;
+
+	pr_debug("%s\n", __func__);
+
+	if (!info || !atomic_read(&info->connected))
+		return;
+
+	spin_lock_irqsave(&info->rx_lock, flags);
+	if (test_bit(TX_THROTTLED, &info->brdg.flags)) {
+		spin_unlock_irqrestore(&info->rx_lock, flags);
+		return;
+	}
+
+	while ((skb = __skb_dequeue(&info->rx_skb_q))) {
+		pr_debug("%s: info:%p tom:%lu pno:%d\n", __func__,
+			 info, info->to_modem, info->tty->index);
+
+		spin_unlock_irqrestore(&info->rx_lock, flags);
+		ret = data_bridge_write(info->brdg.ch_id, skb);
+		spin_lock_irqsave(&info->rx_lock, flags);
+		if (ret < 0) {
+			if (ret == -EBUSY) {
+				/*flow control */
+				info->tx_throttled_cnt++;
+				break;
+			}
+			pr_err("%s: write error:%d\n", __func__, ret);
+			info->tomodem_drp_cnt++;
+			dev_kfree_skb_any(skb);
+			break;
+		}
+		info->to_modem++;
+	}
+	spin_unlock_irqrestore(&info->rx_lock, flags);
+}
+
+static void hsic_tty_data_connect_w(struct work_struct *w)
+{
+	struct hsic_tty_info *info =
+		container_of(w, struct hsic_tty_info, connect_w);
+	unsigned long flags;
+	int ret;
+
+	pr_debug("%s\n", __func__);
+
+	if (!info || !atomic_read(&info->connected) ||
+			!test_bit(CH_READY, &info->bridge_sts))
+		return;
+
+	pr_debug("%s: info:%p\n", __func__, info);
+
+	ret = data_bridge_open(&info->brdg);
+	if (ret) {
+		pr_err("%s: unable open bridge ch:%d err:%d\n",
+		       __func__, info->brdg.ch_id, ret);
+		return;
+	}
+
+	set_bit(CH_OPENED, &info->bridge_sts);
+
+	spin_lock_irqsave(&info->reset_lock, flags);
+	info->in_reset = 0;
+	info->in_reset_updated = 1;
+	info->is_open = 1;
+	wake_up_interruptible(&info->ch_opened_wait_queue);
+	spin_unlock_irqrestore(&info->reset_lock, flags);
+}
+
+static void hsic_tty_data_disconnect_w(struct work_struct *w)
+{
+	struct hsic_tty_info *info =
+		container_of(w, struct hsic_tty_info, connect_w);
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	if (!test_bit(CH_OPENED, &info->bridge_sts))
+		return;
+
+	data_bridge_close(info->brdg.ch_id);
+	clear_bit(CH_OPENED, &info->bridge_sts);
+
+	spin_lock_irqsave(&info->reset_lock, flags);
+	info->in_reset = 1;
+	info->in_reset_updated = 1;
+	info->is_open = 0;
+	wake_up_interruptible(&info->ch_opened_wait_queue);
+	spin_unlock_irqrestore(&info->reset_lock, flags);
+	/* schedule task to send TTY_BREAK */
+	queue_work(info->wq, &info->write_tohost_w);
+}
+
+static int hsic_tty_open(struct tty_struct *tty, struct file *f)
+{
+	int res = 0;
+	unsigned int n = tty->index;
+	struct hsic_tty_info *info;
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	if (n >= MAX_HSIC_TTYS || !hsic_tty[n].hsic)
+		return -ENODEV;
+
+	info = hsic_tty + n;
+
+	mutex_lock(&hsic_tty_lock);
+	tty->driver_data = info;
+
+	if (info->open_count++ == 0) {
+		/*
+		 * Wait for a channel to be allocated so we know
+		 * the modem is ready enough.
+		 */
+		if (hsic_tty_modem_wait) {
+			res = try_wait_for_completion(&info->ch_allocated);
+
+			if (res == 0) {
+				pr_debug
+				    ("%s: Timed out waiting for HSIC channel\n",
+				     __func__);
+				res = -ETIMEDOUT;
+				goto out;
+			} else if (res < 0) {
+				pr_err
+				    ("%s: Error waiting for HSIC channel: %d\n",
+				     __func__, res);
+				goto out;
+			}
+			pr_info("%s: opened %s\n", __func__,
+				hsic_tty[n].hsic->port_name);
+
+			res = 0;
+		}
+
+		info->tty = tty;
+		wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND,
+			       hsic_tty[n].hsic->port_name);
+		if (!atomic_read(&info->connected)) {
+			atomic_set(&info->connected, 1);
+
+			spin_lock_irqsave(&info->tx_lock, flags);
+			info->to_host = 0;
+			info->rx_throttled_cnt = 0;
+			info->rx_unthrottled_cnt = 0;
+			info->unthrottled_pnd_skbs = 0;
+			spin_unlock_irqrestore(&info->tx_lock, flags);
+
+			spin_lock_irqsave(&info->rx_lock, flags);
+			info->to_modem = 0;
+			info->tomodem_drp_cnt = 0;
+			info->tx_throttled_cnt = 0;
+			info->tx_unthrottled_cnt = 0;
+			spin_unlock_irqrestore(&info->rx_lock, flags);
+
+			set_bit(CH_READY, &info->bridge_sts);
+
+			queue_work(info->wq, &info->connect_w);
+
+			res =
+			    wait_event_interruptible_timeout(info->
+							     ch_opened_wait_queue,
+							     info->is_open,
+							     (2 * HZ));
+			if (res == 0)
+				res = -ETIMEDOUT;
+			if (res < 0) {
+				pr_err("%s: wait for %s hsic_open failed %d\n",
+				       __func__, hsic_tty[n].hsic->port_name,
+				       res);
+				goto out;
+			}
+			res = 0;
+		}
+	}
+
+out:
+	mutex_unlock(&hsic_tty_lock);
+
+	return res;
+}
+
+static void hsic_tty_close(struct tty_struct *tty, struct file *f)
+{
+	struct hsic_tty_info *info = tty->driver_data;
+	unsigned long flags;
+	int res = 0;
+	int n = tty->index;
+	struct sk_buff *skb;
+
+	pr_debug("%s\n", __func__);
+
+	if (info == 0)
+		return;
+
+	mutex_lock(&hsic_tty_lock);
+	if (--info->open_count == 0) {
+		spin_lock_irqsave(&info->reset_lock, flags);
+		info->is_open = 0;
+		spin_unlock_irqrestore(&info->reset_lock, flags);
+		if (info->tty) {
+			wake_lock_destroy(&info->wake_lock);
+			info->tty = 0;
+		}
+		tty->driver_data = 0;
+		del_timer(&info->buf_req_timer);
+		if (atomic_read(&info->connected)) {
+			atomic_set(&info->connected, 0);
+
+			spin_lock_irqsave(&info->tx_lock, flags);
+			clear_bit(RX_THROTTLED, &info->brdg.flags);
+			spin_unlock_irqrestore(&info->tx_lock, flags);
+
+			spin_lock_irqsave(&info->rx_lock, flags);
+			clear_bit(TX_THROTTLED, &info->brdg.flags);
+			spin_unlock_irqrestore(&info->rx_lock, flags);
+
+			queue_work(info->wq, &info->disconnect_w);
+
+			pr_info("%s: waiting to close hsic %s completely\n",
+				__func__, hsic_tty[n].hsic->port_name);
+			/* wait for reopen ready status in seconds */
+			res =
+			    wait_event_interruptible_timeout(info->
+							     ch_opened_wait_queue,
+							     !info->is_open,
+							     (lge_ds_modem_wait
+							      * HZ));
+			if (res == 0) {
+				/* just in case, remain result value */
+				res = -ETIMEDOUT;
+				pr_err("%s: timeout to wait for %s hsic_close.\
+                        next hsic_open may fail....%d\n", __func__, hsic_tty[n].hsic->port_name, res);
+			}
+			if (res < 0) {
+				pr_err("%s: wait for %s hsic_close failed.\
+                        next hsic_open may fail....%d\n", __func__, hsic_tty[n].hsic->port_name, res);
+			}
+
+			data_bridge_close(info->brdg.ch_id);
+
+			clear_bit(CH_READY, &info->bridge_sts);
+			clear_bit(CH_OPENED, &info->bridge_sts);
+
+			spin_lock_irqsave(&info->tx_lock, flags);
+			while ((skb = __skb_dequeue(&info->tx_skb_q)))
+				dev_kfree_skb_any(skb);
+			spin_unlock_irqrestore(&info->tx_lock, flags);
+
+			spin_lock_irqsave(&info->rx_lock, flags);
+			while ((skb = __skb_dequeue(&info->rx_skb_q)))
+				dev_kfree_skb_any(skb);
+			spin_unlock_irqrestore(&info->rx_lock, flags);
+		}
+	}
+	mutex_unlock(&hsic_tty_lock);
+}
+
+static int hsic_tty_write(struct tty_struct *tty, const unsigned char *buf,
+			  int len)
+{
+	struct hsic_tty_info *info = tty->driver_data;
+	int avail;
+	struct sk_buff *skb;
+
+	pr_debug("%s\n", __func__);
+
+	/* if we're writing to a packet channel we will
+	 ** never be able to write more data than there
+	 ** is currently space for
+	 */
+	if (is_in_reset(info))
+		return -ENETRESET;
+
+	avail = test_bit(CH_OPENED, &info->bridge_sts);
+	/* if no space, we'll have to setup a notification later to wake up the
+	 * tty framework when space becomes avaliable
+	 */
+	if (!avail)
+		return 0;
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	skb->data = (unsigned char *)buf;
+	skb->len = len;
+
+	spin_lock(&info->rx_lock);
+	__skb_queue_tail(&info->rx_skb_q, skb);
+	queue_work(info->wq, &info->write_tomdm_w);
+	spin_unlock(&info->rx_lock);
+
+	return len;
+}
+
+static int hsic_tty_write_room(struct tty_struct *tty)
+{
+	struct hsic_tty_info *info = tty->driver_data;
+	return test_bit(CH_OPENED, &info->bridge_sts);
+}
+
+static int hsic_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct hsic_tty_info *info = tty->driver_data;
+	return test_bit(CH_OPENED, &info->bridge_sts);
+}
+
+static void hsic_tty_unthrottle(struct tty_struct *tty)
+{
+	struct hsic_tty_info *info = tty->driver_data;
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	spin_lock_irqsave(&info->reset_lock, flags);
+	if (info->is_open) {
+		spin_unlock_irqrestore(&info->reset_lock, flags);
+		if (hsic_tty_data_fctrl_support &&
+		    info->tx_skb_q.qlen <= hsic_tty_data_fctrl_dis_thld &&
+		    test_and_clear_bit(RX_THROTTLED, &info->brdg.flags)) {
+			info->rx_unthrottled_cnt++;
+			info->unthrottled_pnd_skbs = info->tx_skb_q.qlen;
+			pr_debug("%s: disable flow ctrl:"
+				 " tx skbq len: %u\n",
+				 __func__, info->tx_skb_q.qlen);
+			data_bridge_unthrottle_rx(info->brdg.ch_id);
+			queue_work(info->wq, &info->write_tohost_w);
+		}
+		return;
+	}
+	spin_unlock_irqrestore(&info->reset_lock, flags);
+}
+
+static struct tty_operations hsic_tty_ops = {
+	.open = hsic_tty_open,
+	.close = hsic_tty_close,
+	.write = hsic_tty_write,
+	.write_room = hsic_tty_write_room,
+	.chars_in_buffer = hsic_tty_chars_in_buffer,
+	.unthrottle = hsic_tty_unthrottle,
+};
+
+static int hsic_tty_dummy_probe(struct platform_device *pdev)
+{
+	int n;
+	int idx;
+
+	for (n = 0; n < ARRAY_SIZE(hsic_configs); ++n) {
+		idx = hsic_configs[n].tty_dev_index;
+
+		if (!hsic_configs[n].dev_name)
+			continue;
+
+		if (/* pdev->id == hsic_configs[n].edge && */
+		    !strncmp(pdev->name, hsic_configs[n].dev_name,
+			    DATA_BRIDGE_NAME_MAX_LEN)) {
+			complete_all(&hsic_tty[idx].ch_allocated);
+			pr_info("%s: %s ch_allocated\n", __func__,
+				hsic_configs[n].dev_name);
+			return 0;
+		}
+	}
+	pr_err("%s: unknown device '%s'\n", __func__, pdev->name);
+
+	return -ENODEV;
+}
+
+static struct tty_driver *hsic_tty_driver;
+
+static int __init hsic_tty_init(void)
+{
+	int ret;
+	int n;
+	int idx;
+
+	pr_debug("%s\n", __func__);
+
+	hsic_tty_driver = alloc_tty_driver(MAX_HSIC_TTYS);
+	if (hsic_tty_driver == 0)
+		return -ENOMEM;
+
+	hsic_tty_driver->owner = THIS_MODULE;
+	hsic_tty_driver->driver_name = "hsic_tty_driver";
+	hsic_tty_driver->name = "hsic";
+	hsic_tty_driver->major = 0;
+	hsic_tty_driver->minor_start = 0;
+	hsic_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	hsic_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	hsic_tty_driver->init_termios = tty_std_termios;
+	hsic_tty_driver->init_termios.c_iflag = 0;
+	hsic_tty_driver->init_termios.c_oflag = 0;
+	hsic_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	hsic_tty_driver->init_termios.c_lflag = 0;
+	hsic_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS |
+		TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	tty_set_operations(hsic_tty_driver, &hsic_tty_ops);
+
+	ret = tty_register_driver(hsic_tty_driver);
+	if (ret) {
+		put_tty_driver(hsic_tty_driver);
+		pr_err("%s: driver registration failed %d\n", __func__, ret);
+		return ret;
+	}
+
+	for (n = 0; n < ARRAY_SIZE(hsic_configs); ++n) {
+		idx = hsic_configs[n].tty_dev_index;
+
+		if (hsic_configs[n].dev_name == NULL)
+			hsic_configs[n].dev_name = hsic_configs[n].port_name;
+
+		tty_register_device(hsic_tty_driver, idx, 0);
+		init_completion(&hsic_tty[idx].ch_allocated);
+
+		hsic_tty[idx].wq =
+		    create_singlethread_workqueue(hsic_configs[n].port_name);
+		if (!hsic_tty[idx].wq) {
+			pr_err("%s: Unable to create workqueue:%s\n",
+			       __func__, hsic_configs[n].port_name);
+			return -ENOMEM;
+		}
+
+		/* port initialization */
+		spin_lock_init(&hsic_tty[idx].rx_lock);
+		spin_lock_init(&hsic_tty[idx].tx_lock);
+
+		INIT_WORK(&hsic_tty[idx].connect_w, hsic_tty_data_connect_w);
+		INIT_WORK(&hsic_tty[idx].disconnect_w,
+			  hsic_tty_data_disconnect_w);
+		INIT_WORK(&hsic_tty[idx].write_tohost_w,
+			  hsic_tty_data_write_tohost);
+		INIT_WORK(&hsic_tty[idx].write_tomdm_w,
+			  hsic_tty_data_write_tomdm);
+
+		INIT_LIST_HEAD(&hsic_tty[idx].tx_idle);
+		INIT_LIST_HEAD(&hsic_tty[idx].rx_idle);
+
+		skb_queue_head_init(&hsic_tty[idx].tx_skb_q);
+		skb_queue_head_init(&hsic_tty[idx].rx_skb_q);
+
+		hsic_tty[idx].brdg.ch_id = idx;
+		hsic_tty[idx].brdg.ctx = &hsic_tty[idx];
+		hsic_tty[idx].brdg.ops.send_pkt = hsic_tty_data_receive;
+
+		hsic_tty[idx].driver.probe = hsic_tty_dummy_probe;
+		hsic_tty[idx].driver.driver.name = hsic_configs[n].dev_name;
+		hsic_tty[idx].driver.driver.owner = THIS_MODULE;
+		spin_lock_init(&hsic_tty[idx].reset_lock);
+		hsic_tty[idx].is_open = 0;
+		init_waitqueue_head(&hsic_tty[idx].ch_opened_wait_queue);
+		ret = platform_driver_register(&hsic_tty[idx].driver);
+
+		if (ret) {
+			pr_err("%s: init failed %d (%d)\n", __func__, idx, ret);
+			hsic_tty[idx].driver.probe = NULL;
+			goto out;
+		}
+		hsic_tty[idx].hsic = &hsic_configs[n];
+	}
+	return 0;
+
+out:
+	/* unregister platform devices */
+	for (n = 0; n < ARRAY_SIZE(hsic_configs); ++n) {
+		idx = hsic_configs[n].tty_dev_index;
+
+		if (hsic_tty[idx].driver.probe) {
+			platform_driver_unregister(&hsic_tty[idx].driver);
+			tty_unregister_device(hsic_tty_driver, idx);
+		}
+	}
+
+	tty_unregister_driver(hsic_tty_driver);
+	put_tty_driver(hsic_tty_driver);
+	return ret;
+}
+
+module_init(hsic_tty_init);