Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / gdm724x / gdm_mux.c
diff --git a/kernel/drivers/staging/gdm724x/gdm_mux.c b/kernel/drivers/staging/gdm724x/gdm_mux.c
new file mode 100644 (file)
index 0000000..1cf24e4
--- /dev/null
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/usb/cdc.h>
+
+#include "gdm_mux.h"
+
+static struct workqueue_struct *mux_rx_wq;
+
+static u16 packet_type[TTY_MAX_COUNT] = {0xF011, 0xF010};
+
+#define USB_DEVICE_CDC_DATA(vid, pid) \
+       .match_flags = \
+               USB_DEVICE_ID_MATCH_DEVICE |\
+               USB_DEVICE_ID_MATCH_INT_CLASS |\
+               USB_DEVICE_ID_MATCH_INT_SUBCLASS,\
+       .idVendor = vid,\
+       .idProduct = pid,\
+       .bInterfaceClass = USB_CLASS_COMM,\
+       .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM
+
+static const struct usb_device_id id_table[] = {
+       { USB_DEVICE_CDC_DATA(0x1076, 0x8000) }, /* GCT GDM7240 */
+       { USB_DEVICE_CDC_DATA(0x1076, 0x8f00) }, /* GCT GDM7243 */
+       { USB_DEVICE_CDC_DATA(0x1076, 0x9000) }, /* GCT GDM7243 */
+       { USB_DEVICE_CDC_DATA(0x1d74, 0x2300) }, /* LGIT Phoenix */
+       {}
+};
+
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int packet_type_to_index(u16 packetType)
+{
+       int i;
+
+       for (i = 0; i < TTY_MAX_COUNT; i++) {
+               if (packet_type[i] == packetType)
+                       return i;
+       }
+
+       return -1;
+}
+
+static struct mux_tx *alloc_mux_tx(int len)
+{
+       struct mux_tx *t = NULL;
+
+       t = kzalloc(sizeof(struct mux_tx), GFP_ATOMIC);
+       if (!t)
+               return NULL;
+
+       t->urb = usb_alloc_urb(0, GFP_ATOMIC);
+       t->buf = kmalloc(MUX_TX_MAX_SIZE, GFP_ATOMIC);
+       if (!t->urb || !t->buf) {
+               usb_free_urb(t->urb);
+               kfree(t->buf);
+               kfree(t);
+               return NULL;
+       }
+
+       return t;
+}
+
+static void free_mux_tx(struct mux_tx *t)
+{
+       if (t) {
+               usb_free_urb(t->urb);
+               kfree(t->buf);
+               kfree(t);
+       }
+}
+
+static struct mux_rx *alloc_mux_rx(void)
+{
+       struct mux_rx *r = NULL;
+
+       r = kzalloc(sizeof(struct mux_rx), GFP_KERNEL);
+       if (!r)
+               return NULL;
+
+       r->urb = usb_alloc_urb(0, GFP_KERNEL);
+       r->buf = kmalloc(MUX_RX_MAX_SIZE, GFP_KERNEL);
+       if (!r->urb || !r->buf) {
+               usb_free_urb(r->urb);
+               kfree(r->buf);
+               kfree(r);
+               return NULL;
+       }
+
+       return r;
+}
+
+static void free_mux_rx(struct mux_rx *r)
+{
+       if (r) {
+               usb_free_urb(r->urb);
+               kfree(r->buf);
+               kfree(r);
+       }
+}
+
+static struct mux_rx *get_rx_struct(struct rx_cxt *rx)
+{
+       struct mux_rx *r;
+       unsigned long flags;
+
+       spin_lock_irqsave(&rx->free_list_lock, flags);
+
+       if (list_empty(&rx->rx_free_list)) {
+               spin_unlock_irqrestore(&rx->free_list_lock, flags);
+               return NULL;
+       }
+
+       r = list_entry(rx->rx_free_list.prev, struct mux_rx, free_list);
+       list_del(&r->free_list);
+
+       spin_unlock_irqrestore(&rx->free_list_lock, flags);
+
+       return r;
+}
+
+static void put_rx_struct(struct rx_cxt *rx, struct mux_rx *r)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&rx->free_list_lock, flags);
+       list_add_tail(&r->free_list, &rx->rx_free_list);
+       spin_unlock_irqrestore(&rx->free_list_lock, flags);
+}
+
+
+static int up_to_host(struct mux_rx *r)
+{
+       struct mux_dev *mux_dev = (struct mux_dev *)r->mux_dev;
+       struct mux_pkt_header *mux_header;
+       unsigned int start_flag;
+       unsigned int payload_size;
+       unsigned short packet_type;
+       int total_len;
+       u32 packet_size_sum = r->offset;
+       int index;
+       int ret = TO_HOST_INVALID_PACKET;
+       int len = r->len;
+
+       while (1) {
+               mux_header = (struct mux_pkt_header *)(r->buf +
+                                                      packet_size_sum);
+               start_flag = __le32_to_cpu(mux_header->start_flag);
+               payload_size = __le32_to_cpu(mux_header->payload_size);
+               packet_type = __le16_to_cpu(mux_header->packet_type);
+
+               if (start_flag != START_FLAG) {
+                       pr_err("invalid START_FLAG %x\n", start_flag);
+                       break;
+               }
+
+               total_len = ALIGN(MUX_HEADER_SIZE + payload_size, 4);
+
+               if (len - packet_size_sum <
+                       total_len) {
+                       pr_err("invalid payload : %d %d %04x\n",
+                              payload_size, len, packet_type);
+                       break;
+               }
+
+               index = packet_type_to_index(packet_type);
+               if (index < 0) {
+                       pr_err("invalid index %d\n", index);
+                       break;
+               }
+
+               ret = r->callback(mux_header->data,
+                               payload_size,
+                               index,
+                               mux_dev->tty_dev,
+                               RECV_PACKET_PROCESS_CONTINUE
+                               );
+               if (ret == TO_HOST_BUFFER_REQUEST_FAIL) {
+                       r->offset += packet_size_sum;
+                       break;
+               }
+
+               packet_size_sum += total_len;
+               if (len - packet_size_sum <= MUX_HEADER_SIZE + 2) {
+                       ret = r->callback(NULL,
+                                       0,
+                                       index,
+                                       mux_dev->tty_dev,
+                                       RECV_PACKET_PROCESS_COMPLETE
+                                       );
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static void do_rx(struct work_struct *work)
+{
+       struct mux_dev *mux_dev =
+               container_of(work, struct mux_dev, work_rx.work);
+       struct mux_rx *r;
+       struct rx_cxt *rx = (struct rx_cxt *)&mux_dev->rx;
+       unsigned long flags;
+       int ret = 0;
+
+       while (1) {
+               spin_lock_irqsave(&rx->to_host_lock, flags);
+               if (list_empty(&rx->to_host_list)) {
+                       spin_unlock_irqrestore(&rx->to_host_lock, flags);
+                       break;
+               }
+               r = list_entry(rx->to_host_list.next, struct mux_rx,
+                              to_host_list);
+               list_del(&r->to_host_list);
+               spin_unlock_irqrestore(&rx->to_host_lock, flags);
+
+               ret = up_to_host(r);
+               if (ret == TO_HOST_BUFFER_REQUEST_FAIL)
+                       pr_err("failed to send mux data to host\n");
+               else
+                       put_rx_struct(rx, r);
+       }
+}
+
+static void remove_rx_submit_list(struct mux_rx *r, struct rx_cxt *rx)
+{
+       unsigned long flags;
+       struct mux_rx   *r_remove, *r_remove_next;
+
+       spin_lock_irqsave(&rx->submit_list_lock, flags);
+       list_for_each_entry_safe(r_remove, r_remove_next, &rx->rx_submit_list,
+                                rx_submit_list) {
+               if (r == r_remove)
+                       list_del(&r->rx_submit_list);
+       }
+       spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+}
+
+static void gdm_mux_rcv_complete(struct urb *urb)
+{
+       struct mux_rx *r = urb->context;
+       struct mux_dev *mux_dev = (struct mux_dev *)r->mux_dev;
+       struct rx_cxt *rx = &mux_dev->rx;
+       unsigned long flags;
+
+       remove_rx_submit_list(r, rx);
+
+       if (urb->status) {
+               if (mux_dev->usb_state == PM_NORMAL)
+                       dev_err(&urb->dev->dev, "%s: urb status error %d\n",
+                              __func__, urb->status);
+               put_rx_struct(rx, r);
+       } else {
+               r->len = r->urb->actual_length;
+               spin_lock_irqsave(&rx->to_host_lock, flags);
+               list_add_tail(&r->to_host_list, &rx->to_host_list);
+               queue_work(mux_rx_wq, &mux_dev->work_rx.work);
+               spin_unlock_irqrestore(&rx->to_host_lock, flags);
+       }
+}
+
+static int gdm_mux_recv(void *priv_dev, int (*cb)(void *data, int len,
+                       int tty_index, struct tty_dev *tty_dev, int complete))
+{
+       struct mux_dev *mux_dev = priv_dev;
+       struct usb_device *usbdev = mux_dev->usbdev;
+       struct mux_rx *r;
+       struct rx_cxt *rx = &mux_dev->rx;
+       unsigned long flags;
+       int ret;
+
+       if (!usbdev) {
+               pr_err("device is disconnected\n");
+               return -ENODEV;
+       }
+
+       r = get_rx_struct(rx);
+       if (!r) {
+               pr_err("get_rx_struct fail\n");
+               return -ENOMEM;
+       }
+
+       r->offset = 0;
+       r->mux_dev = (void *)mux_dev;
+       r->callback = cb;
+       mux_dev->rx_cb = cb;
+
+       usb_fill_bulk_urb(r->urb,
+                         usbdev,
+                         usb_rcvbulkpipe(usbdev, 0x86),
+                         r->buf,
+                         MUX_RX_MAX_SIZE,
+                         gdm_mux_rcv_complete,
+                         r);
+
+       spin_lock_irqsave(&rx->submit_list_lock, flags);
+       list_add_tail(&r->rx_submit_list, &rx->rx_submit_list);
+       spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+       ret = usb_submit_urb(r->urb, GFP_KERNEL);
+
+       if (ret) {
+               spin_lock_irqsave(&rx->submit_list_lock, flags);
+               list_del(&r->rx_submit_list);
+               spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+               put_rx_struct(rx, r);
+
+               pr_err("usb_submit_urb ret=%d\n", ret);
+       }
+
+       usb_mark_last_busy(usbdev);
+
+       return ret;
+}
+
+static void gdm_mux_send_complete(struct urb *urb)
+{
+       struct mux_tx *t = urb->context;
+
+       if (urb->status == -ECONNRESET) {
+               dev_info(&urb->dev->dev, "CONNRESET\n");
+               free_mux_tx(t);
+               return;
+       }
+
+       if (t->callback)
+               t->callback(t->cb_data);
+
+       free_mux_tx(t);
+}
+
+static int gdm_mux_send(void *priv_dev, void *data, int len, int tty_index,
+                       void (*cb)(void *data), void *cb_data)
+{
+       struct mux_dev *mux_dev = priv_dev;
+       struct usb_device *usbdev = mux_dev->usbdev;
+       struct mux_pkt_header *mux_header;
+       struct mux_tx *t = NULL;
+       static u32 seq_num = 1;
+       int total_len;
+       int ret;
+       unsigned long flags;
+
+       if (mux_dev->usb_state == PM_SUSPEND) {
+               ret = usb_autopm_get_interface(mux_dev->intf);
+               if (!ret)
+                       usb_autopm_put_interface(mux_dev->intf);
+       }
+
+       spin_lock_irqsave(&mux_dev->write_lock, flags);
+
+       total_len = ALIGN(MUX_HEADER_SIZE + len, 4);
+
+       t = alloc_mux_tx(total_len);
+       if (!t) {
+               pr_err("alloc_mux_tx fail\n");
+               spin_unlock_irqrestore(&mux_dev->write_lock, flags);
+               return -ENOMEM;
+       }
+
+       mux_header = (struct mux_pkt_header *)t->buf;
+       mux_header->start_flag = __cpu_to_le32(START_FLAG);
+       mux_header->seq_num = __cpu_to_le32(seq_num++);
+       mux_header->payload_size = __cpu_to_le32((u32)len);
+       mux_header->packet_type = __cpu_to_le16(packet_type[tty_index]);
+
+       memcpy(t->buf+MUX_HEADER_SIZE, data, len);
+       memset(t->buf+MUX_HEADER_SIZE+len, 0, total_len - MUX_HEADER_SIZE -
+              len);
+
+       t->len = total_len;
+       t->callback = cb;
+       t->cb_data = cb_data;
+
+       usb_fill_bulk_urb(t->urb,
+                         usbdev,
+                         usb_sndbulkpipe(usbdev, 5),
+                         t->buf,
+                         total_len,
+                         gdm_mux_send_complete,
+                         t);
+
+       ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+
+       spin_unlock_irqrestore(&mux_dev->write_lock, flags);
+
+       if (ret)
+               pr_err("usb_submit_urb Error: %d\n", ret);
+
+       usb_mark_last_busy(usbdev);
+
+       return ret;
+}
+
+static int gdm_mux_send_control(void *priv_dev, int request, int value,
+                               void *buf, int len)
+{
+       struct mux_dev *mux_dev = priv_dev;
+       struct usb_device *usbdev = mux_dev->usbdev;
+       int ret;
+
+       ret = usb_control_msg(usbdev,
+                             usb_sndctrlpipe(usbdev, 0),
+                             request,
+                             USB_RT_ACM,
+                             value,
+                             2,
+                             buf,
+                             len,
+                             5000
+                            );
+
+       if (ret < 0)
+               pr_err("usb_control_msg error: %d\n", ret);
+
+       return ret < 0 ? ret : 0;
+}
+
+static void release_usb(struct mux_dev *mux_dev)
+{
+       struct rx_cxt           *rx = &mux_dev->rx;
+       struct mux_rx           *r, *r_next;
+       unsigned long           flags;
+
+       cancel_delayed_work(&mux_dev->work_rx);
+
+       spin_lock_irqsave(&rx->submit_list_lock, flags);
+       list_for_each_entry_safe(r, r_next, &rx->rx_submit_list,
+                                rx_submit_list) {
+               spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+               usb_kill_urb(r->urb);
+               spin_lock_irqsave(&rx->submit_list_lock, flags);
+       }
+       spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+       spin_lock_irqsave(&rx->free_list_lock, flags);
+       list_for_each_entry_safe(r, r_next, &rx->rx_free_list, free_list) {
+               list_del(&r->free_list);
+               free_mux_rx(r);
+       }
+       spin_unlock_irqrestore(&rx->free_list_lock, flags);
+
+       spin_lock_irqsave(&rx->to_host_lock, flags);
+       list_for_each_entry_safe(r, r_next, &rx->to_host_list, to_host_list) {
+               if (r->mux_dev == (void *)mux_dev) {
+                       list_del(&r->to_host_list);
+                       free_mux_rx(r);
+               }
+       }
+       spin_unlock_irqrestore(&rx->to_host_lock, flags);
+}
+
+
+static int init_usb(struct mux_dev *mux_dev)
+{
+       struct mux_rx *r;
+       struct rx_cxt *rx = &mux_dev->rx;
+       int ret = 0;
+       int i;
+
+       spin_lock_init(&mux_dev->write_lock);
+       INIT_LIST_HEAD(&rx->to_host_list);
+       INIT_LIST_HEAD(&rx->rx_submit_list);
+       INIT_LIST_HEAD(&rx->rx_free_list);
+       spin_lock_init(&rx->to_host_lock);
+       spin_lock_init(&rx->submit_list_lock);
+       spin_lock_init(&rx->free_list_lock);
+
+       for (i = 0; i < MAX_ISSUE_NUM * 2; i++) {
+               r = alloc_mux_rx();
+               if (r == NULL) {
+                       ret = -ENOMEM;
+                       break;
+               }
+
+               list_add(&r->free_list, &rx->rx_free_list);
+       }
+
+       INIT_DELAYED_WORK(&mux_dev->work_rx, do_rx);
+
+       return ret;
+}
+
+static int gdm_mux_probe(struct usb_interface *intf,
+                        const struct usb_device_id *id)
+{
+       struct mux_dev *mux_dev;
+       struct tty_dev *tty_dev;
+       u16 idVendor, idProduct;
+       int bInterfaceNumber;
+       int ret;
+       int i;
+       struct usb_device *usbdev = interface_to_usbdev(intf);
+
+       bInterfaceNumber = intf->cur_altsetting->desc.bInterfaceNumber;
+
+       idVendor = __le16_to_cpu(usbdev->descriptor.idVendor);
+       idProduct = __le16_to_cpu(usbdev->descriptor.idProduct);
+
+       pr_info("mux vid = 0x%04x pid = 0x%04x\n", idVendor, idProduct);
+
+       if (bInterfaceNumber != 2)
+               return -ENODEV;
+
+       mux_dev = kzalloc(sizeof(struct mux_dev), GFP_KERNEL);
+       if (!mux_dev)
+               return -ENOMEM;
+
+       tty_dev = kzalloc(sizeof(struct tty_dev), GFP_KERNEL);
+       if (!tty_dev) {
+               ret = -ENOMEM;
+               goto err_free_mux;
+       }
+
+       mux_dev->usbdev = usbdev;
+       mux_dev->control_intf = intf;
+
+       ret = init_usb(mux_dev);
+       if (ret)
+               goto err_free_usb;
+
+       tty_dev->priv_dev = (void *)mux_dev;
+       tty_dev->send_func = gdm_mux_send;
+       tty_dev->recv_func = gdm_mux_recv;
+       tty_dev->send_control = gdm_mux_send_control;
+
+       ret = register_lte_tty_device(tty_dev, &intf->dev);
+       if (ret)
+               goto err_unregister_tty;
+
+       for (i = 0; i < TTY_MAX_COUNT; i++)
+               mux_dev->tty_dev = tty_dev;
+
+       mux_dev->intf = intf;
+       mux_dev->usb_state = PM_NORMAL;
+
+       usb_get_dev(usbdev);
+       usb_set_intfdata(intf, tty_dev);
+
+       return 0;
+
+err_unregister_tty:
+       unregister_lte_tty_device(tty_dev);
+err_free_usb:
+       release_usb(mux_dev);
+       kfree(tty_dev);
+err_free_mux:
+       kfree(mux_dev);
+
+       return ret;
+}
+
+static void gdm_mux_disconnect(struct usb_interface *intf)
+{
+       struct tty_dev *tty_dev;
+       struct mux_dev *mux_dev;
+       struct usb_device *usbdev = interface_to_usbdev(intf);
+
+       tty_dev = usb_get_intfdata(intf);
+
+       mux_dev = tty_dev->priv_dev;
+
+       release_usb(mux_dev);
+       unregister_lte_tty_device(tty_dev);
+
+       kfree(mux_dev);
+       kfree(tty_dev);
+
+       usb_put_dev(usbdev);
+}
+
+static int gdm_mux_suspend(struct usb_interface *intf, pm_message_t pm_msg)
+{
+       struct tty_dev *tty_dev;
+       struct mux_dev *mux_dev;
+       struct rx_cxt *rx;
+       struct mux_rx *r, *r_next;
+       unsigned long flags;
+
+       tty_dev = usb_get_intfdata(intf);
+       mux_dev = tty_dev->priv_dev;
+       rx = &mux_dev->rx;
+
+       if (mux_dev->usb_state != PM_NORMAL) {
+               dev_err(intf->usb_dev, "usb suspend - invalid state\n");
+               return -1;
+       }
+
+       mux_dev->usb_state = PM_SUSPEND;
+
+
+       spin_lock_irqsave(&rx->submit_list_lock, flags);
+       list_for_each_entry_safe(r, r_next, &rx->rx_submit_list,
+                                rx_submit_list) {
+               spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+               usb_kill_urb(r->urb);
+               spin_lock_irqsave(&rx->submit_list_lock, flags);
+       }
+       spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+       return 0;
+}
+
+static int gdm_mux_resume(struct usb_interface *intf)
+{
+       struct tty_dev *tty_dev;
+       struct mux_dev *mux_dev;
+       u8 i;
+
+       tty_dev = usb_get_intfdata(intf);
+       mux_dev = tty_dev->priv_dev;
+
+       if (mux_dev->usb_state != PM_SUSPEND) {
+               dev_err(intf->usb_dev, "usb resume - invalid state\n");
+               return -1;
+       }
+
+       mux_dev->usb_state = PM_NORMAL;
+
+       for (i = 0; i < MAX_ISSUE_NUM; i++)
+               gdm_mux_recv(mux_dev, mux_dev->rx_cb);
+
+       return 0;
+}
+
+static struct usb_driver gdm_mux_driver = {
+       .name = "gdm_mux",
+       .probe = gdm_mux_probe,
+       .disconnect = gdm_mux_disconnect,
+       .id_table = id_table,
+       .supports_autosuspend = 1,
+       .suspend = gdm_mux_suspend,
+       .resume = gdm_mux_resume,
+       .reset_resume = gdm_mux_resume,
+};
+
+static int __init gdm_usb_mux_init(void)
+{
+
+       mux_rx_wq = create_workqueue("mux_rx_wq");
+       if (mux_rx_wq == NULL) {
+               pr_err("work queue create fail\n");
+               return -1;
+       }
+
+       register_lte_tty_driver();
+
+       return usb_register(&gdm_mux_driver);
+}
+
+static void __exit gdm_usb_mux_exit(void)
+{
+       unregister_lte_tty_driver();
+
+       if (mux_rx_wq) {
+               flush_workqueue(mux_rx_wq);
+               destroy_workqueue(mux_rx_wq);
+       }
+
+       usb_deregister(&gdm_mux_driver);
+}
+
+module_init(gdm_usb_mux_init);
+module_exit(gdm_usb_mux_exit);
+
+MODULE_DESCRIPTION("GCT LTE TTY Device Driver");
+MODULE_LICENSE("GPL");