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