// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#define KMSG_COMPONENT "QDSS diag bridge"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direction.h>
#include <linux/mhi.h>
#include <linux/usb/usb_qdss.h>
#include <linux/of.h>
#include <linux/delay.h>
#include "qdss_bridge.h"

#define MODULE_NAME "qdss_bridge"
#define INIT_STATUS -1
#define DEFAULT_CHANNEL "QDSS"

static struct class *mhi_class;
static enum mhi_state dev_state = INIT_STATUS;
static enum mhi_ch curr_chan;
static struct qdss_bridge_drvdata *bridge_drvdata;

static const char * const str_mhi_curr_chan[] = {
		[QDSS]			= "QDSS",
		[QDSS_HW]		= "IP_HW_QDSS",
		[EMPTY]			= "EMPTY",
};

static const char * const str_mhi_transfer_mode[] = {
		[MHI_TRANSFER_TYPE_USB]			= "usb",
		[MHI_TRANSFER_TYPE_UCI]			= "uci",
};

static int qdss_destroy_mhi_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_mhi_buf_tbl_t *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->mhi_buf_tbl) {
		entry = list_entry(start, struct qdss_mhi_buf_tbl_t, link);
		list_del(&entry->link);
		kfree(entry->buf);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_destroy_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_buf_tbl_lst *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->buf_tbl) {
		entry = list_entry(start, struct qdss_buf_tbl_lst, link);
		list_del(&entry->link);
		kfree(entry->buf);
		kfree(entry->usb_req);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_destroy_read_done_list(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_mhi_buf_tbl_t *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->read_done_list) {
		entry = list_entry(start, struct qdss_mhi_buf_tbl_t, link);
		list_del(&entry->link);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_create_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct qdss_buf_tbl_lst *entry;
	void *buf;
	struct qdss_request *usb_req;
	int i;
	struct mhi_device *mhi_dev = drvdata->mhi_dev;

	drvdata->nr_trbs = mhi_get_free_desc_count(mhi_dev,
							DMA_FROM_DEVICE);

	for (i = 0; i < drvdata->nr_trbs; i++) {
		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
		if (!entry)
			goto err;

		buf = kzalloc(drvdata->mtu, GFP_KERNEL);
		usb_req = kzalloc(sizeof(*usb_req), GFP_KERNEL);

		entry->buf = buf;
		entry->usb_req = usb_req;
		atomic_set(&entry->available, 1);
		list_add_tail(&entry->link, &drvdata->buf_tbl);

		if (!buf || !usb_req)
			goto err;
	}

	return 0;
err:
	qdss_destroy_buf_tbl(drvdata);
	return -ENOMEM;
}

struct qdss_buf_tbl_lst *qdss_get_buf_tbl_entry(
					struct qdss_bridge_drvdata *drvdata,
					void *buf)
{
	struct qdss_buf_tbl_lst *entry;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry(entry, &drvdata->buf_tbl, link) {
		if (atomic_read(&entry->available))
			continue;
		if (entry->buf == buf) {
			spin_unlock_bh(&drvdata->lock);
			return entry;
		}
	}

	spin_unlock_bh(&drvdata->lock);
	return NULL;
}

static int qdss_check_entry(struct qdss_bridge_drvdata *drvdata)
{
	struct qdss_buf_tbl_lst *entry;
	int ret = 0;

	list_for_each_entry(entry, &drvdata->buf_tbl, link) {
		if (atomic_read(&entry->available) == 0
			&& atomic_read(&entry->used) == 1) {
			ret = 1;
			return ret;
		}
	}

	return ret;
}

static void qdss_del_buf_tbl_entry(struct qdss_bridge_drvdata *drvdata,
				void *buf)
{
	struct qdss_mhi_buf_tbl_t *entry, *tmp;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry_safe(entry, tmp, &drvdata->mhi_buf_tbl, link) {
		if (entry->buf == buf) {
			list_del(&entry->link);
			kfree(entry->buf);
			kfree(entry);
			spin_unlock_bh(&drvdata->lock);
			return;
		}
	}
	spin_unlock_bh(&drvdata->lock);
}

struct qdss_buf_tbl_lst *qdss_get_entry(struct qdss_bridge_drvdata *drvdata)
{
	struct qdss_buf_tbl_lst *item;

	list_for_each_entry(item, &drvdata->buf_tbl, link)
		if (atomic_cmpxchg(&item->available, 1, 0) == 1)
			return item;

	return NULL;
}

static void qdss_buf_tbl_remove(struct qdss_bridge_drvdata *drvdata,
				void *buf)
{
	struct qdss_buf_tbl_lst *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry(entry, &drvdata->buf_tbl, link) {
		if (entry->buf != buf)
			continue;
		atomic_set(&entry->available, 1);
		atomic_set(&entry->used, 0);
		spin_unlock_bh(&drvdata->lock);
		return;
	}
	spin_unlock_bh(&drvdata->lock);
	pr_err_ratelimited("Failed to find buffer for removal\n");
}

static void mhi_ch_close(struct qdss_bridge_drvdata *drvdata)
{
	if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
		qdss_destroy_buf_tbl(drvdata);
		qdss_destroy_read_done_list(drvdata);
	} else if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
		qdss_destroy_mhi_buf_tbl(drvdata);
		drvdata->cur_buf = NULL;
		qdss_destroy_read_done_list(drvdata);
	}
}

static ssize_t mode_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct qdss_bridge_drvdata *drvdata = dev_get_drvdata(dev);

	return scnprintf(buf, PAGE_SIZE, "%s\n",
			str_mhi_transfer_mode[drvdata->mode]);
}

static ssize_t curr_chan_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	if (curr_chan < QDSS || curr_chan > EMPTY)
		return -EINVAL;
	return scnprintf(buf, PAGE_SIZE, "%s\n", str_mhi_curr_chan[curr_chan]);
}

static ssize_t mode_store(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t size)
{
	struct qdss_bridge_drvdata *drvdata = dev_get_drvdata(dev);
	char str[10] = "";
	int ret;

	if (strlen(buf) >= 10)
		return -EINVAL;
	if (sscanf(buf, "%3s", str) != 1)
		return -EINVAL;

	spin_lock_bh(&drvdata->lock);
	if (!strcmp(str, str_mhi_transfer_mode[MHI_TRANSFER_TYPE_UCI])) {
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
			if (drvdata->opened == ENABLE) {
				drvdata->opened = DISABLE;
				spin_unlock_bh(&drvdata->lock);
				usb_qdss_close(drvdata->usb_ch);
				mhi_unprepare_from_transfer(drvdata->mhi_dev);
				flush_workqueue(drvdata->mhi_wq);
				mhi_ch_close(drvdata);
				drvdata->mode = MHI_TRANSFER_TYPE_UCI;
			} else if (drvdata->opened == DISABLE) {
				drvdata->mode = MHI_TRANSFER_TYPE_UCI;
				spin_unlock_bh(&drvdata->lock);
			} else {
				ret = -ERESTARTSYS;
				goto out;
			}
		} else
			spin_unlock_bh(&drvdata->lock);

	} else if (!strcmp(str, str_mhi_transfer_mode[MHI_TRANSFER_TYPE_USB])) {
		if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
			if (drvdata->opened == ENABLE) {
				drvdata->opened = DISABLE;
				spin_unlock_bh(&drvdata->lock);
				wake_up(&drvdata->uci_wq);
				mhi_unprepare_from_transfer(drvdata->mhi_dev);
				flush_workqueue(drvdata->mhi_wq);
				mhi_ch_close(drvdata);
				drvdata->mode = MHI_TRANSFER_TYPE_USB;
				queue_work(drvdata->mhi_wq,
						&drvdata->open_work);
			} else if (drvdata->opened == DISABLE) {
				drvdata->mode = MHI_TRANSFER_TYPE_USB;
				spin_unlock_bh(&drvdata->lock);
				queue_work(drvdata->mhi_wq,
						&drvdata->open_work);
			} else {
				ret = -ERESTARTSYS;
				goto out;
			}
		} else
			spin_unlock_bh(&drvdata->lock);

	} else {
		ret = -EINVAL;
		goto out;
	}

	ret = size;
	return ret;
out:
	spin_unlock_bh(&drvdata->lock);
	return ret;
}

static DEVICE_ATTR_RW(mode);
static DEVICE_ATTR_RO(curr_chan);

static struct attribute *qdss_bridge_attrs[] = {
	&dev_attr_mode.attr,
	&dev_attr_curr_chan.attr,
	NULL,
};

static const struct attribute_group qdss_bridge_group = {
	.attrs = qdss_bridge_attrs,
};

static const struct attribute_group *qdss_bridge_groups[] = {
	&qdss_bridge_group,
	NULL,
};

static void mhi_read_work_fn(struct work_struct *work)
{
	int err = 0;
	enum mhi_flags mhi_flags = MHI_EOT;
	struct qdss_buf_tbl_lst *entry;

	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     read_work);

	if (!bridge_drvdata)
		return;
	do {
		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}
		entry = qdss_get_entry(drvdata);
		if (!entry) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}

		err = mhi_queue_buf(drvdata->mhi_dev, DMA_FROM_DEVICE,
					entry->buf, drvdata->mtu, mhi_flags);
		if (err) {
			pr_err_ratelimited("Unable to read from MHI buffer err:%d",
					   err);
			goto fail;
		}
		spin_unlock_bh(&drvdata->lock);

	} while (entry);

	return;
fail:
	spin_unlock_bh(&drvdata->lock);
	qdss_buf_tbl_remove(drvdata, entry->buf);
	queue_work(drvdata->mhi_wq, &drvdata->read_work);
}

static int mhi_queue_read(struct qdss_bridge_drvdata *drvdata)
{
	queue_work(drvdata->mhi_wq, &(drvdata->read_work));
	return 0;
}

static int usb_write(struct qdss_bridge_drvdata *drvdata,
			     unsigned char *buf, size_t len)
{
	int ret = 0;
	struct qdss_buf_tbl_lst *entry;

	entry = qdss_get_buf_tbl_entry(drvdata, buf);
	if (!entry)
		return -EINVAL;

	entry->usb_req->buf = buf;
	entry->usb_req->length = len;
	atomic_set(&entry->used, 1);
	ret = usb_qdss_write(drvdata->usb_ch, entry->usb_req);

	return ret;
}

static void mhi_read_done_work_fn(struct work_struct *work)
{
	unsigned char *buf = NULL;
	int err = 0;
	size_t len = 0;
	struct qdss_mhi_buf_tbl_t *tp, *_tp;
	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     read_done_work);
	LIST_HEAD(head);

	do {
		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE
		    || drvdata->mode != MHI_TRANSFER_TYPE_USB) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}

		if (list_empty(&drvdata->read_done_list)) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}
		list_splice_tail_init(&drvdata->read_done_list, &head);
		spin_unlock_bh(&drvdata->lock);

		list_for_each_entry_safe(tp, _tp, &head, link) {
			list_del(&tp->link);
			buf = tp->buf;
			len = tp->len;
			kfree(tp);
			if (!buf)
				break;
			pr_debug("Read from mhi buf %pK len:%zd\n", buf, len);
			/*
			 * The read buffers can come after the MHI channels are
			 * closed. If the channels are closed at the time of
			 * read, discard the buffers here and do not forward
			 * them to the mux layer.
			 */
			spin_lock_bh(&drvdata->lock);
			if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
				if (drvdata->opened == ENABLE) {
					spin_unlock_bh(&drvdata->lock);
					err = usb_write(drvdata, buf, len);
					if (err)
						qdss_buf_tbl_remove(drvdata,
								    buf);
				} else if (drvdata->opened == DISABLE) {
					spin_unlock_bh(&drvdata->lock);
					qdss_buf_tbl_remove(drvdata, buf);
				} else {
					spin_unlock_bh(&drvdata->lock);
				}
			} else {
				spin_unlock_bh(&drvdata->lock);
			}
		}
		list_del_init(&head);
	} while (buf);
}

static void usb_write_done(struct qdss_bridge_drvdata *drvdata,
				   struct qdss_request *d_req)
{
	if (d_req->status)
		pr_err_ratelimited("USB write failed err:%d\n", d_req->status);

	qdss_buf_tbl_remove(drvdata, d_req->buf);
	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE)
		mhi_queue_read(drvdata);
	spin_unlock_bh(&drvdata->lock);
}

static void usb_notifier(void *priv, unsigned int event,
			struct qdss_request *d_req, struct usb_qdss_ch *ch)
{
	struct qdss_bridge_drvdata *drvdata = priv;

	if (!drvdata || drvdata->mode != MHI_TRANSFER_TYPE_USB) {
		pr_err_ratelimited("%s can't be called in invalid status.\n",
				__func__);
		return;
	}

	switch (event) {
	case USB_QDSS_CONNECT:
		if (drvdata->opened == ENABLE) {
			usb_qdss_alloc_req(ch, drvdata->nr_trbs);
			mhi_queue_read(drvdata);
		}
		break;

	case USB_QDSS_DISCONNECT:
		if (drvdata->opened == ENABLE)
			usb_qdss_free_req(drvdata->usb_ch);
		break;

	case USB_QDSS_DATA_WRITE_DONE:
		usb_write_done(drvdata, d_req);
		break;

	default:
		break;
	}
}

static int mhi_ch_open(struct qdss_bridge_drvdata *drvdata)
{
	int ret;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE) {
		spin_unlock_bh(&drvdata->lock);
		return 0;
	}
	if (drvdata->opened == SSR) {
		spin_unlock_bh(&drvdata->lock);
		return -ERESTARTSYS;
	}
	spin_unlock_bh(&drvdata->lock);

	ret = mhi_prepare_for_transfer(drvdata->mhi_dev, 0);
	if (ret) {
		pr_err("Unable to open MHI channel\n");
		return ret;
	}

	return 0;
}

static void qdss_bridge_open_work_fn(struct work_struct *work)
{
	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     open_work);
	int ret;

	ret = mhi_ch_open(drvdata);
	if (ret)
		goto err_open;

	ret = qdss_create_buf_tbl(drvdata);
	if (ret)
		goto err;

	drvdata->usb_ch = usb_qdss_open("qdss_mdm", drvdata, usb_notifier);
	if (IS_ERR_OR_NULL(drvdata->usb_ch)) {
		ret = PTR_ERR(drvdata->usb_ch);
		goto err;
	}

	drvdata->opened = ENABLE;
	return;
err:
	mhi_unprepare_from_transfer(drvdata->mhi_dev);
	mhi_ch_close(drvdata);
err_open:
	pr_err("Open work failed with err:%d\n", ret);
}

static void qdss_mhi_write_cb(struct mhi_device *mhi_dev,
				struct mhi_result *result)
{
}

static void qdss_mhi_read_cb(struct mhi_device *mhi_dev,
				struct mhi_result *result)
{
	struct qdss_bridge_drvdata *drvdata = NULL;
	struct qdss_mhi_buf_tbl_t *tp;
	void *buf = NULL;

	drvdata = bridge_drvdata;
	if (!drvdata)
		return;
	buf = result->buf_addr;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE &&
	    result->transaction_status != -ENOTCONN) {
		spin_unlock_bh(&drvdata->lock);
		tp = kmalloc(sizeof(*tp), GFP_ATOMIC);
		if (!tp)
			return;
		tp->buf = buf;
		tp->len = result->bytes_xferd;
		spin_lock_bh(&drvdata->lock);
		list_add_tail(&tp->link, &drvdata->read_done_list);
		spin_unlock_bh(&drvdata->lock);
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB)
			queue_work(drvdata->mhi_wq, &drvdata->read_done_work);
		else
			wake_up(&drvdata->uci_wq);
	} else {
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
			spin_unlock_bh(&drvdata->lock);
			qdss_buf_tbl_remove(drvdata, buf);
		} else {
			spin_unlock_bh(&drvdata->lock);
			return;
		}
	}

}

static int mhi_uci_release(struct inode *inode, struct file *file)
{
	struct qdss_bridge_drvdata *drvdata = file->private_data;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
		if (drvdata->opened == ENABLE) {
			drvdata->opened = DISABLE;
			spin_unlock_bh(&drvdata->lock);
			wake_up(&drvdata->uci_wq);
			mhi_unprepare_from_transfer(drvdata->mhi_dev);
			flush_workqueue(drvdata->mhi_wq);
			mhi_ch_close(drvdata);
		} else if (drvdata->opened == SSR) {
			spin_unlock_bh(&drvdata->lock);
			complete(&drvdata->completion);
		} else
			spin_unlock_bh(&drvdata->lock);
	} else
		spin_unlock_bh(&drvdata->lock);

	return 0;
}

static ssize_t mhi_uci_read(struct file *file,
			char __user *buf,
			size_t count,
			loff_t *ppos)
{
	struct qdss_bridge_drvdata *drvdata = file->private_data;
	struct mhi_device *mhi_dev = drvdata->mhi_dev;
	struct qdss_mhi_buf_tbl_t *uci_buf;
	char *ptr;
	size_t to_copy;
	int ret = 0;

	if (!buf)
		return -EINVAL;

	pr_debug("Client provided buf len:%lu\n", count);

	/* confirm channel is active */
	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened != ENABLE ||
	    drvdata->mode != MHI_TRANSFER_TYPE_UCI) {
		spin_unlock_bh(&drvdata->lock);
		return -ERESTARTSYS;
	}

	/* No data available to read, wait */
	if (!drvdata->cur_buf && list_empty(&drvdata->read_done_list)) {
		spin_unlock_bh(&drvdata->lock);

		pr_debug("No data available to read waiting\n");
		ret = wait_event_interruptible(drvdata->uci_wq,
				((drvdata->opened != ENABLE
				 || !list_empty(&drvdata->read_done_list))));
		if (ret == -ERESTARTSYS) {
			pr_debug("Exit signal caught for node\n");
			return -ERESTARTSYS;
		}

		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE) {
			spin_unlock_bh(&drvdata->lock);
			pr_debug("node was disabled or SSR occurred.\n");
			ret = -ERESTARTSYS;
			return ret;
		}
	}

	/* new read, get the next descriptor from the list */
	if (!drvdata->cur_buf) {
		uci_buf = list_first_entry_or_null(&drvdata->read_done_list,
					struct qdss_mhi_buf_tbl_t, link);
		if (unlikely(!uci_buf)) {
			ret = -EIO;
			goto read_error;
		}

		list_del(&uci_buf->link);
		drvdata->cur_buf = uci_buf;
		drvdata->rx_size = uci_buf->len;
		pr_debug("Got pkt of size:%zu\n", drvdata->rx_size);
	}

	uci_buf = drvdata->cur_buf;
	spin_unlock_bh(&drvdata->lock);

	/* Copy the buffer to user space */
	to_copy = min_t(size_t, count, drvdata->rx_size);
	ptr = uci_buf->buf + (uci_buf->len - drvdata->rx_size);
	ret = copy_to_user(buf, ptr, to_copy);
	if (ret)
		return ret;

	pr_debug("Copied %lu of %lu bytes\n", to_copy, drvdata->rx_size);
	drvdata->rx_size -= to_copy;

	/* we finished with this buffer, queue it back to hardware */
	if (!drvdata->rx_size) {
		spin_lock_bh(&drvdata->lock);
		drvdata->cur_buf = NULL;

		if (drvdata->opened == ENABLE)
			ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE,
						 uci_buf->buf, drvdata->mtu,
						 MHI_EOT);
		else
			ret = -ERESTARTSYS;

		spin_unlock_bh(&drvdata->lock);

		if (ret) {
			pr_err("Failed to recycle element, ret: %d\n", ret);
			qdss_del_buf_tbl_entry(drvdata, uci_buf->buf);
			uci_buf->buf = NULL;
			kfree(uci_buf);
			return ret;
		}
		kfree(uci_buf);
	}

	pr_debug("Returning %lu bytes\n", to_copy);
	return to_copy;

read_error:
	spin_unlock_bh(&drvdata->lock);
	return ret;
}

static int mhi_queue_inbound(struct qdss_bridge_drvdata *drvdata)
{
	struct mhi_device *mhi_dev = drvdata->mhi_dev;
	void *buf;
	struct qdss_mhi_buf_tbl_t *entry;
	int ret = -EIO, i;

	for (i = 0; i < drvdata->nr_trbs; i++) {
		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
		if (!entry)
			goto err;

		buf = kzalloc(drvdata->mtu, GFP_KERNEL);
		if (!buf) {
			kfree(entry);
			goto err;
		}

		entry->buf = buf;

		ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf,
					drvdata->mtu,
					MHI_EOT);
		if (ret) {
			kfree(buf);
			kfree(entry);
			pr_err("Failed to queue buffer %d\n", i);
			return ret;
		}
		list_add_tail(&entry->link, &drvdata->mhi_buf_tbl);
	}

	return ret;
err:
	return -ENOMEM;

}

static int mhi_uci_open(struct inode *inode, struct file *filp)
{
	int ret = -EIO;
	struct qdss_mhi_buf_tbl_t *buf_itr, *tmp;
	struct qdss_bridge_drvdata *drvdata = bridge_drvdata;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened) {
		pr_err("Node was opened or SSR occurred\n");
		spin_unlock_bh(&drvdata->lock);
		return ret;
	}
	spin_unlock_bh(&drvdata->lock);

	ret = mhi_prepare_for_transfer(drvdata->mhi_dev, 0);
	if (ret) {
		pr_err("Error starting transfer channels\n");
		return ret;
	}

	ret = mhi_queue_inbound(drvdata);
	if (ret)
		goto error_rx_queue;

	filp->private_data = drvdata;
	drvdata->opened = ENABLE;
	return ret;

error_rx_queue:
	mhi_unprepare_from_transfer(drvdata->mhi_dev);
	list_for_each_entry_safe(buf_itr, tmp, &drvdata->read_done_list, link) {
		list_del(&buf_itr->link);
		kfree(buf_itr->buf);
		kfree(buf_itr);
	}

	return ret;
}



static const struct file_operations mhidev_fops = {
	.open = mhi_uci_open,
	.release = mhi_uci_release,
	.read = mhi_uci_read,
};

static void qdss_mhi_remove(struct mhi_device *mhi_dev)
{
	struct qdss_bridge_drvdata *drvdata = NULL;

	if (!mhi_dev)
		return;
	drvdata = bridge_drvdata;
	if (!drvdata)
		return;

	pr_debug("remove dev state: %d\n", mhi_dev->mhi_cntrl->dev_state);

	dev_state = mhi_dev->mhi_cntrl->dev_state;
	if (mhi_dev->mhi_cntrl->dev_state != MHI_STATE_RESET)
		curr_chan = EMPTY;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE) {
		drvdata->opened = SSR;
		if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
			spin_unlock_bh(&drvdata->lock);
			wake_up(&drvdata->uci_wq);
			wait_for_completion(&drvdata->completion);
		} else {
			spin_unlock_bh(&drvdata->lock);
			if (drvdata->usb_ch)
				usb_qdss_close(drvdata->usb_ch);
			do {
				msleep(20);
			} while (qdss_check_entry(drvdata));
		}
		flush_workqueue(drvdata->mhi_wq);
		mhi_ch_close(drvdata);
	} else
		spin_unlock_bh(&drvdata->lock);

	device_destroy(mhi_class, drvdata->cdev->dev);
	unregister_chrdev_region(drvdata->cdev->dev, 1);
	cdev_del(drvdata->cdev);
	bridge_drvdata = NULL;
}

int qdss_mhi_init(struct qdss_bridge_drvdata *drvdata)
{
	drvdata->mhi_wq = create_singlethread_workqueue(MODULE_NAME);
	if (!drvdata->mhi_wq)
		return -ENOMEM;

	spin_lock_init(&drvdata->lock);
	INIT_WORK(&(drvdata->read_work), mhi_read_work_fn);
	INIT_WORK(&(drvdata->read_done_work), mhi_read_done_work_fn);
	INIT_WORK(&(drvdata->open_work), qdss_bridge_open_work_fn);
	INIT_LIST_HEAD(&drvdata->buf_tbl);
	INIT_LIST_HEAD(&drvdata->mhi_buf_tbl);
	init_waitqueue_head(&drvdata->uci_wq);
	init_completion(&drvdata->completion);
	INIT_LIST_HEAD(&drvdata->read_done_list);
	drvdata->opened = DISABLE;

	return 0;
}

static int qdss_mhi_probe(struct mhi_device *mhi_dev,
				const struct mhi_device_id *id)
{
	int ret;
	unsigned int baseminor = 0;
	unsigned int count = 1;
	struct qdss_bridge_drvdata *drvdata;
	dev_t dev;

	pr_debug("probe dev state: %d chan: %s curr_chan: %d\n",
		  mhi_dev->mhi_cntrl->dev_state,
		  id->chan,
		  curr_chan);

	if (dev_state == INIT_STATUS) {
		if (strcmp(mhi_dev->name, DEFAULT_CHANNEL))
			return -EINVAL;
		if (!strcmp(id->chan, "QDSS"))
			curr_chan = QDSS;
		if (!strcmp(id->chan, "IP_HW_QDSS"))
			curr_chan = QDSS_HW;
	} else if (dev_state == MHI_STATE_RESET) {
		if (strcmp(id->chan, str_mhi_curr_chan[curr_chan]))
			return -EINVAL;
	} else {
		if (curr_chan != EMPTY) {
			pr_err("Need unbind another channel before bind.\n");
			return -EINVAL;
		}
		if (!strcmp(id->chan, "QDSS"))
			curr_chan = QDSS;
		if (!strcmp(id->chan, "IP_HW_QDSS"))
			curr_chan = QDSS_HW;
	}

	drvdata = devm_kzalloc(&mhi_dev->dev, sizeof(*drvdata), GFP_KERNEL);
	if (!drvdata) {
		ret = -ENOMEM;
		return ret;
	}
	drvdata->cdev = cdev_alloc();
	if (!drvdata->cdev) {
		ret = -ENOMEM;
		return ret;
	}

	ret = alloc_chrdev_region(&dev, baseminor, count, "mhi_qdss");
	if (ret < 0) {
		pr_err("alloc_chrdev_region failed %d\n", ret);
		return ret;
	}

	drvdata->cdev->owner = THIS_MODULE;
	drvdata->cdev->ops = &mhidev_fops;

	ret = cdev_add(drvdata->cdev, dev, 1);
	if (ret)
		goto exit_unreg_chrdev_region;

	drvdata->dev = device_create(mhi_class, NULL,
			       drvdata->cdev->dev, drvdata,
			       "mhi_qdss");
	if (IS_ERR(drvdata->dev)) {
		pr_err("class_device_create failed %d\n", ret);
		ret = -ENOMEM;
		goto exit_cdev_add;
	}

	drvdata->mode = MHI_TRANSFER_TYPE_USB;
	drvdata->mtu = min_t(size_t, id->driver_data, MHI_MAX_MTU);
	drvdata->mhi_dev = mhi_dev;
	bridge_drvdata = drvdata;
	dev_set_drvdata(drvdata->dev, drvdata);

	ret = qdss_mhi_init(drvdata);
	if (ret) {
		pr_err("Device probe failed err:%d\n", ret);
		goto exit_destroy_device;
	}
	queue_work(drvdata->mhi_wq, &drvdata->open_work);
	return 0;

exit_destroy_device:
	device_destroy(mhi_class, drvdata->cdev->dev);
exit_cdev_add:
	cdev_del(drvdata->cdev);
exit_unreg_chrdev_region:
	unregister_chrdev_region(drvdata->cdev->dev, 1);
	return ret;

}

static const struct mhi_device_id qdss_mhi_match_table[] = {
	{ .chan = "QDSS", .driver_data = 0x8000 },
	{ .chan = "IP_HW_QDSS", .driver_data = 0x8000 },
	{},
};

static struct mhi_driver qdss_mhi_driver = {
	.id_table = qdss_mhi_match_table,
	.probe = qdss_mhi_probe,
	.remove = qdss_mhi_remove,
	.dl_xfer_cb = qdss_mhi_read_cb,
	.ul_xfer_cb = qdss_mhi_write_cb,
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
	}
};

static int __init qdss_bridge_init(void)
{
	int ret;

	mhi_class = class_create(THIS_MODULE, MODULE_NAME);
	if (IS_ERR(mhi_class))
		return -ENODEV;

	mhi_class->dev_groups = qdss_bridge_groups;

	ret = mhi_driver_register(&qdss_mhi_driver);
	if (ret)
		class_destroy(mhi_class);

	return ret;
}

static void __exit qdss_bridge_exit(void)
{
	mhi_driver_unregister(&qdss_mhi_driver);
}

module_init(qdss_bridge_init);
module_exit(qdss_bridge_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QDSS Bridge driver");
