From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/gdm724x/gdm_mux.c | 691 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 691 insertions(+) create mode 100644 drivers/staging/gdm724x/gdm_mux.c (limited to 'drivers/staging/gdm724x/gdm_mux.c') diff --git a/drivers/staging/gdm724x/gdm_mux.c b/drivers/staging/gdm724x/gdm_mux.c new file mode 100644 index 000000000..1cf24e4ed --- /dev/null +++ b/drivers/staging/gdm724x/gdm_mux.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v1.2.3-54-g00ecf