/*
 * u_sdio.c - utilities for USB gadget serial over sdio
 *
 * This code also borrows from drivers/usb/gadget/u_serial.c, which is
 * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
 * Copyright (C) 2008 David Brownell
 * Copyright (C) 2008 by Nokia Corporation
 * Copyright (c) 2011, The Linux Foundation. All rights reserved.
 *
 * This program from The Linux Foundation is free software; you can
 * redistribute it and/or modify it under the GNU General Public License
 * version 2 and only version 2 as published by the Free Software Foundation.
 * The original work available from [kernel.org] is subject to the notice below.
 *
 * This software is distributed under the terms of the GNU General
 * Public License ("GPL") as published by the Free Software Foundation,
 * either version 2 of that License or (at your option) any later version.
 *
 */

#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 <mach/sdio_al.h>
#include <mach/sdio_cmux.h>
#include "u_serial.h"

#define SDIO_RX_QUEUE_SIZE		8
#define SDIO_RX_BUF_SIZE		2048

#define SDIO_TX_QUEUE_SIZE		8
#define SDIO_TX_BUF_SIZE		2048

/* 1 - DUN, 2-NMEA/GPS */
#define SDIO_N_PORTS	2
static struct sdio_portmaster {
	struct mutex lock;
	struct gsdio_port *port;
	struct platform_driver gsdio_ch;
} sdio_ports[SDIO_N_PORTS];
static unsigned n_sdio_ports;

struct sdio_port_info {
	/* data channel info */
	char *data_ch_name;
	struct sdio_channel *ch;

	/* control channel info */
	int ctrl_ch_id;
};

struct sdio_port_info sport_info[SDIO_N_PORTS] = {
	{
		.data_ch_name = "SDIO_DUN",
		.ctrl_ch_id = 9,
	},
	{
		.data_ch_name = "SDIO_NMEA",
		.ctrl_ch_id = 10,
	},
};

static struct workqueue_struct *gsdio_wq;

struct gsdio_port {
	unsigned			port_num;
	spinlock_t			port_lock;

	unsigned			n_read;
	struct list_head		read_pool;
	struct list_head		read_queue;
	struct work_struct		push;
	unsigned long			rp_len;
	unsigned long			rq_len;

	struct list_head		write_pool;
	struct work_struct		pull;
	unsigned long			wp_len;

	struct work_struct		notify_modem;

	struct gserial			*port_usb;
	struct usb_cdc_line_coding	line_coding;

	int				sdio_open;
	int				sdio_probe;
	int				ctrl_ch_err;
	struct sdio_port_info		*sport_info;
	struct delayed_work		sdio_open_work;

#define SDIO_ACM_CTRL_RI		(1 << 3)
#define SDIO_ACM_CTRL_DSR		(1 << 1)
#define SDIO_ACM_CTRL_DCD		(1 << 0)
	int				cbits_to_laptop;

#define SDIO_ACM_CTRL_RTS	(1 << 1)	/* unused with full duplex */
#define SDIO_ACM_CTRL_DTR	(1 << 0)	/* host is ready for data r/w */
	int				cbits_to_modem;

	/* pkt logging */
	unsigned long			nbytes_tolaptop;
	unsigned long			nbytes_tomodem;
};

void gsdio_free_req(struct usb_ep *ep, struct usb_request *req)
{
	kfree(req->buf);
	usb_ep_free_request(ep, req);
}

struct usb_request *
gsdio_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags)
{
	struct usb_request *req;

	req = usb_ep_alloc_request(ep, flags);
	if (!req) {
		pr_err("%s: usb alloc request failed\n", __func__);
		return NULL;
	}

	req->length = len;
	req->buf = kmalloc(len, flags);
	if (!req->buf) {
		pr_err("%s: request buf allocation failed\n", __func__);
		usb_ep_free_request(ep, req);
		return NULL;
	}

	return req;
}

void gsdio_free_requests(struct usb_ep *ep, struct list_head *head)
{
	struct usb_request	*req;

	while (!list_empty(head)) {
		req = list_entry(head->next, struct usb_request, list);
		list_del(&req->list);
		gsdio_free_req(ep, req);
	}
}

int gsdio_alloc_requests(struct usb_ep *ep, struct list_head *head,
		int num, int size,
		void (*cb)(struct usb_ep *ep, struct usb_request *))
{
	int i;
	struct usb_request *req;

	pr_debug("%s: ep:%p head:%p num:%d size:%d cb:%p", __func__,
			ep, head, num, size, cb);

	for (i = 0; i < num; i++) {
		req = gsdio_alloc_req(ep, size, GFP_ATOMIC);
		if (!req) {
			pr_debug("%s: req allocated:%d\n", __func__, i);
			return list_empty(head) ? -ENOMEM : 0;
		}
		req->complete = cb;
		list_add(&req->list, head);
	}

	return 0;
}

void gsdio_start_rx(struct gsdio_port *port)
{
	struct list_head	*pool;
	struct usb_ep		*out;
	int ret;

	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return;
	}

	pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num);

	spin_lock_irq(&port->port_lock);

	if (!port->port_usb) {
		pr_debug("%s: usb is disconnected\n", __func__);
		goto start_rx_end;
	}

	if (!port->sdio_open) {
		pr_debug("%s: sdio is not open\n", __func__);
		goto start_rx_end;
	}

	pool = &port->read_pool;
	out = port->port_usb->out;

	while (!list_empty(pool)) {
		struct usb_request	*req;

		req = list_entry(pool->next, struct usb_request, list);
		list_del(&req->list);
		req->length = SDIO_RX_BUF_SIZE;
		port->rp_len--;

		spin_unlock_irq(&port->port_lock);
		ret = usb_ep_queue(out, req, GFP_ATOMIC);
		spin_lock_irq(&port->port_lock);
		if (ret) {
			pr_err("%s: usb ep out queue failed"
					"port:%p, port#%d\n",
					__func__, port, port->port_num);
			list_add_tail(&req->list, pool);
			port->rp_len++;
			break;
		}

		/* usb could have disconnected while we released spin lock */
		if (!port->port_usb) {
			pr_debug("%s: usb is disconnected\n", __func__);
			goto start_rx_end;
		}
	}

start_rx_end:
	spin_unlock_irq(&port->port_lock);
}

int gsdio_write(struct gsdio_port *port, struct usb_request *req)
{
	unsigned	avail;
	char		*packet;
	unsigned	size = req->actual;
	unsigned	n;
	int		ret = 0;


	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return -ENODEV;
	}

	if (!req) {
		pr_err("%s: usb request is null port#%d\n",
				__func__, port->port_num);
		return -ENODEV;
	}

	pr_debug("%s: port:%p port#%d req:%p actual:%d n_read:%d\n",
			__func__, port, port->port_num, req,
			req->actual, port->n_read);

	if (!port->sdio_open) {
		pr_debug("%s: SDIO IO is not supported\n", __func__);
		return -ENODEV;
	}

	avail = sdio_write_avail(port->sport_info->ch);

	pr_debug("%s: sdio_write_avail:%d", __func__, avail);

	if (!avail)
		return -EBUSY;

	if (!req->actual) {
		pr_debug("%s: req->actual is already zero,update bytes read\n",
				__func__);
		port->n_read = 0;
		return -ENODEV;
	}

	packet = req->buf;
	n = port->n_read;
	if (n) {
		packet += n;
		size -= n;
	}

	if (size > avail)
		size = avail;

	spin_unlock_irq(&port->port_lock);
	ret = sdio_write(port->sport_info->ch, packet, size);
	spin_lock_irq(&port->port_lock);
	if (ret) {
		pr_err("%s: port#%d sdio write failed err:%d",
				__func__, port->port_num, ret);
		/* try again later */
		return ret;
	}

	port->nbytes_tomodem += size;

	if (size + n == req->actual)
		port->n_read = 0;
	else
		port->n_read += size;

	return ret;
}

void gsdio_rx_push(struct work_struct *w)
{
	struct gsdio_port *port = container_of(w, struct gsdio_port, push);
	struct list_head *q = &port->read_queue;
	struct usb_ep		*out;
	int ret;

	pr_debug("%s: port:%p port#%d read_queue:%p", __func__,
			port, port->port_num, q);

	spin_lock_irq(&port->port_lock);

	if (!port->port_usb) {
		pr_debug("%s: usb cable is disconencted\n", __func__);
		spin_unlock_irq(&port->port_lock);
		return;
	}

	out = port->port_usb->out;

	while (!list_empty(q)) {
		struct usb_request *req;

		req = list_first_entry(q, struct usb_request, list);

		switch (req->status) {
		case -ESHUTDOWN:
			pr_debug("%s: req status shutdown portno#%d port:%p",
					__func__, port->port_num, port);
			goto rx_push_end;
		default:
			pr_warning("%s: port:%p port#%d"
					" Unexpected Rx Status:%d\n", __func__,
					port, port->port_num, req->status);
			/* FALL THROUGH */
		case 0:
			/* normal completion */
			break;
		}

		if (!port->sdio_open) {
			pr_err("%s: sio channel is not open\n", __func__);
			list_move(&req->list, &port->read_pool);
			port->rp_len++;
			port->rq_len--;
			goto rx_push_end;
		}


		list_del(&req->list);
		port->rq_len--;

		ret = gsdio_write(port, req);
		/* as gsdio_write drops spin_lock while writing data
		 * to sdio usb cable may have been disconnected
		 */
		if (!port->port_usb) {
			port->n_read = 0;
			gsdio_free_req(out, req);
			spin_unlock_irq(&port->port_lock);
			return;
		}

		if (ret || port->n_read) {
			list_add(&req->list, &port->read_queue);
			port->rq_len++;
			goto rx_push_end;
		}

		list_add(&req->list, &port->read_pool);
		port->rp_len++;
	}

	if (port->sdio_open && !list_empty(q)) {
		if (sdio_write_avail(port->sport_info->ch))
			queue_work(gsdio_wq, &port->push);
	}
rx_push_end:
	spin_unlock_irq(&port->port_lock);

	/* start queuing out requests again to host */
	gsdio_start_rx(port);
}

void gsdio_read_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct gsdio_port *port = ep->driver_data;
	unsigned long flags;

	pr_debug("%s: ep:%p port:%p\n", __func__, ep, port);

	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return;
	}

	spin_lock_irqsave(&port->port_lock, flags);
	list_add_tail(&req->list, &port->read_queue);
	port->rq_len++;
	queue_work(gsdio_wq, &port->push);
	spin_unlock_irqrestore(&port->port_lock, flags);

	return;
}

void gsdio_write_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct gsdio_port *port = ep->driver_data;
	unsigned long flags;

	pr_debug("%s: ep:%p port:%p\n", __func__, ep, port);

	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return;
	}

	spin_lock_irqsave(&port->port_lock, flags);
	list_add(&req->list, &port->write_pool);
	port->wp_len++;

	switch (req->status) {
	default:
		pr_warning("%s: port:%p port#%d unexpected %s status %d\n",
				__func__, port, port->port_num,
				ep->name, req->status);
		/* FALL THROUGH */
	case 0:
		queue_work(gsdio_wq, &port->pull);
		break;

	case -ESHUTDOWN:
		/* disconnect */
		pr_debug("%s: %s shutdown\n", __func__, ep->name);
		break;
	}

	spin_unlock_irqrestore(&port->port_lock, flags);

	return;
}

void gsdio_read_pending(struct gsdio_port *port)
{
	struct sdio_channel *ch;
	char buf[1024];
	int avail;

	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return;
	}

	ch = port->sport_info->ch;

	if (!ch)
		return;

	while ((avail = sdio_read_avail(ch))) {
		if (avail > 1024)
			avail = 1024;
		sdio_read(ch, buf, avail);

		pr_debug("%s: flushed out %d bytes\n", __func__, avail);
	}
}

void gsdio_tx_pull(struct work_struct *w)
{
	struct gsdio_port *port = container_of(w, struct gsdio_port, pull);
	struct list_head *pool = &port->write_pool;

	pr_debug("%s: port:%p port#%d pool:%p\n", __func__,
			port, port->port_num, pool);

	if (!port->port_usb) {
		pr_err("%s: usb disconnected\n", __func__);

		/* take out all the pending data from sdio */
		gsdio_read_pending(port);

		return;
	}

	spin_lock_irq(&port->port_lock);

	while (!list_empty(pool)) {
		int avail;
		struct usb_ep *in = port->port_usb->in;
		struct sdio_channel *ch = port->sport_info->ch;
		struct usb_request *req;
		unsigned len = SDIO_TX_BUF_SIZE;
		int ret;


		req = list_entry(pool->next, struct usb_request, list);

		if (!port->sdio_open) {
			pr_debug("%s: SDIO channel is not open\n", __func__);
			goto tx_pull_end;
		}

		avail = sdio_read_avail(ch);
		if (!avail) {
			/* REVISIT: for ZLP */
			pr_debug("%s: read_avail:%d port:%p port#%d\n",
					__func__, avail, port, port->port_num);
			goto tx_pull_end;
		}

		if (avail > len)
			avail = len;

		list_del(&req->list);
		port->wp_len--;

		spin_unlock_irq(&port->port_lock);
		ret = sdio_read(ch, req->buf, avail);
		spin_lock_irq(&port->port_lock);
		if (ret) {
			pr_err("%s: port:%p port#%d sdio read failed err:%d",
					__func__, port, port->port_num, ret);

			/* check if usb is still active */
			if (!port->port_usb) {
				gsdio_free_req(in, req);
			} else {
				list_add(&req->list, pool);
				port->wp_len++;
			}
			goto tx_pull_end;
		}

		req->length = avail;

		spin_unlock_irq(&port->port_lock);
		ret = usb_ep_queue(in, req, GFP_KERNEL);
		spin_lock_irq(&port->port_lock);
		if (ret) {
			pr_err("%s: usb ep out queue failed"
					"port:%p, port#%d err:%d\n",
					__func__, port, port->port_num, ret);

			/* could be usb disconnected */
			if (!port->port_usb) {
				gsdio_free_req(in, req);
			} else {
				list_add(&req->list, pool);
				port->wp_len++;
			}
			goto tx_pull_end;
		}

		port->nbytes_tolaptop += avail;
	}
tx_pull_end:
	spin_unlock_irq(&port->port_lock);
}

int gsdio_start_io(struct gsdio_port *port)
{
	int			ret;
	unsigned long		flags;

	pr_debug("%s:\n", __func__);

	spin_lock_irqsave(&port->port_lock, flags);

	if (!port->port_usb) {
		spin_unlock_irqrestore(&port->port_lock, flags);
		return -ENODEV;
	}

	/* start usb out queue */
	ret = gsdio_alloc_requests(port->port_usb->out,
				&port->read_pool,
				SDIO_RX_QUEUE_SIZE, SDIO_RX_BUF_SIZE,
				gsdio_read_complete);
	if (ret) {
		spin_unlock_irqrestore(&port->port_lock, flags);
		pr_err("%s: unable to allocate out reqs\n", __func__);
		return ret;
	}
	port->rp_len = SDIO_RX_QUEUE_SIZE;

	ret = gsdio_alloc_requests(port->port_usb->in,
				&port->write_pool,
				SDIO_TX_QUEUE_SIZE, SDIO_TX_BUF_SIZE,
				gsdio_write_complete);
	if (ret) {
		gsdio_free_requests(port->port_usb->out, &port->read_pool);
		port->rp_len = 0;
		spin_unlock_irqrestore(&port->port_lock, flags);
		pr_err("%s: unable to allocate in reqs\n", __func__);
		return ret;
	}
	port->wp_len = SDIO_TX_QUEUE_SIZE;
	spin_unlock_irqrestore(&port->port_lock, flags);

	gsdio_start_rx(port);
	queue_work(gsdio_wq, &port->pull);

	return 0;
}

void gsdio_port_free(unsigned portno)
{
	struct gsdio_port *port = sdio_ports[portno].port;
	struct platform_driver *pdriver = &sdio_ports[portno].gsdio_ch;

	if (!port) {
		pr_err("%s: invalid portno#%d\n", __func__, portno);
		return;
	}

	platform_driver_unregister(pdriver);

	kfree(port);
}

void gsdio_ctrl_wq(struct work_struct *w)
{
	struct gsdio_port *port;

	port = container_of(w, struct gsdio_port, notify_modem);

	if (!port) {
		pr_err("%s: port is null\n", __func__);
		return;
	}

	if (!port->sdio_open || port->ctrl_ch_err)
		return;

	sdio_cmux_tiocmset(port->sport_info->ctrl_ch_id,
			port->cbits_to_modem, ~(port->cbits_to_modem));
}

void gsdio_ctrl_notify_modem(void *gptr, u8 portno, int ctrl_bits)
{
	struct gsdio_port *port;
	int temp;
	struct gserial *gser = gptr;

	if (portno >= n_sdio_ports) {
		pr_err("%s: invalid portno#%d\n", __func__, portno);
		return;
	}

	if (!gser) {
		pr_err("%s: gser is null\n", __func__);
		return;
	}

	port = sdio_ports[portno].port;

	temp = ctrl_bits & SDIO_ACM_CTRL_DTR ? TIOCM_DTR : 0;

	if (port->cbits_to_modem == temp)
		return;

	 port->cbits_to_modem = temp;

	/* TIOCM_DTR - 0x002 - bit(1) */
	pr_debug("%s: port:%p port#%d ctrl_bits:%08x\n", __func__,
		port, port->port_num, ctrl_bits);

	if (!port->sdio_open) {
		pr_err("%s: port:%p port#%d sdio not connected\n",
				__func__, port, port->port_num);
		return;
	}

	/* whenever DTR is high let laptop know that modem status */
	if (port->cbits_to_modem && gser->send_modem_ctrl_bits)
		gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);

	queue_work(gsdio_wq, &port->notify_modem);
}

void gsdio_ctrl_modem_status(int ctrl_bits, void *_dev)
{
	struct gsdio_port *port = _dev;

	/* TIOCM_CD - 0x040 - bit(6)
	 * TIOCM_RI - 0x080 - bit(7)
	 * TIOCM_DSR- 0x100 - bit(8)
	 */
	pr_debug("%s: port:%p port#%d event:%08x\n", __func__,
		port, port->port_num, ctrl_bits);

	port->cbits_to_laptop = 0;
	ctrl_bits &= TIOCM_RI | TIOCM_CD | TIOCM_DSR;
	if (ctrl_bits & TIOCM_RI)
		port->cbits_to_laptop |= SDIO_ACM_CTRL_RI;
	if (ctrl_bits & TIOCM_CD)
		port->cbits_to_laptop |= SDIO_ACM_CTRL_DCD;
	if (ctrl_bits & TIOCM_DSR)
		port->cbits_to_laptop |= SDIO_ACM_CTRL_DSR;

	if (port->port_usb && port->port_usb->send_modem_ctrl_bits)
		port->port_usb->send_modem_ctrl_bits(port->port_usb,
					port->cbits_to_laptop);
}

void gsdio_ch_notify(void *_dev, unsigned event)
{
	struct gsdio_port *port = _dev;

	pr_debug("%s: port:%p port#%d event:%s\n", __func__,
		port, port->port_num,
		event == 1 ? "READ AVAIL" : "WRITE_AVAIL");

	if (event == SDIO_EVENT_DATA_WRITE_AVAIL)
		queue_work(gsdio_wq, &port->push);
	if (event == SDIO_EVENT_DATA_READ_AVAIL)
		queue_work(gsdio_wq, &port->pull);
}

static void gsdio_open_work(struct work_struct *w)
{
	struct gsdio_port *port =
			container_of(w, struct gsdio_port, sdio_open_work.work);
	struct sdio_port_info *pi = port->sport_info;
	struct gserial *gser;
	int ret;
	int ctrl_bits;
	int startio;

	ret = sdio_open(pi->data_ch_name, &pi->ch, port, gsdio_ch_notify);
	if (ret) {
		pr_err("%s: port:%p port#%d unable to open sdio ch:%s\n",
				__func__, port, port->port_num,
				pi->data_ch_name);
		return;
	}

	port->ctrl_ch_err = 0;
	ret = sdio_cmux_open(pi->ctrl_ch_id, 0, 0,
			gsdio_ctrl_modem_status, port);
	if (ret) {
		pr_err("%s: port:%p port#%d unable to open ctrl ch:%d\n",
				__func__, port, port->port_num, pi->ctrl_ch_id);
		port->ctrl_ch_err = 1;
	}

	/* check for latest status update from modem */
	if (!port->ctrl_ch_err) {
		ctrl_bits = sdio_cmux_tiocmget(pi->ctrl_ch_id);
		gsdio_ctrl_modem_status(ctrl_bits, port);
	}

	pr_debug("%s: SDIO data:%s ctrl:%d are open\n", __func__,
					pi->data_ch_name,
					pi->ctrl_ch_id);

	port->sdio_open = 1;

	/* start tx if usb is open already */
	spin_lock_irq(&port->port_lock);
	startio = port->port_usb ? 1 : 0;
	gser = port->port_usb;
	spin_unlock_irq(&port->port_lock);

	if (startio) {
		pr_debug("%s: USB is already open, start io\n", __func__);
		gsdio_start_io(port);
		 if (gser->send_modem_ctrl_bits)
			gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);
	}
}

#define SDIO_CH_NAME_MAX_LEN	9
#define SDIO_OPEN_DELAY		msecs_to_jiffies(10000)
static int gsdio_ch_remove(struct platform_device *dev)
{
	struct gsdio_port	*port;
	struct sdio_port_info	*pi;
	int i;
	unsigned long		flags;

	pr_debug("%s: name:%s\n", __func__, dev->name);

	for (i = 0; i < n_sdio_ports; i++) {
		port = sdio_ports[i].port;
		pi = port->sport_info;

		if (!strncmp(pi->data_ch_name, dev->name,
					SDIO_CH_NAME_MAX_LEN)) {
			struct gserial *gser = port->port_usb;

			port->sdio_open = 0;
			port->sdio_probe = 0;
			port->ctrl_ch_err = 1;

			/* check if usb cable is connected */
			if (!gser)
				continue;

			/* indicated call status to usb host */
			gsdio_ctrl_modem_status(0, port);

			usb_ep_fifo_flush(gser->in);
			usb_ep_fifo_flush(gser->out);

			cancel_work_sync(&port->push);
			cancel_work_sync(&port->pull);

			spin_lock_irqsave(&port->port_lock, flags);
			gsdio_free_requests(gser->out, &port->read_pool);
			gsdio_free_requests(gser->out, &port->read_queue);
			gsdio_free_requests(gser->in, &port->write_pool);

			port->rp_len = 0;
			port->rq_len = 0;
			port->wp_len = 0;
			port->n_read = 0;
			spin_unlock_irqrestore(&port->port_lock, flags);

		}
	}

	return 0;
}

static int gsdio_ch_probe(struct platform_device *dev)
{
	struct gsdio_port	*port;
	struct sdio_port_info	*pi;
	int i;

	pr_debug("%s: name:%s\n", __func__, dev->name);

	for (i = 0; i < n_sdio_ports; i++) {
		port = sdio_ports[i].port;
		pi = port->sport_info;

		pr_debug("%s: sdio_ch_name:%s dev_name:%s\n", __func__,
				pi->data_ch_name, dev->name);

		/* unfortunately cmux channle might not be ready even if
		 * sdio channel is ready. as we dont have good notification
		 * mechanism schedule a delayed work
		 */
		if (!strncmp(pi->data_ch_name, dev->name,
					SDIO_CH_NAME_MAX_LEN)) {
			port->sdio_probe = 1;
			queue_delayed_work(gsdio_wq,
				&port->sdio_open_work, SDIO_OPEN_DELAY);
			return 0;
		}
	}

	pr_info("%s: name:%s is not found\n", __func__, dev->name);

	return -ENODEV;
}

int gsdio_port_alloc(unsigned portno,
		struct usb_cdc_line_coding *coding,
		struct sdio_port_info *pi)
{
	struct gsdio_port *port;
	struct platform_driver *pdriver;

	port = kzalloc(sizeof(struct gsdio_port), GFP_KERNEL);
	if (!port) {
		pr_err("%s: port allocation failed\n", __func__);
		return -ENOMEM;
	}

	port->port_num = portno;
	spin_lock_init(&port->port_lock);
	port->line_coding = *coding;

	/* READ: read from usb and write into sdio */
	INIT_LIST_HEAD(&port->read_pool);
	INIT_LIST_HEAD(&port->read_queue);
	INIT_WORK(&port->push, gsdio_rx_push);

	INIT_LIST_HEAD(&port->write_pool);
	INIT_WORK(&port->pull, gsdio_tx_pull);

	INIT_WORK(&port->notify_modem, gsdio_ctrl_wq);

	INIT_DELAYED_WORK(&port->sdio_open_work, gsdio_open_work);

	sdio_ports[portno].port = port;

	port->sport_info = pi;
	pdriver = &sdio_ports[portno].gsdio_ch;

	pdriver->probe = gsdio_ch_probe;
	pdriver->remove = gsdio_ch_remove;
	pdriver->driver.name = pi->data_ch_name;
	pdriver->driver.owner = THIS_MODULE;

	pr_debug("%s: port:%p port#%d sdio_name: %s\n", __func__,
			port, port->port_num, pi->data_ch_name);

	platform_driver_register(pdriver);

	pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num);

	return 0;
}

int gsdio_connect(struct gserial *gser, u8 portno)
{
	struct gsdio_port *port;
	int ret = 0;
	unsigned long flags;

	if (portno >= n_sdio_ports) {
		pr_err("%s: invalid portno#%d\n", __func__, portno);
		return -EINVAL;
	}

	if (!gser) {
		pr_err("%s: gser is null\n", __func__);
		return -EINVAL;
	}

	port = sdio_ports[portno].port;

	spin_lock_irqsave(&port->port_lock, flags);
	port->port_usb = gser;
	gser->notify_modem = gsdio_ctrl_notify_modem;
	spin_unlock_irqrestore(&port->port_lock, flags);

	ret = usb_ep_enable(gser->in);
	if (ret) {
		pr_err("%s: failed to enable in ep w/ err:%d\n",
					__func__, ret);
		port->port_usb = 0;
		return ret;
	}
	gser->in->driver_data = port;

	ret = usb_ep_enable(gser->out);
	if (ret) {
		pr_err("%s: failed to enable in ep w/ err:%d\n",
					__func__, ret);
		usb_ep_disable(gser->in);
		port->port_usb = 0;
		gser->in->driver_data = 0;
		return ret;
	}
	gser->out->driver_data = port;

	if (port->sdio_open) {
		pr_debug("%s: sdio is already open, start io\n", __func__);
		gsdio_start_io(port);
		 if (gser->send_modem_ctrl_bits)
			gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);
	}

	return 0;
}

void gsdio_disconnect(struct gserial *gser, u8 portno)
{
	unsigned long flags;
	struct gsdio_port *port;

	if (portno >= n_sdio_ports) {
		pr_err("%s: invalid portno#%d\n", __func__, portno);
		return;
	}

	if (!gser) {
		pr_err("%s: gser is null\n", __func__);
		return;
	}

	port = sdio_ports[portno].port;

	/* send dtr zero to modem to notify disconnect */
	port->cbits_to_modem = 0;
	queue_work(gsdio_wq, &port->notify_modem);

	spin_lock_irqsave(&port->port_lock, flags);
	port->port_usb = 0;
	port->nbytes_tomodem = 0;
	port->nbytes_tolaptop = 0;
	spin_unlock_irqrestore(&port->port_lock, flags);

	/* disable endpoints, aborting down any active I/O */
	usb_ep_disable(gser->out);
	gser->out->driver_data = NULL;

	usb_ep_disable(gser->in);
	gser->in->driver_data = NULL;

	spin_lock_irqsave(&port->port_lock, flags);
	gsdio_free_requests(gser->out, &port->read_pool);
	gsdio_free_requests(gser->out, &port->read_queue);
	gsdio_free_requests(gser->in, &port->write_pool);

	port->rp_len = 0;
	port->rq_len = 0;
	port->wp_len = 0;
	port->n_read = 0;
	spin_unlock_irqrestore(&port->port_lock, flags);
}

#if defined(CONFIG_DEBUG_FS)
static char debug_buffer[PAGE_SIZE];

static ssize_t debug_sdio_read_stats(struct file *file, char __user *ubuf,
		size_t count, loff_t *ppos)
{
	struct gsdio_port *port;
	char *buf;
	unsigned long flags;
	int i = 0;
	int temp = 0;
	int ret;

	buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	while (i < n_sdio_ports) {
		port = sdio_ports[i].port;
		spin_lock_irqsave(&port->port_lock, flags);
		temp += scnprintf(buf + temp, PAGE_SIZE - temp,
				"###PORT:%d port:%p###\n"
				"nbytes_tolaptop: %lu\n"
				"nbytes_tomodem:  %lu\n"
				"cbits_to_modem:  %u\n"
				"cbits_to_laptop: %u\n"
				"read_pool_len:   %lu\n"
				"read_queue_len:  %lu\n"
				"write_pool_len:  %lu\n"
				"n_read:          %u\n"
				"sdio_open:       %d\n"
				"sdio_probe:      %d\n",
				i, port,
				port->nbytes_tolaptop, port->nbytes_tomodem,
				port->cbits_to_modem, port->cbits_to_laptop,
				port->rp_len, port->rq_len, port->wp_len,
				port->n_read,
				port->sdio_open, port->sdio_probe);
		spin_unlock_irqrestore(&port->port_lock, flags);
		i++;
	}

	ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);

	kfree(buf);

	return ret;
}

static ssize_t debug_sdio_reset_stats(struct file *file, const char __user *buf,
				 size_t count, loff_t *ppos)
{
	struct gsdio_port *port;
	unsigned long flags;
	int i = 0;

	while (i < n_sdio_ports) {
		port = sdio_ports[i].port;

		spin_lock_irqsave(&port->port_lock, flags);
		port->nbytes_tolaptop = 0;
		port->nbytes_tomodem = 0;
		spin_unlock_irqrestore(&port->port_lock, flags);
		i++;
	}

	return count;
}

static int debug_sdio_open(struct inode *inode, struct file *file)
{
	return 0;
}

static const struct file_operations debug_gsdio_ops = {
	.open = debug_sdio_open,
	.read = debug_sdio_read_stats,
	.write = debug_sdio_reset_stats,
};

static void gsdio_debugfs_init(void)
{
	struct dentry *dent;

	dent = debugfs_create_dir("usb_gsdio", 0);
	if (IS_ERR(dent))
		return;

	debugfs_create_file("status", 0444, dent, 0, &debug_gsdio_ops);
}
#else
static void gsdio_debugfs_init(void)
{
	return;
}
#endif

/* connect, disconnect, alloc_requests, free_requests */
int gsdio_setup(struct usb_gadget *g, unsigned count)
{
	struct usb_cdc_line_coding	coding;
	int i;
	int ret = 0;

	pr_debug("%s: gadget:(%p) count:%d\n", __func__, g, count);

	if (count == 0 || count > SDIO_N_PORTS) {
		pr_err("%s: invalid number of ports count:%d max_ports:%d\n",
				__func__, count, SDIO_N_PORTS);
		return -EINVAL;
	}

	coding.dwDTERate = cpu_to_le32(9600);
	coding.bCharFormat = 8;
	coding.bParityType = USB_CDC_NO_PARITY;
	coding.bDataBits = USB_CDC_1_STOP_BITS;

	gsdio_wq = create_singlethread_workqueue("k_gserial");
	if (!gsdio_wq) {
		pr_err("%s: unable to create workqueue gsdio_wq\n",
				__func__);
		return -ENOMEM;
	}

	for (i = 0; i < count; i++) {
		mutex_init(&sdio_ports[i].lock);
		ret = gsdio_port_alloc(i, &coding, sport_info + i);
		n_sdio_ports++;
		if (ret) {
			n_sdio_ports--;
			pr_err("%s: sdio logical port allocation failed\n",
					__func__);
			goto free_sdio_ports;
		}

	}

	gsdio_debugfs_init();

	return 0;

free_sdio_ports:
	for (i = 0; i < n_sdio_ports; i++)
		gsdio_port_free(i);
	destroy_workqueue(gsdio_wq);

	return ret;
}

/* TODO: Add gserial_cleanup */
