aboutsummaryrefslogtreecommitdiff
path: root/drivers/tty/serial/msm_smd_tty.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/msm_smd_tty.c')
-rw-r--r--drivers/tty/serial/msm_smd_tty.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/drivers/tty/serial/msm_smd_tty.c b/drivers/tty/serial/msm_smd_tty.c
new file mode 100644
index 000000000000..1ffab5ea0d92
--- /dev/null
+++ b/drivers/tty/serial/msm_smd_tty.c
@@ -0,0 +1,417 @@
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/soc/qcom/smd.h>
+#include <linux/suspend.h>
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#define MAX_SMD_TTY_PORTS 32
+#define CHANNEL_PROBE_TIMEOUT 60000
+
+static struct tty_driver *smd_tty_driver;
+
+struct msm_smd_port_info_s {
+ char *name;
+ u32 flags;
+ int index;
+ int smd_tty_info_index;
+};
+
+#define SMD_DROP_ONE_TX_BYTE (1)
+#define SMD_TTY_ACL (1 << 1)
+#define SMD_TTY_CMD (1 << 2)
+
+static struct msm_smd_port_info_s msm_smd_port_info[] = {
+ {
+ .name = "DUMMY_SMD_TTY0",
+ .flags = 0,
+ .index = 0,
+ .smd_tty_info_index = 0,
+ }, {
+ .name = "DUMMY_SMD_TTY1",
+ .flags = 0,
+ .index = 1,
+ .smd_tty_info_index = 1,
+ }, {
+ .name = "APPS_RIVA_BT_ACL",
+ .flags = SMD_DROP_ONE_TX_BYTE | SMD_TTY_ACL,
+ .index = 2,
+ .smd_tty_info_index = 3,
+ }, {
+ .name = "APPS_RIVA_BT_CMD",
+ .flags = SMD_DROP_ONE_TX_BYTE | SMD_TTY_CMD,
+ .index = 3,
+ .smd_tty_info_index = 3,
+ },
+};
+
+struct msm_smd_port_info_s *match_smd_port_info(char *name)
+{
+ int i;
+ struct msm_smd_port_info_s *pinfo = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(msm_smd_port_info); i++) {
+ if (!strcmp(name, msm_smd_port_info[i].name)) {
+ pinfo = &msm_smd_port_info[i];
+ break;
+ }
+ }
+
+ return pinfo;
+}
+
+struct msm_smd_channel_info {
+ struct qcom_smd_device *cmd_sdev;
+ struct qcom_smd_channel *channel;
+
+ struct completion ack;
+ struct mutex lock;
+
+ u32 flags;
+ int smd_tty_info_index;
+};
+
+struct msm_smd_tty_info_s {
+ /* For some special case (like BT), out/in channel are differnet.
+ * In most case, they should be same.
+ */
+ struct msm_smd_channel_info *cmd_info;
+ struct msm_smd_channel_info *acl_info;
+
+ struct tty_port port;
+ struct device *tty;
+
+ struct mutex open_lock;
+ struct completion probe_done;
+ int opened;
+};
+
+void dump_buf(unsigned char *buf, int len)
+{
+ int i;
+ unsigned char *tmp = buf;
+
+ printk("\n\t");
+
+ for (i = 0; i < len; i++) {
+ printk("0x%02x ", tmp[i]);
+ if (((i + 1) % 16) == 0)
+ printk("\n\t");
+ }
+}
+
+static struct msm_smd_tty_info_s msm_smd_tty_info[MAX_SMD_TTY_PORTS];
+
+static int qcom_smd_tty_probe(struct qcom_smd_device *sdev)
+{
+ struct msm_smd_channel_info *ch_info;
+ struct msm_smd_port_info_s *pinfo;
+ int idx = 0;
+
+ ch_info = devm_kzalloc(&sdev->dev, sizeof(*ch_info), GFP_KERNEL);
+ if (!ch_info)
+ return -ENOMEM;
+
+ mutex_init(&ch_info->lock);
+ init_completion(&ch_info->ack);
+ ch_info->channel = sdev->channel;
+
+ pinfo = match_smd_port_info(ch_info->channel->name);
+ if (!pinfo) {
+ pr_err("Can't match channel %s\n", ch_info->channel->name);
+ kfree(ch_info);
+ return -EINVAL;
+ }
+
+ ch_info->flags = pinfo->flags;
+ idx = ch_info->smd_tty_info_index = pinfo->smd_tty_info_index;
+
+ if (pinfo->flags & SMD_TTY_ACL) {
+ msm_smd_tty_info[idx].acl_info = ch_info;
+ } else if (pinfo->flags & SMD_TTY_CMD) {
+ msm_smd_tty_info[idx].cmd_info = ch_info;
+ } else {
+ msm_smd_tty_info[idx].cmd_info =
+ msm_smd_tty_info[idx].acl_info = ch_info;
+ }
+
+ /* We use all here to prevent the maltiple port open
+ * blocking.
+ */
+ complete_all(&msm_smd_tty_info[pinfo->index].probe_done);
+
+ dev_set_drvdata(&sdev->dev, ch_info);
+ return 0;
+}
+
+static void qcom_smd_tty_remove(struct qcom_smd_device *sdev)
+{
+ struct msm_smd_channel_info *ch_info = dev_get_drvdata(&sdev->dev);
+
+ kfree(ch_info);
+ return;
+}
+
+static int qcom_smd_tty_callback(struct qcom_smd_device *sdev,
+ const void *data,
+ size_t count)
+{
+ unsigned char *ptr, *tmp;
+ int buf_len;
+ struct msm_smd_channel_info *ch_info;
+ struct tty_struct *tty;
+
+ ch_info = dev_get_drvdata(&sdev->dev);
+ tty = tty_port_tty_get(
+ &msm_smd_tty_info[ch_info->smd_tty_info_index].port);
+ if (!tty) {
+ pr_err("can't get tty for index: %d\n",
+ ch_info->smd_tty_info_index);
+ return 0;
+ }
+
+ if (ch_info->flags & SMD_DROP_ONE_TX_BYTE)
+ buf_len = count + 1;
+ else
+ buf_len = count;
+
+ if (buf_len > ch_info->channel->fifo_size)
+ buf_len = ch_info->channel->fifo_size;
+
+ buf_len = tty_prepare_flip_string(tty->port, &ptr, buf_len);
+ if (buf_len <= 0) {
+ pr_err("can't get flip buffer for tty index: %d\n",
+ ch_info->smd_tty_info_index);
+ tty_kref_put(tty);
+ return 0;
+ }
+
+ tmp = ptr;
+ if (ch_info->flags & SMD_TTY_ACL) {
+ *ptr++ = HCI_ACLDATA_PKT;
+ } else if (ch_info->flags & SMD_TTY_CMD) {
+ *ptr++ = HCI_EVENT_PKT;
+ }
+
+ memcpy_fromio(ptr, data, count);
+
+ tty_flip_buffer_push(tty->port);
+ tty_kref_put(tty);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_smd_tty_of_match[] = {
+ { .compatible = "qcom,hci-smd-bt-cmd"},
+ { .compatible = "qcom,hci-smd-bt-acl"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_smd_tty_of_match);
+
+static struct qcom_smd_driver qcom_smd_tty_driver = {
+ .probe = qcom_smd_tty_probe,
+ .remove = qcom_smd_tty_remove,
+ .callback = qcom_smd_tty_callback,
+ .driver = {
+ .name = "qcom_smd_tty",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_smd_tty_of_match,
+ },
+};
+
+static int smd_tty_open(struct tty_struct *tty, struct file *f)
+{
+ struct msm_smd_tty_info_s *info = msm_smd_tty_info + tty->index;
+
+ return tty_port_open(&info->port, tty, f);
+}
+
+static void smd_tty_close(struct tty_struct *tty, struct file *f)
+{
+ struct msm_smd_tty_info_s *info = tty->driver_data;
+
+ tty_port_close(&info->port, tty, f);
+}
+
+static int smd_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int len)
+{
+ int ret = 0;
+ int avail = len;
+ struct msm_smd_tty_info_s *info = tty->driver_data;
+ struct msm_smd_channel_info *cinfo;
+
+ if ((info->acl_info->flags & SMD_DROP_ONE_TX_BYTE) ||
+ (info->cmd_info->flags & SMD_DROP_ONE_TX_BYTE)) {
+ if (HCI_COMMAND_PKT == *buf)
+ cinfo = info->cmd_info;
+ else
+ cinfo = info->acl_info;
+ buf++;
+ avail--;
+ }
+
+ mutex_lock(&cinfo->lock);
+ ret = qcom_smd_send(cinfo->channel, buf, avail);
+ if (ret) {
+ pr_err("smd_tty_write failed on smd tty %d\n", tty->index);
+ }
+ mutex_unlock(&cinfo->lock);
+
+ return len;
+}
+
+static int smd_tty_port_activate(struct tty_port *tport,
+ struct tty_struct *tty)
+{
+ int ret = 0;
+ unsigned int n = tty->index;
+ struct msm_smd_tty_info_s *info;
+
+ if (n >= ARRAY_SIZE(msm_smd_port_info))
+ return -ENODEV;
+
+ info = &msm_smd_tty_info[n];
+
+ mutex_lock(&info->open_lock);
+ tty->driver_data = info;
+ if (info->opened) {
+ mutex_unlock(&info->open_lock);
+ return 0;
+ }
+
+ /* We'd better to add the channel probe for BT here.
+ * So the open will be blocked till channel is probe.
+ */
+ ret = wait_for_completion_interruptible_timeout(
+ &info->probe_done, msecs_to_jiffies(CHANNEL_PROBE_TIMEOUT));
+ if (ret == 0) {
+ pr_err("Wait for smd channel ready timeout\n");
+ info->opened--;
+ return -ETIMEDOUT;
+ } else if (ret < 0) {
+ pr_err("Error waiting for smd channel ready\n");
+ info->opened--;
+ return ret;
+ }
+
+ info->opened++;
+ mutex_unlock(&info->open_lock);
+
+ return 0;
+}
+
+static void smd_tty_port_shutdown(struct tty_port *tport)
+{
+ struct msm_smd_tty_info_s *info;
+ struct tty_struct *tty = tty_port_tty_get(tport);
+
+ info = tty->driver_data;
+
+ mutex_lock(&info->open_lock);
+ info->opened--;
+ if (info->opened > 0) {
+ mutex_unlock(&info->open_lock);
+ tty_kref_put(tty);
+ return;
+ }
+ mutex_unlock(&info->open_lock);
+ tty_kref_put(tty);
+ return;
+}
+
+static const struct tty_port_operations smd_tty_port_ops = {
+ .shutdown = smd_tty_port_shutdown,
+ .activate = smd_tty_port_activate,
+};
+
+static const struct tty_operations smd_tty_ops = {
+ .open = smd_tty_open,
+ .close = smd_tty_close,
+ .write = smd_tty_write,
+};
+
+static void smd_tty_device_init(int idx)
+{
+ struct tty_port *port;
+
+ port = &msm_smd_tty_info[idx].port;
+ tty_port_init(port);
+ mutex_init(&msm_smd_tty_info[idx].open_lock);
+ init_completion(&msm_smd_tty_info[idx].probe_done);
+
+ msm_smd_tty_info[idx].opened = 0;
+
+ port->ops = &smd_tty_port_ops;
+ msm_smd_tty_info[idx].tty = tty_port_register_device(port,
+ smd_tty_driver, idx, NULL);
+ return;
+}
+
+static int smd_tty_register_driver(void)
+{
+ int ret = 0;
+
+ smd_tty_driver = tty_alloc_driver(MAX_SMD_TTY_PORTS,
+ TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_RESET_TERMIOS);
+ if (IS_ERR(smd_tty_driver)) {
+ ret = PTR_ERR(smd_tty_driver);
+ return ret;
+ }
+
+ smd_tty_driver->owner = THIS_MODULE;
+ smd_tty_driver->driver_name = "smd_tty_driver";
+ smd_tty_driver->name = "smd";
+ smd_tty_driver->major = 0;
+ smd_tty_driver->minor_start = 0;
+ smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ smd_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ smd_tty_driver->init_termios = tty_std_termios;
+ smd_tty_driver->init_termios.c_iflag = 0;
+ smd_tty_driver->init_termios.c_oflag = 0;
+ smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+ smd_tty_driver->init_termios.c_lflag = 0;
+ tty_set_operations(smd_tty_driver, &smd_tty_ops);
+
+ ret = tty_register_driver(smd_tty_driver);
+ if (ret) {
+ put_tty_driver(smd_tty_driver);
+ pr_err("register tty driver failed (%d) for smd tty\n", ret);
+ }
+
+ return ret;
+}
+
+static int __init smd_tty_init(void)
+{
+ int ret = 0;
+ int i = 0;
+ ret = smd_tty_register_driver();
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(msm_smd_port_info); i++)
+ smd_tty_device_init(i);
+
+ qcom_smd_driver_register(&qcom_smd_tty_driver);
+
+ return 0;
+}
+
+module_init(smd_tty_init);