u_ctrl_hsuart: Add HSUART control module
RMNET driver can use this hsuart control module
for sending and receiving the control information.
CRs-Fixed: 355162
Change-Id: Ib7735ae0492ca80b0d41d5abb24aab569dfea86d
Signed-off-by: Chiranjeevi Velempati <cvelempa@codeaurora.org>
diff --git a/drivers/usb/gadget/u_ctrl_hsuart.c b/drivers/usb/gadget/u_ctrl_hsuart.c
new file mode 100644
index 0000000..7102d81
--- /dev/null
+++ b/drivers/usb/gadget/u_ctrl_hsuart.c
@@ -0,0 +1,576 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/termios.h>
+#include <linux/debugfs.h>
+#include <linux/smux.h>
+
+#include <mach/usb_gadget_xport.h>
+
+#define CH_OPENED 0
+#define CH_READY 1
+
+static unsigned int num_ctrl_ports;
+
+static const char *ghsuart_ctrl_names[] = {
+ "SMUX_RMNET_CTL_HSUART"
+};
+
+struct ghsuart_ctrl_port {
+ /* port */
+ unsigned port_num;
+ /* gadget */
+ enum gadget_type gtype;
+ spinlock_t port_lock;
+ void *port_usb;
+ /* work queue*/
+ struct workqueue_struct *wq;
+ struct work_struct connect_w;
+ struct work_struct disconnect_w;
+ /*ctrl pkt response cb*/
+ int (*send_cpkt_response)(void *g, void *buf, size_t len);
+ void *ctxt;
+ unsigned int ch_id;
+ /* flow control bits */
+ unsigned long flags;
+ int (*send_pkt)(void *, void *, size_t actual);
+ /* Channel status */
+ unsigned long channel_sts;
+ /* control bits */
+ unsigned cbits_tomodem;
+ /* counters */
+ unsigned long to_modem;
+ unsigned long to_host;
+ unsigned long drp_cpkt_cnt;
+};
+
+static struct {
+ struct ghsuart_ctrl_port *port;
+ struct platform_driver pdrv;
+} ghsuart_ctrl_ports[NUM_HSUART_PORTS];
+
+static int ghsuart_ctrl_receive(void *dev, void *buf, size_t actual);
+
+static void smux_control_event(void *priv, int event_type, const void *metadata)
+{
+ struct grmnet *gr = NULL;
+ struct ghsuart_ctrl_port *port = priv;
+ void *buf;
+ unsigned long flags;
+ size_t len;
+
+ switch (event_type) {
+ case SMUX_CONNECTED:
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ set_bit(CH_OPENED, &port->channel_sts);
+ if (port->gtype == USB_GADGET_RMNET) {
+ gr = port->port_usb;
+ if (gr && gr->connect)
+ gr->connect(gr);
+ }
+ break;
+ case SMUX_DISCONNECTED:
+ clear_bit(CH_OPENED, &port->channel_sts);
+ break;
+ case SMUX_READ_DONE:
+ len = ((struct smux_meta_read *)metadata)->len;
+ buf = ((struct smux_meta_read *)metadata)->buffer;
+ ghsuart_ctrl_receive(port, buf, len);
+ break;
+ case SMUX_READ_FAIL:
+ buf = ((struct smux_meta_read *)metadata)->buffer;
+ kfree(buf);
+ break;
+ case SMUX_WRITE_DONE:
+ case SMUX_WRITE_FAIL:
+ buf = ((struct smux_meta_write *)metadata)->buffer;
+ kfree(buf);
+ break;
+ case SMUX_LOW_WM_HIT:
+ case SMUX_HIGH_WM_HIT:
+ case SMUX_TIOCM_UPDATE:
+ break;
+ default:
+ pr_err("%s Event %d not supported\n", __func__, event_type);
+ };
+}
+
+static int rx_control_buffer(void *priv, void **pkt_priv, void **buffer,
+ int size)
+{
+ void *rx_buf;
+
+ rx_buf = kmalloc(size, GFP_KERNEL);
+ if (!rx_buf)
+ return -EAGAIN;
+ *buffer = rx_buf;
+ *pkt_priv = NULL;
+
+ return 0;
+}
+
+static int ghsuart_ctrl_receive(void *dev, void *buf, size_t actual)
+{
+ struct ghsuart_ctrl_port *port = dev;
+ int retval = 0;
+
+ pr_debug_ratelimited("%s: read complete bytes read: %d\n",
+ __func__, actual);
+
+ /* send it to USB here */
+ if (port && port->send_cpkt_response) {
+ retval = port->send_cpkt_response(port->port_usb, buf, actual);
+ port->to_host++;
+ }
+ kfree(buf);
+ return retval;
+}
+
+static int
+ghsuart_send_cpkt_tomodem(u8 portno, void *buf, size_t len)
+{
+ void *cbuf;
+ struct ghsuart_ctrl_port *port;
+ int ret;
+
+ if (portno >= num_ctrl_ports) {
+ pr_err("%s: Invalid portno#%d\n", __func__, portno);
+ return -ENODEV;
+ }
+
+ port = ghsuart_ctrl_ports[portno].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return -ENODEV;
+ }
+ /* drop cpkt if ch is not open */
+ if (!test_bit(CH_OPENED, &port->channel_sts)) {
+ port->drp_cpkt_cnt++;
+ return 0;
+ }
+ cbuf = kmalloc(len, GFP_ATOMIC);
+ if (!cbuf)
+ return -ENOMEM;
+
+ memcpy(cbuf, buf, len);
+
+ pr_debug("%s: ctrl_pkt:%d bytes\n", __func__, len);
+
+ ret = msm_smux_write(port->ch_id, port, (void *)cbuf, len);
+ if (ret < 0) {
+ pr_err_ratelimited("%s: write error:%d\n",
+ __func__, ret);
+ port->drp_cpkt_cnt++;
+ kfree(cbuf);
+ return ret;
+ }
+ port->to_modem++;
+
+ return 0;
+}
+
+static void
+ghsuart_send_cbits_tomodem(void *gptr, u8 portno, int cbits)
+{
+ struct ghsuart_ctrl_port *port;
+
+ if (portno >= num_ctrl_ports || !gptr) {
+ pr_err("%s: Invalid portno#%d\n", __func__, portno);
+ return;
+ }
+
+ port = ghsuart_ctrl_ports[portno].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return;
+ }
+
+ if (cbits == port->cbits_tomodem)
+ return;
+
+ port->cbits_tomodem = cbits;
+
+ if (!test_bit(CH_OPENED, &port->channel_sts))
+ return;
+
+ pr_debug("%s: ctrl_tomodem:%d\n", __func__, cbits);
+ /* Send the control bits to the Modem */
+ msm_smux_tiocm_set(port->ch_id, cbits, ~cbits);
+}
+
+static void ghsuart_ctrl_connect_w(struct work_struct *w)
+{
+ struct ghsuart_ctrl_port *port =
+ container_of(w, struct ghsuart_ctrl_port, connect_w);
+ int retval;
+
+ if (!port || !test_bit(CH_READY, &port->channel_sts))
+ return;
+
+ pr_debug("%s: port:%p\n", __func__, port);
+
+ retval = msm_smux_open(port->ch_id, port->ctxt, smux_control_event,
+ rx_control_buffer);
+ if (retval < 0) {
+ pr_err(" %s smux_open failed\n", __func__);
+ return;
+ }
+
+}
+
+int ghsuart_ctrl_connect(void *gptr, int port_num)
+{
+ struct ghsuart_ctrl_port *port;
+ struct grmnet *gr;
+ unsigned long flags;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ if (port_num > num_ctrl_ports || !gptr) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return -ENODEV;
+ }
+
+ port = ghsuart_ctrl_ports[port_num].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ gr = gptr;
+ port->send_cpkt_response = gr->send_cpkt_response;
+ gr->send_encap_cmd = ghsuart_send_cpkt_tomodem;
+ gr->notify_modem = ghsuart_send_cbits_tomodem;
+
+ port->port_usb = gptr;
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->drp_cpkt_cnt = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ if (test_bit(CH_READY, &port->channel_sts))
+ queue_work(port->wq, &port->connect_w);
+
+ return 0;
+}
+
+static void ghsuart_ctrl_disconnect_w(struct work_struct *w)
+{
+ struct ghsuart_ctrl_port *port =
+ container_of(w, struct ghsuart_ctrl_port, disconnect_w);
+
+ if (!test_bit(CH_OPENED, &port->channel_sts))
+ return;
+
+ msm_smux_close(port->ch_id);
+ clear_bit(CH_OPENED, &port->channel_sts);
+}
+
+void ghsuart_ctrl_disconnect(void *gptr, int port_num)
+{
+ struct gctrl_port *port;
+ struct grmnet *gr = NULL;
+ unsigned long flags;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ if (port_num > num_ctrl_ports) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return;
+ }
+
+ port = gctrl_ports[port_num].port;
+
+ if (!gptr || !port) {
+ pr_err("%s: grmnet port is null\n", __func__);
+ return;
+ }
+
+ gr = gptr;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ gr->send_encap_cmd = 0;
+ gr->notify_modem = 0;
+ port->cbits_tomodem = 0;
+ port->port_usb = 0;
+ port->send_cpkt_response = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->disconnect_w);
+}
+
+static int ghsuart_ctrl_probe(struct platform_device *pdev)
+{
+ struct ghsuart_ctrl_port *port;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s\n", __func__, pdev->name);
+
+ port = ghsuart_ctrl_ports[pdev->id].port;
+ set_bit(CH_READY, &port->channel_sts);
+
+ /* if usb is online, start read */
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb)
+ queue_work(port->wq, &port->connect_w);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return 0;
+}
+
+static int ghsuart_ctrl_remove(struct platform_device *pdev)
+{
+ struct ghsuart_ctrl_port *port;
+ struct grmnet *gr = NULL;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s\n", __func__, pdev->name);
+
+ port = ghsuart_ctrl_ports[pdev->id].port;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ goto not_ready;
+ }
+
+ gr = port->port_usb;
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ if (gr && gr->disconnect)
+ gr->disconnect(gr);
+
+ clear_bit(CH_OPENED, &port->channel_sts);
+not_ready:
+ clear_bit(CH_READY, &port->channel_sts);
+
+ return 0;
+}
+
+static void ghsuart_ctrl_port_free(int portno)
+{
+ struct ghsuart_ctrl_port *port = ghsuart_ctrl_ports[portno].port;
+ struct platform_driver *pdrv = &gctrl_ports[portno].pdrv;
+
+ destroy_workqueue(port->wq);
+ if (pdrv)
+ platform_driver_unregister(pdrv);
+ kfree(port);
+}
+
+static int ghsuart_ctrl_port_alloc(int portno, enum gadget_type gtype)
+{
+ struct ghsuart_ctrl_port *port;
+ struct platform_driver *pdrv;
+ int err;
+
+ port = kzalloc(sizeof(struct ghsuart_ctrl_port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->wq = create_singlethread_workqueue(ghsuart_ctrl_names[portno]);
+ if (!port->wq) {
+ pr_err("%s: Unable to create workqueue:%s\n",
+ __func__, ghsuart_ctrl_names[portno]);
+ kfree(port);
+ return -ENOMEM;
+ }
+
+ port->port_num = portno;
+ port->gtype = gtype;
+
+ spin_lock_init(&port->port_lock);
+
+ INIT_WORK(&port->connect_w, ghsuart_ctrl_connect_w);
+ INIT_WORK(&port->disconnect_w, ghsuart_ctrl_disconnect_w);
+
+ port->ch_id = SMUX_USB_RMNET_CTL_0;
+ port->ctxt = port;
+ port->send_pkt = ghsuart_ctrl_receive;
+ ghsuart_ctrl_ports[portno].port = port;
+
+ pdrv = &ghsuart_ctrl_ports[portno].pdrv;
+ pdrv->probe = ghsuart_ctrl_probe;
+ pdrv->remove = ghsuart_ctrl_remove;
+ pdrv->driver.name = ghsuart_ctrl_names[portno];
+ pdrv->driver.owner = THIS_MODULE;
+
+ err = platform_driver_register(pdrv);
+ if (unlikely(err < 0))
+ return err;
+ pr_debug("%s: port:%p portno:%d\n", __func__, port, portno);
+
+ return 0;
+}
+
+int ghsuart_ctrl_setup(unsigned int num_ports, enum gadget_type gtype)
+{
+ int first_port_id = num_ctrl_ports;
+ int total_num_ports = num_ports + num_ctrl_ports;
+ int i;
+ int ret = 0;
+
+ if (!num_ports || total_num_ports > NUM_HSUART_PORTS) {
+ pr_err("%s: Invalid num of ports count:%d\n",
+ __func__, num_ports);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: requested ports:%d\n", __func__, num_ports);
+
+ for (i = first_port_id; i < (first_port_id + num_ports); i++) {
+
+ num_ctrl_ports++;
+ ret = ghsuart_ctrl_port_alloc(i, gtype);
+ if (ret) {
+ num_ctrl_ports--;
+ pr_err("%s: Unable to alloc port:%d\n", __func__, i);
+ goto free_ports;
+ }
+ }
+
+ return first_port_id;
+
+free_ports:
+ for (i = first_port_id; i < num_ctrl_ports; i++)
+ ghsuart_ctrl_port_free(i);
+ num_ctrl_ports = first_port_id;
+ return ret;
+}
+
+#define DEBUG_BUF_SIZE 1024
+static ssize_t ghsuart_ctrl_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct ghsuart_ctrl_port *port;
+ char *buf;
+ unsigned long flags;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < num_ctrl_ports; i++) {
+ port = ghsuart_ctrl_ports[i].port;
+ if (!port)
+ continue;
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "#PORT:%d port: %p\n"
+ "to_usbhost: %lu\n"
+ "to_modem: %lu\n"
+ "cpkt_drp_cnt: %lu\n"
+ "DTR: %s\n",
+ i, port,
+ port->to_host, port->to_modem,
+ port->drp_cpkt_cnt,
+ port->cbits_tomodem ? "HIGH" : "LOW");
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t ghsuart_ctrl_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct ghsuart_ctrl_port *port;
+ int i;
+ unsigned long flags;
+
+ for (i = 0; i < num_ctrl_ports; i++) {
+ port = ghsuart_ctrl_ports[i].port;
+ if (!port)
+ continue;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->drp_cpkt_cnt = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+ return count;
+}
+
+static const struct file_operations ghsuart_ctrl_stats_ops = {
+ .read = ghsuart_ctrl_read_stats,
+ .write = ghsuart_ctrl_reset_stats,
+};
+
+static struct dentry *ghsuart_ctrl_dent;
+static int ghsuart_ctrl_debugfs_init(void)
+{
+ struct dentry *ghsuart_ctrl_dfile;
+
+ ghsuart_ctrl_dent = debugfs_create_dir("ghsuart_ctrl_xport", 0);
+ if (!ghsuart_ctrl_dent || IS_ERR(ghsuart_ctrl_dent))
+ return -ENODEV;
+
+ ghsuart_ctrl_dfile =
+ debugfs_create_file("status", S_IRUGO | S_IWUSR,
+ ghsuart_ctrl_dent, 0, &gctrl_stats_ops);
+ if (!ghsuart_ctrl_dfile || IS_ERR(ghsuart_ctrl_dfile)) {
+ debugfs_remove(ghsuart_ctrl_dent);
+ ghsuart_ctrl_dent = NULL;
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void ghsuart_ctrl_debugfs_exit(void)
+{
+ debugfs_remove_recursive(ghsuart_ctrl_dent);
+}
+
+static int __init ghsuart_ctrl_init(void)
+{
+ int ret;
+
+ ret = ghsuart_ctrl_debugfs_init();
+ if (ret) {
+ pr_debug("mode debugfs file is not available\n");
+ return ret;
+ }
+ return 0;
+}
+module_init(ghsuart_ctrl_init);
+
+static void __exit ghsuart_ctrl_exit(void)
+{
+ ghsuart_ctrl_debugfs_exit();
+}
+module_exit(ghsuart_ctrl_exit);
+
+MODULE_DESCRIPTION("HSUART control xport for RmNet");
+MODULE_LICENSE("GPL v2");