ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/usb/gadget/function/Makefile b/marvell/linux/drivers/usb/gadget/function/Makefile
new file mode 100644
index 0000000..d0577fd
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/Makefile
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# USB peripheral controller drivers
+#
+
+ccflags-y			:= -I$(srctree)/drivers/usb/gadget/
+ccflags-y			+= -I$(srctree)/drivers/usb/gadget/udc/
+
+# USB Functions
+usb_f_acm-y			:= f_acm.o
+obj-$(CONFIG_USB_F_ACM)		+= usb_f_acm.o
+usb_f_ss_lb-y			:= f_loopback.o f_sourcesink.o
+obj-$(CONFIG_USB_F_SS_LB)	+= usb_f_ss_lb.o
+obj-$(CONFIG_USB_U_SERIAL)	+= u_serial.o
+usb_f_serial-y			:= f_serial.o
+obj-$(CONFIG_USB_F_SERIAL)	+= usb_f_serial.o
+usb_f_obex-y			:= f_obex.o
+obj-$(CONFIG_USB_F_OBEX)	+= usb_f_obex.o
+obj-$(CONFIG_USB_U_ETHER)	+= u_ether.o
+usb_f_ncm-y			:= f_ncm.o
+obj-$(CONFIG_USB_F_NCM)		+= usb_f_ncm.o
+usb_f_ecm-y			:= f_ecm.o
+obj-$(CONFIG_USB_F_ECM)		+= usb_f_ecm.o
+usb_f_phonet-y			:= f_phonet.o
+obj-$(CONFIG_USB_F_PHONET)	+= usb_f_phonet.o
+usb_f_eem-y			:= f_eem.o
+obj-$(CONFIG_USB_F_EEM)		+= usb_f_eem.o
+usb_f_ecm_subset-y		:= f_subset.o
+obj-$(CONFIG_USB_F_SUBSET)	+= usb_f_ecm_subset.o
+usb_f_rndis-y			:= f_rndis.o rndis.o
+obj-$(CONFIG_USB_F_RNDIS)	+= usb_f_rndis.o
+usb_f_mass_storage-y		:= f_mass_storage.o storage_common.o
+obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o
+usb_f_fs-y			:= f_fs.o
+obj-$(CONFIG_USB_F_FS)		+= usb_f_fs.o
+obj-$(CONFIG_USB_U_AUDIO)	+= u_audio.o
+usb_f_uac1-y			:= f_uac1.o
+obj-$(CONFIG_USB_F_UAC1)	+= usb_f_uac1.o
+usb_f_uac1_legacy-y		:= f_uac1_legacy.o u_uac1_legacy.o
+obj-$(CONFIG_USB_F_UAC1_LEGACY)	+= usb_f_uac1_legacy.o
+usb_f_uac2-y			:= f_uac2.o
+obj-$(CONFIG_USB_F_UAC2)	+= usb_f_uac2.o
+usb_f_uvc-y			:= f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o
+obj-$(CONFIG_USB_F_UVC)		+= usb_f_uvc.o
+usb_f_midi-y			:= f_midi.o
+obj-$(CONFIG_USB_F_MIDI)	+= usb_f_midi.o
+usb_f_hid-y			:= f_hid.o
+obj-$(CONFIG_USB_F_HID)		+= usb_f_hid.o
+usb_f_printer-y			:= f_printer.o
+obj-$(CONFIG_USB_F_PRINTER)	+= usb_f_printer.o
+usb_f_tcm-y			:= f_tcm.o
+obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
+usb_f_accessory-y		:= f_accessory.o
+obj-$(CONFIG_USB_F_ACC)		+= usb_f_accessory.o
+usb_f_audio_source-y		:= f_audio_source.o
+obj-$(CONFIG_USB_F_AUDIO_SRC)	+= usb_f_audio_source.o
+obj-$(CONFIG_ASR_UAC1)	+= f_asr_uac1.o
+
diff --git a/marvell/linux/drivers/usb/gadget/function/f_accessory.c b/marvell/linux/drivers/usb/gadget/function/f_accessory.c
new file mode 100644
index 0000000..78ea5e5
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_accessory.c
@@ -0,0 +1,1467 @@
+/*
+ * Gadget Function Driver for Android USB accessories
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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 DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/kref.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/usb.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/f_accessory.h>
+
+#include <linux/configfs.h>
+#include <linux/usb/composite.h>
+
+#define MAX_INST_NAME_LEN        40
+#define BULK_BUFFER_SIZE    16384
+#define ACC_STRING_SIZE     256
+
+#define PROTOCOL_VERSION    2
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX	0
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+
+struct acc_hid_dev {
+	struct list_head	list;
+	struct hid_device *hid;
+	struct acc_dev *dev;
+	/* accessory defined ID */
+	int id;
+	/* HID report descriptor */
+	u8 *report_desc;
+	/* length of HID report descriptor */
+	int report_desc_len;
+	/* number of bytes of report_desc we have received so far */
+	int report_desc_offset;
+};
+
+struct acc_dev {
+	struct usb_function function;
+	struct usb_composite_dev *cdev;
+	spinlock_t lock;
+	struct acc_dev_ref *ref;
+
+	struct usb_ep *ep_in;
+	struct usb_ep *ep_out;
+
+	/* online indicates state of function_set_alt & function_unbind
+	 * set to 1 when we connect
+	 */
+	int online;
+
+	/* disconnected indicates state of open & release
+	 * Set to 1 when we disconnect.
+	 * Not cleared until our file is closed.
+	 */
+	int disconnected;
+
+	/* strings sent by the host */
+	char manufacturer[ACC_STRING_SIZE];
+	char model[ACC_STRING_SIZE];
+	char description[ACC_STRING_SIZE];
+	char version[ACC_STRING_SIZE];
+	char uri[ACC_STRING_SIZE];
+	char serial[ACC_STRING_SIZE];
+
+	/* for acc_complete_set_string */
+	int string_index;
+
+	/* set to 1 if we have a pending start request */
+	int start_requested;
+
+	int audio_mode;
+
+	/* synchronize access to our device file */
+	atomic_t open_excl;
+
+	struct list_head tx_idle;
+
+	wait_queue_head_t read_wq;
+	wait_queue_head_t write_wq;
+	struct usb_request *rx_req[RX_REQ_MAX];
+	int rx_done;
+
+	/* delayed work for handling ACCESSORY_START */
+	struct delayed_work start_work;
+
+	/* worker for registering and unregistering hid devices */
+	struct work_struct hid_work;
+
+	/* list of active HID devices */
+	struct list_head	hid_list;
+
+	/* list of new HID devices to register */
+	struct list_head	new_hid_list;
+
+	/* list of dead HID devices to unregister */
+	struct list_head	dead_hid_list;
+};
+
+static struct usb_interface_descriptor acc_interface_desc = {
+	.bLength                = USB_DT_INTERFACE_SIZE,
+	.bDescriptorType        = USB_DT_INTERFACE,
+	.bInterfaceNumber       = 0,
+	.bNumEndpoints          = 2,
+	.bInterfaceClass        = USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass     = USB_SUBCLASS_VENDOR_SPEC,
+	.bInterfaceProtocol     = 0,
+};
+
+static struct usb_endpoint_descriptor acc_highspeed_in_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_IN,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acc_highspeed_out_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_OUT,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acc_fullspeed_in_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_IN,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor acc_fullspeed_out_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_OUT,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_acc_descs[] = {
+	(struct usb_descriptor_header *) &acc_interface_desc,
+	(struct usb_descriptor_header *) &acc_fullspeed_in_desc,
+	(struct usb_descriptor_header *) &acc_fullspeed_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_acc_descs[] = {
+	(struct usb_descriptor_header *) &acc_interface_desc,
+	(struct usb_descriptor_header *) &acc_highspeed_in_desc,
+	(struct usb_descriptor_header *) &acc_highspeed_out_desc,
+	NULL,
+};
+
+static struct usb_string acc_string_defs[] = {
+	[INTERFACE_STRING_INDEX].s	= "Android Accessory Interface",
+	{  },	/* end of list */
+};
+
+static struct usb_gadget_strings acc_string_table = {
+	.language		= 0x0409,	/* en-US */
+	.strings		= acc_string_defs,
+};
+
+static struct usb_gadget_strings *acc_strings[] = {
+	&acc_string_table,
+	NULL,
+};
+
+struct acc_dev_ref {
+	struct kref	kref;
+	struct acc_dev	*acc_dev;
+};
+
+static struct acc_dev_ref _acc_dev_ref = {
+	.kref = KREF_INIT(0),
+};
+
+struct acc_instance {
+	struct usb_function_instance func_inst;
+	const char *name;
+};
+
+static struct acc_dev *get_acc_dev(void)
+{
+	struct acc_dev_ref *ref = &_acc_dev_ref;
+
+	return kref_get_unless_zero(&ref->kref) ? ref->acc_dev : NULL;
+}
+
+static void __put_acc_dev(struct kref *kref)
+{
+	struct acc_dev_ref *ref = container_of(kref, struct acc_dev_ref, kref);
+	struct acc_dev *dev = ref->acc_dev;
+
+	/* Cancel any async work */
+	cancel_delayed_work_sync(&dev->start_work);
+	cancel_work_sync(&dev->hid_work);
+
+	ref->acc_dev = NULL;
+	kfree(dev);
+}
+
+static void put_acc_dev(struct acc_dev *dev)
+{
+	struct acc_dev_ref *ref = dev->ref;
+
+	WARN_ON(ref->acc_dev != dev);
+	kref_put(&ref->kref, __put_acc_dev);
+}
+
+static inline struct acc_dev *func_to_dev(struct usb_function *f)
+{
+	return container_of(f, struct acc_dev, function);
+}
+
+static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size)
+{
+	struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+
+	if (!req)
+		return NULL;
+
+	/* now allocate buffers for the requests */
+	req->buf = kmalloc(buffer_size, GFP_KERNEL);
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+
+	return req;
+}
+
+static void acc_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(ep, req);
+	}
+}
+
+/* add a request to the tail of a list */
+static void req_put(struct acc_dev *dev, struct list_head *head,
+		struct usb_request *req)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	list_add_tail(&req->list, head);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head)
+{
+	unsigned long flags;
+	struct usb_request *req;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (list_empty(head)) {
+		req = 0;
+	} else {
+		req = list_first_entry(head, struct usb_request, list);
+		list_del(&req->list);
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+	return req;
+}
+
+static void acc_set_disconnected(struct acc_dev *dev)
+{
+	dev->disconnected = 1;
+}
+
+static void acc_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	if (!dev)
+		return;
+
+	if (req->status == -ESHUTDOWN) {
+		pr_debug("acc_complete_in set disconnected");
+		acc_set_disconnected(dev);
+	}
+
+	req_put(dev, &dev->tx_idle, req);
+
+	wake_up(&dev->write_wq);
+	put_acc_dev(dev);
+}
+
+static void acc_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	if (!dev)
+		return;
+
+	dev->rx_done = 1;
+	if (req->status == -ESHUTDOWN) {
+		pr_debug("acc_complete_out set disconnected");
+		acc_set_disconnected(dev);
+	}
+
+	wake_up(&dev->read_wq);
+	put_acc_dev(dev);
+}
+
+static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req)
+{
+	struct acc_dev	*dev = ep->driver_data;
+	char *string_dest = NULL;
+	int length = req->actual;
+
+	if (req->status != 0) {
+		pr_err("acc_complete_set_string, err %d\n", req->status);
+		return;
+	}
+
+	switch (dev->string_index) {
+	case ACCESSORY_STRING_MANUFACTURER:
+		string_dest = dev->manufacturer;
+		break;
+	case ACCESSORY_STRING_MODEL:
+		string_dest = dev->model;
+		break;
+	case ACCESSORY_STRING_DESCRIPTION:
+		string_dest = dev->description;
+		break;
+	case ACCESSORY_STRING_VERSION:
+		string_dest = dev->version;
+		break;
+	case ACCESSORY_STRING_URI:
+		string_dest = dev->uri;
+		break;
+	case ACCESSORY_STRING_SERIAL:
+		string_dest = dev->serial;
+		break;
+	}
+	if (string_dest) {
+		unsigned long flags;
+
+		if (length >= ACC_STRING_SIZE)
+			length = ACC_STRING_SIZE - 1;
+
+		spin_lock_irqsave(&dev->lock, flags);
+		memcpy(string_dest, req->buf, length);
+		/* ensure zero termination */
+		string_dest[length] = 0;
+		spin_unlock_irqrestore(&dev->lock, flags);
+	} else {
+		pr_err("unknown accessory string index %d\n",
+			dev->string_index);
+	}
+}
+
+static void acc_complete_set_hid_report_desc(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct acc_hid_dev *hid = req->context;
+	struct acc_dev *dev = hid->dev;
+	int length = req->actual;
+
+	if (req->status != 0) {
+		pr_err("acc_complete_set_hid_report_desc, err %d\n",
+			req->status);
+		return;
+	}
+
+	memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length);
+	hid->report_desc_offset += length;
+	if (hid->report_desc_offset == hid->report_desc_len) {
+		/* After we have received the entire report descriptor
+		 * we schedule work to initialize the HID device
+		 */
+		schedule_work(&dev->hid_work);
+	}
+}
+
+static void acc_complete_send_hid_event(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct acc_hid_dev *hid = req->context;
+	int length = req->actual;
+
+	if (req->status != 0) {
+		pr_err("acc_complete_send_hid_event, err %d\n", req->status);
+		return;
+	}
+
+	hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1);
+}
+
+static int acc_hid_parse(struct hid_device *hid)
+{
+	struct acc_hid_dev *hdev = hid->driver_data;
+
+	hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len);
+	return 0;
+}
+
+static int acc_hid_start(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void acc_hid_stop(struct hid_device *hid)
+{
+}
+
+static int acc_hid_open(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void acc_hid_close(struct hid_device *hid)
+{
+}
+
+static int acc_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+	__u8 *buf, size_t len, unsigned char rtype, int reqtype)
+{
+	return 0;
+}
+
+static struct hid_ll_driver acc_hid_ll_driver = {
+	.parse = acc_hid_parse,
+	.start = acc_hid_start,
+	.stop = acc_hid_stop,
+	.open = acc_hid_open,
+	.close = acc_hid_close,
+	.raw_request = acc_hid_raw_request,
+};
+
+static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev,
+		int id, int desc_len)
+{
+	struct acc_hid_dev *hdev;
+
+	hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC);
+	if (!hdev)
+		return NULL;
+	hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC);
+	if (!hdev->report_desc) {
+		kfree(hdev);
+		return NULL;
+	}
+	hdev->dev = dev;
+	hdev->id = id;
+	hdev->report_desc_len = desc_len;
+
+	return hdev;
+}
+
+static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id)
+{
+	struct acc_hid_dev *hid;
+
+	list_for_each_entry(hid, list, list) {
+		if (hid->id == id)
+			return hid;
+	}
+	return NULL;
+}
+
+static int acc_register_hid(struct acc_dev *dev, int id, int desc_length)
+{
+	struct acc_hid_dev *hid;
+	unsigned long flags;
+
+	/* report descriptor length must be > 0 */
+	if (desc_length <= 0)
+		return -EINVAL;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	/* replace HID if one already exists with this ID */
+	hid = acc_hid_get(&dev->hid_list, id);
+	if (!hid)
+		hid = acc_hid_get(&dev->new_hid_list, id);
+	if (hid)
+		list_move(&hid->list, &dev->dead_hid_list);
+
+	hid = acc_hid_new(dev, id, desc_length);
+	if (!hid) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return -ENOMEM;
+	}
+
+	list_add(&hid->list, &dev->new_hid_list);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	/* schedule work to register the HID device */
+	schedule_work(&dev->hid_work);
+	return 0;
+}
+
+static int acc_unregister_hid(struct acc_dev *dev, int id)
+{
+	struct acc_hid_dev *hid;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	hid = acc_hid_get(&dev->hid_list, id);
+	if (!hid)
+		hid = acc_hid_get(&dev->new_hid_list, id);
+	if (!hid) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return -EINVAL;
+	}
+
+	list_move(&hid->list, &dev->dead_hid_list);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	schedule_work(&dev->hid_work);
+	return 0;
+}
+
+static int create_bulk_endpoints(struct acc_dev *dev,
+				struct usb_endpoint_descriptor *in_desc,
+				struct usb_endpoint_descriptor *out_desc)
+{
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	int i;
+
+	DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+	ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+	if (!ep) {
+		DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+		return -ENODEV;
+	}
+	DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+	ep->driver_data = dev;		/* claim the endpoint */
+	dev->ep_in = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+	if (!ep) {
+		DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+		return -ENODEV;
+	}
+	DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name);
+	ep->driver_data = dev;		/* claim the endpoint */
+	dev->ep_out = ep;
+
+	/* now allocate requests for our endpoints */
+	for (i = 0; i < TX_REQ_MAX; i++) {
+		req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+		if (!req)
+			goto fail;
+		req->complete = acc_complete_in;
+		req_put(dev, &dev->tx_idle, req);
+	}
+	for (i = 0; i < RX_REQ_MAX; i++) {
+		req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+		if (!req)
+			goto fail;
+		req->complete = acc_complete_out;
+		dev->rx_req[i] = req;
+	}
+
+	return 0;
+
+fail:
+	pr_err("acc_bind() could not allocate requests\n");
+	while ((req = req_get(dev, &dev->tx_idle)))
+		acc_request_free(req, dev->ep_in);
+	for (i = 0; i < RX_REQ_MAX; i++) {
+		acc_request_free(dev->rx_req[i], dev->ep_out);
+		dev->rx_req[i] = NULL;
+	}
+
+	return -1;
+}
+
+static ssize_t acc_read(struct file *fp, char __user *buf,
+	size_t count, loff_t *pos)
+{
+	struct acc_dev *dev = fp->private_data;
+	struct usb_request *req;
+	ssize_t r = count;
+	ssize_t data_length;
+	unsigned xfer;
+	int ret = 0;
+
+	pr_debug("acc_read(%zu)\n", count);
+
+	if (dev->disconnected) {
+		pr_debug("acc_read disconnected");
+		return -ENODEV;
+	}
+
+	if (count > BULK_BUFFER_SIZE)
+		count = BULK_BUFFER_SIZE;
+
+	/* we will block until we're online */
+	pr_debug("acc_read: waiting for online\n");
+	ret = wait_event_interruptible(dev->read_wq, dev->online);
+	if (ret < 0) {
+		r = ret;
+		goto done;
+	}
+
+	if (!dev->rx_req[0]) {
+		pr_warn("acc_read: USB request already handled/freed");
+		r = -EINVAL;
+		goto done;
+	}
+
+	/*
+	 * Calculate the data length by considering termination character.
+	 * Then compansite the difference of rounding up to
+	 * integer multiple of maxpacket size.
+	 */
+	data_length = count;
+	data_length += dev->ep_out->maxpacket - 1;
+	data_length -= data_length % dev->ep_out->maxpacket;
+
+	if (dev->rx_done) {
+		// last req cancelled. try to get it.
+		req = dev->rx_req[0];
+		goto copy_data;
+	}
+
+requeue_req:
+	/* queue a request */
+	req = dev->rx_req[0];
+	req->length = data_length;
+	dev->rx_done = 0;
+	ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+	if (ret < 0) {
+		r = -EIO;
+		goto done;
+	} else {
+		pr_debug("rx %p queue\n", req);
+	}
+
+	/* wait for a request to complete */
+	ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+	if (ret < 0) {
+		r = ret;
+		ret = usb_ep_dequeue(dev->ep_out, req);
+		if (ret != 0) {
+			// cancel failed. There can be a data already received.
+			// it will be retrieved in the next read.
+			pr_debug("acc_read: cancelling failed %d", ret);
+		}
+		goto done;
+	}
+
+copy_data:
+	dev->rx_done = 0;
+	if (dev->online) {
+		/* If we got a 0-len packet, throw it back and try again. */
+		if (req->actual == 0)
+			goto requeue_req;
+
+		pr_debug("rx %p %u\n", req, req->actual);
+		xfer = (req->actual < count) ? req->actual : count;
+		r = xfer;
+		if (copy_to_user(buf, req->buf, xfer))
+			r = -EFAULT;
+	} else
+		r = -EIO;
+
+done:
+	pr_debug("acc_read returning %zd\n", r);
+	return r;
+}
+
+static ssize_t acc_write(struct file *fp, const char __user *buf,
+	size_t count, loff_t *pos)
+{
+	struct acc_dev *dev = fp->private_data;
+	struct usb_request *req = 0;
+	ssize_t r = count;
+	unsigned xfer;
+	int ret;
+
+	pr_debug("acc_write(%zu)\n", count);
+
+	if (!dev->online || dev->disconnected) {
+		pr_debug("acc_write disconnected or not online");
+		return -ENODEV;
+	}
+
+	while (count > 0) {
+		if (!dev->online) {
+			pr_debug("acc_write dev->error\n");
+			r = -EIO;
+			break;
+		}
+
+		/* get an idle tx request to use */
+		req = 0;
+		ret = wait_event_interruptible(dev->write_wq,
+			((req = req_get(dev, &dev->tx_idle)) || !dev->online));
+		if (!req) {
+			r = ret;
+			break;
+		}
+
+		if (count > BULK_BUFFER_SIZE) {
+			xfer = BULK_BUFFER_SIZE;
+			/* ZLP, They will be more TX requests so not yet. */
+			req->zero = 0;
+		} else {
+			xfer = count;
+			/* If the data length is a multple of the
+			 * maxpacket size then send a zero length packet(ZLP).
+			*/
+			req->zero = ((xfer % dev->ep_in->maxpacket) == 0);
+		}
+		if (copy_from_user(req->buf, buf, xfer)) {
+			r = -EFAULT;
+			break;
+		}
+
+		req->length = xfer;
+		ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+		if (ret < 0) {
+			pr_debug("acc_write: xfer error %d\n", ret);
+			r = -EIO;
+			break;
+		}
+
+		buf += xfer;
+		count -= xfer;
+
+		/* zero this so we don't try to free it on error exit */
+		req = 0;
+	}
+
+	if (req)
+		req_put(dev, &dev->tx_idle, req);
+
+	pr_debug("acc_write returning %zd\n", r);
+	return r;
+}
+
+static long acc_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+	struct acc_dev *dev = fp->private_data;
+	char *src = NULL;
+	int ret;
+
+	switch (code) {
+	case ACCESSORY_GET_STRING_MANUFACTURER:
+		src = dev->manufacturer;
+		break;
+	case ACCESSORY_GET_STRING_MODEL:
+		src = dev->model;
+		break;
+	case ACCESSORY_GET_STRING_DESCRIPTION:
+		src = dev->description;
+		break;
+	case ACCESSORY_GET_STRING_VERSION:
+		src = dev->version;
+		break;
+	case ACCESSORY_GET_STRING_URI:
+		src = dev->uri;
+		break;
+	case ACCESSORY_GET_STRING_SERIAL:
+		src = dev->serial;
+		break;
+	case ACCESSORY_IS_START_REQUESTED:
+		return dev->start_requested;
+	case ACCESSORY_GET_AUDIO_MODE:
+		return dev->audio_mode;
+	}
+	if (!src)
+		return -EINVAL;
+
+	ret = strlen(src) + 1;
+	if (copy_to_user((void __user *)value, src, ret))
+		ret = -EFAULT;
+	return ret;
+}
+
+static int acc_open(struct inode *ip, struct file *fp)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	if (!dev)
+		return -ENODEV;
+
+	if (atomic_xchg(&dev->open_excl, 1)) {
+		put_acc_dev(dev);
+		return -EBUSY;
+	}
+
+	dev->disconnected = 0;
+	fp->private_data = dev;
+	return 0;
+}
+
+static int acc_release(struct inode *ip, struct file *fp)
+{
+	struct acc_dev *dev = fp->private_data;
+
+	if (!dev)
+		return -ENOENT;
+
+	/* indicate that we are disconnected
+	 * still could be online so don't touch online flag
+	 */
+	dev->disconnected = 1;
+
+	fp->private_data = NULL;
+	WARN_ON(!atomic_xchg(&dev->open_excl, 0));
+	put_acc_dev(dev);
+	return 0;
+}
+
+/* file operations for /dev/usb_accessory */
+static const struct file_operations acc_fops = {
+	.owner = THIS_MODULE,
+	.read = acc_read,
+	.write = acc_write,
+	.unlocked_ioctl = acc_ioctl,
+	.compat_ioctl = acc_ioctl,
+	.open = acc_open,
+	.release = acc_release,
+};
+
+static int acc_hid_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+	return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+}
+
+static struct miscdevice acc_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "usb_accessory",
+	.fops = &acc_fops,
+};
+
+static const struct hid_device_id acc_hid_table[] = {
+	{ HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
+	{ }
+};
+
+static struct hid_driver acc_hid_driver = {
+	.name = "USB accessory",
+	.id_table = acc_hid_table,
+	.probe = acc_hid_probe,
+};
+
+static void acc_complete_setup_noop(struct usb_ep *ep, struct usb_request *req)
+{
+	/*
+	 * Default no-op function when nothing needs to be done for the
+	 * setup request
+	 */
+}
+
+int acc_ctrlrequest(struct usb_composite_dev *cdev,
+				const struct usb_ctrlrequest *ctrl)
+{
+	struct acc_dev	*dev = get_acc_dev();
+	int	value = -EOPNOTSUPP;
+	struct acc_hid_dev *hid;
+	int offset;
+	u8 b_requestType = ctrl->bRequestType;
+	u8 b_request = ctrl->bRequest;
+	u16	w_index = le16_to_cpu(ctrl->wIndex);
+	u16	w_value = le16_to_cpu(ctrl->wValue);
+	u16	w_length = le16_to_cpu(ctrl->wLength);
+	unsigned long flags;
+
+	/*
+	 * If instance is not created which is the case in power off charging
+	 * mode, dev will be NULL. Hence return error if it is the case.
+	 */
+	if (!dev)
+		return -ENODEV;
+
+	if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) {
+		if (b_request == ACCESSORY_START) {
+			dev->start_requested = 1;
+			schedule_delayed_work(
+				&dev->start_work, msecs_to_jiffies(10));
+			value = 0;
+			cdev->req->complete = acc_complete_setup_noop;
+		} else if (b_request == ACCESSORY_SEND_STRING) {
+			dev->string_index = w_index;
+			cdev->gadget->ep0->driver_data = dev;
+			cdev->req->complete = acc_complete_set_string;
+			value = w_length;
+		} else if (b_request == ACCESSORY_SET_AUDIO_MODE &&
+				w_index == 0 && w_length == 0) {
+			dev->audio_mode = w_value;
+			cdev->req->complete = acc_complete_setup_noop;
+			value = 0;
+		} else if (b_request == ACCESSORY_REGISTER_HID) {
+			cdev->req->complete = acc_complete_setup_noop;
+			value = acc_register_hid(dev, w_value, w_index);
+		} else if (b_request == ACCESSORY_UNREGISTER_HID) {
+			cdev->req->complete = acc_complete_setup_noop;
+			value = acc_unregister_hid(dev, w_value);
+		} else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) {
+			spin_lock_irqsave(&dev->lock, flags);
+			hid = acc_hid_get(&dev->new_hid_list, w_value);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			if (!hid) {
+				value = -EINVAL;
+				goto err;
+			}
+			offset = w_index;
+			if (offset != hid->report_desc_offset
+				|| offset + w_length > hid->report_desc_len) {
+				value = -EINVAL;
+				goto err;
+			}
+			cdev->req->context = hid;
+			cdev->req->complete = acc_complete_set_hid_report_desc;
+			value = w_length;
+		} else if (b_request == ACCESSORY_SEND_HID_EVENT) {
+			spin_lock_irqsave(&dev->lock, flags);
+			hid = acc_hid_get(&dev->hid_list, w_value);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			if (!hid) {
+				value = -EINVAL;
+				goto err;
+			}
+			cdev->req->context = hid;
+			cdev->req->complete = acc_complete_send_hid_event;
+			value = w_length;
+		}
+	} else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) {
+		if (b_request == ACCESSORY_GET_PROTOCOL) {
+			*((u16 *)cdev->req->buf) = PROTOCOL_VERSION;
+			value = sizeof(u16);
+			cdev->req->complete = acc_complete_setup_noop;
+			/* clear any string left over from a previous session */
+			memset(dev->manufacturer, 0, sizeof(dev->manufacturer));
+			memset(dev->model, 0, sizeof(dev->model));
+			memset(dev->description, 0, sizeof(dev->description));
+			memset(dev->version, 0, sizeof(dev->version));
+			memset(dev->uri, 0, sizeof(dev->uri));
+			memset(dev->serial, 0, sizeof(dev->serial));
+			dev->start_requested = 0;
+			dev->audio_mode = 0;
+		}
+	}
+
+	if (value >= 0) {
+		cdev->req->zero = 0;
+		cdev->req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "%s setup response queue error\n",
+				__func__);
+	}
+
+err:
+	if (value == -EOPNOTSUPP)
+		VDBG(cdev,
+			"unknown class-specific control req "
+			"%02x.%02x v%04x i%04x l%u\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	put_acc_dev(dev);
+	return value;
+}
+EXPORT_SYMBOL_GPL(acc_ctrlrequest);
+
+int acc_ctrlrequest_composite(struct usb_composite_dev *cdev,
+			      const struct usb_ctrlrequest *ctrl)
+{
+	u16 w_length = le16_to_cpu(ctrl->wLength);
+
+	if (w_length > USB_COMP_EP0_BUFSIZ) {
+		if (ctrl->bRequestType & USB_DIR_IN) {
+			/* Cast away the const, we are going to overwrite on purpose. */
+			__le16 *temp = (__le16 *)&ctrl->wLength;
+
+			*temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ);
+			w_length = USB_COMP_EP0_BUFSIZ;
+		} else {
+			return -EINVAL;
+		}
+	}
+	return acc_ctrlrequest(cdev, ctrl);
+}
+EXPORT_SYMBOL_GPL(acc_ctrlrequest_composite);
+
+static int
+__acc_function_bind(struct usb_configuration *c,
+			struct usb_function *f, bool configfs)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct acc_dev	*dev = func_to_dev(f);
+	int			id;
+	int			ret;
+
+	DBG(cdev, "acc_function_bind dev: %p\n", dev);
+
+	if (configfs) {
+		if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+			ret = usb_string_id(c->cdev);
+			if (ret < 0)
+				return ret;
+			acc_string_defs[INTERFACE_STRING_INDEX].id = ret;
+			acc_interface_desc.iInterface = ret;
+		}
+		dev->cdev = c->cdev;
+	}
+	ret = hid_register_driver(&acc_hid_driver);
+	if (ret)
+		return ret;
+
+	dev->start_requested = 0;
+
+	/* allocate interface ID(s) */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	acc_interface_desc.bInterfaceNumber = id;
+
+	/* allocate endpoints */
+	ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc,
+			&acc_fullspeed_out_desc);
+	if (ret)
+		return ret;
+
+	/* support high speed hardware */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		acc_highspeed_in_desc.bEndpointAddress =
+			acc_fullspeed_in_desc.bEndpointAddress;
+		acc_highspeed_out_desc.bEndpointAddress =
+			acc_fullspeed_out_desc.bEndpointAddress;
+	}
+
+	DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			f->name, dev->ep_in->name, dev->ep_out->name);
+	return 0;
+}
+
+static int
+acc_function_bind_configfs(struct usb_configuration *c,
+			struct usb_function *f) {
+	return __acc_function_bind(c, f, true);
+}
+
+static void
+kill_all_hid_devices(struct acc_dev *dev)
+{
+	struct acc_hid_dev *hid;
+	struct list_head *entry, *temp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	list_for_each_safe(entry, temp, &dev->hid_list) {
+		hid = list_entry(entry, struct acc_hid_dev, list);
+		list_del(&hid->list);
+		list_add(&hid->list, &dev->dead_hid_list);
+	}
+	list_for_each_safe(entry, temp, &dev->new_hid_list) {
+		hid = list_entry(entry, struct acc_hid_dev, list);
+		list_del(&hid->list);
+		list_add(&hid->list, &dev->dead_hid_list);
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	schedule_work(&dev->hid_work);
+}
+
+static void
+acc_hid_unbind(struct acc_dev *dev)
+{
+	hid_unregister_driver(&acc_hid_driver);
+	kill_all_hid_devices(dev);
+}
+
+static void
+acc_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct acc_dev	*dev = func_to_dev(f);
+	struct usb_request *req;
+	int i;
+
+	dev->online = 0;		/* clear online flag */
+	wake_up(&dev->read_wq);		/* unblock reads on closure */
+	wake_up(&dev->write_wq);	/* likewise for writes */
+
+	while ((req = req_get(dev, &dev->tx_idle)))
+		acc_request_free(req, dev->ep_in);
+	for (i = 0; i < RX_REQ_MAX; i++) {
+		acc_request_free(dev->rx_req[i], dev->ep_out);
+		dev->rx_req[i] = NULL;
+	}
+
+	acc_hid_unbind(dev);
+}
+
+static void acc_start_work(struct work_struct *data)
+{
+	char *envp[2] = { "ACCESSORY=START", NULL };
+
+	kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp);
+}
+
+static int acc_hid_init(struct acc_hid_dev *hdev)
+{
+	struct hid_device *hid;
+	int ret;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid))
+		return PTR_ERR(hid);
+
+	hid->ll_driver = &acc_hid_ll_driver;
+	hid->dev.parent = acc_device.this_device;
+
+	hid->bus = BUS_USB;
+	hid->vendor = HID_ANY_ID;
+	hid->product = HID_ANY_ID;
+	hid->driver_data = hdev;
+	ret = hid_add_device(hid);
+	if (ret) {
+		pr_err("can't add hid device: %d\n", ret);
+		hid_destroy_device(hid);
+		return ret;
+	}
+
+	hdev->hid = hid;
+	return 0;
+}
+
+static void acc_hid_delete(struct acc_hid_dev *hid)
+{
+	kfree(hid->report_desc);
+	kfree(hid);
+}
+
+static void acc_hid_work(struct work_struct *data)
+{
+	struct acc_dev *dev = get_acc_dev();
+	struct list_head	*entry, *temp;
+	struct acc_hid_dev *hid;
+	struct list_head	new_list, dead_list;
+	unsigned long flags;
+
+	if (!dev)
+		return;
+
+	INIT_LIST_HEAD(&new_list);
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	/* copy hids that are ready for initialization to new_list */
+	list_for_each_safe(entry, temp, &dev->new_hid_list) {
+		hid = list_entry(entry, struct acc_hid_dev, list);
+		if (hid->report_desc_offset == hid->report_desc_len)
+			list_move(&hid->list, &new_list);
+	}
+
+	if (list_empty(&dev->dead_hid_list)) {
+		INIT_LIST_HEAD(&dead_list);
+	} else {
+		/* move all of dev->dead_hid_list to dead_list */
+		dead_list.prev = dev->dead_hid_list.prev;
+		dead_list.next = dev->dead_hid_list.next;
+		dead_list.next->prev = &dead_list;
+		dead_list.prev->next = &dead_list;
+		INIT_LIST_HEAD(&dev->dead_hid_list);
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	/* register new HID devices */
+	list_for_each_safe(entry, temp, &new_list) {
+		hid = list_entry(entry, struct acc_hid_dev, list);
+		if (acc_hid_init(hid)) {
+			pr_err("can't add HID device %p\n", hid);
+			acc_hid_delete(hid);
+		} else {
+			spin_lock_irqsave(&dev->lock, flags);
+			list_move(&hid->list, &dev->hid_list);
+			spin_unlock_irqrestore(&dev->lock, flags);
+		}
+	}
+
+	/* remove dead HID devices */
+	list_for_each_safe(entry, temp, &dead_list) {
+		hid = list_entry(entry, struct acc_hid_dev, list);
+		list_del(&hid->list);
+		if (hid->hid)
+			hid_destroy_device(hid->hid);
+		acc_hid_delete(hid);
+	}
+
+	put_acc_dev(dev);
+}
+
+static int acc_function_set_alt(struct usb_function *f,
+		unsigned intf, unsigned alt)
+{
+	struct acc_dev	*dev = func_to_dev(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int ret;
+
+	DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt);
+
+	ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in);
+	if (ret)
+		return ret;
+
+	ret = usb_ep_enable(dev->ep_in);
+	if (ret)
+		return ret;
+
+	ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out);
+	if (ret)
+		return ret;
+
+	ret = usb_ep_enable(dev->ep_out);
+	if (ret) {
+		usb_ep_disable(dev->ep_in);
+		return ret;
+	}
+
+	dev->online = 1;
+	dev->disconnected = 0; /* if online then not disconnected */
+
+	/* readers may be blocked waiting for us to go online */
+	wake_up(&dev->read_wq);
+	return 0;
+}
+
+static void acc_function_disable(struct usb_function *f)
+{
+	struct acc_dev	*dev = func_to_dev(f);
+	struct usb_composite_dev	*cdev = dev->cdev;
+
+	DBG(cdev, "acc_function_disable\n");
+	acc_set_disconnected(dev); /* this now only sets disconnected */
+	dev->online = 0; /* so now need to clear online flag here too */
+	usb_ep_disable(dev->ep_in);
+	usb_ep_disable(dev->ep_out);
+
+	/* readers may be blocked waiting for us to go online */
+	wake_up(&dev->read_wq);
+
+	VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int acc_setup(void)
+{
+	struct acc_dev_ref *ref = &_acc_dev_ref;
+	struct acc_dev *dev;
+	int ret;
+
+	if (kref_read(&ref->kref))
+		return -EBUSY;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	spin_lock_init(&dev->lock);
+	init_waitqueue_head(&dev->read_wq);
+	init_waitqueue_head(&dev->write_wq);
+	atomic_set(&dev->open_excl, 0);
+	INIT_LIST_HEAD(&dev->tx_idle);
+	INIT_LIST_HEAD(&dev->hid_list);
+	INIT_LIST_HEAD(&dev->new_hid_list);
+	INIT_LIST_HEAD(&dev->dead_hid_list);
+	INIT_DELAYED_WORK(&dev->start_work, acc_start_work);
+	INIT_WORK(&dev->hid_work, acc_hid_work);
+
+	dev->ref = ref;
+	if (cmpxchg_relaxed(&ref->acc_dev, NULL, dev)) {
+		ret = -EBUSY;
+		goto err_free_dev;
+	}
+
+	ret = misc_register(&acc_device);
+	if (ret)
+		goto err_zap_ptr;
+
+	kref_init(&ref->kref);
+	return 0;
+
+err_zap_ptr:
+	ref->acc_dev = NULL;
+err_free_dev:
+	kfree(dev);
+	pr_err("USB accessory gadget driver failed to initialize\n");
+	return ret;
+}
+
+void acc_disconnect(void)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	if (!dev)
+		return;
+
+	/* unregister all HID devices if USB is disconnected */
+	kill_all_hid_devices(dev);
+	put_acc_dev(dev);
+}
+EXPORT_SYMBOL_GPL(acc_disconnect);
+
+static void acc_cleanup(void)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	misc_deregister(&acc_device);
+	put_acc_dev(dev);
+	put_acc_dev(dev); /* Pairs with kref_init() in acc_setup() */
+}
+static struct acc_instance *to_acc_instance(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct acc_instance,
+		func_inst.group);
+}
+
+static void acc_attr_release(struct config_item *item)
+{
+	struct acc_instance *fi_acc = to_acc_instance(item);
+
+	usb_put_function_instance(&fi_acc->func_inst);
+}
+
+static struct configfs_item_operations acc_item_ops = {
+	.release        = acc_attr_release,
+};
+
+static struct config_item_type acc_func_type = {
+	.ct_item_ops    = &acc_item_ops,
+	.ct_owner       = THIS_MODULE,
+};
+
+static struct acc_instance *to_fi_acc(struct usb_function_instance *fi)
+{
+	return container_of(fi, struct acc_instance, func_inst);
+}
+
+static int acc_set_inst_name(struct usb_function_instance *fi, const char *name)
+{
+	struct acc_instance *fi_acc;
+	char *ptr;
+	int name_len;
+
+	name_len = strlen(name) + 1;
+	if (name_len > MAX_INST_NAME_LEN)
+		return -ENAMETOOLONG;
+
+	ptr = kstrndup(name, name_len, GFP_KERNEL);
+	if (!ptr)
+		return -ENOMEM;
+
+	fi_acc = to_fi_acc(fi);
+	fi_acc->name = ptr;
+	return 0;
+}
+
+static void acc_free_inst(struct usb_function_instance *fi)
+{
+	struct acc_instance *fi_acc;
+
+	fi_acc = to_fi_acc(fi);
+	kfree(fi_acc->name);
+	acc_cleanup();
+}
+
+static struct usb_function_instance *acc_alloc_inst(void)
+{
+	struct acc_instance *fi_acc;
+	int err;
+
+	fi_acc = kzalloc(sizeof(*fi_acc), GFP_KERNEL);
+	if (!fi_acc)
+		return ERR_PTR(-ENOMEM);
+	fi_acc->func_inst.set_inst_name = acc_set_inst_name;
+	fi_acc->func_inst.free_func_inst = acc_free_inst;
+
+	err = acc_setup();
+	if (err) {
+		kfree(fi_acc);
+		return ERR_PTR(err);
+	}
+
+	config_group_init_type_name(&fi_acc->func_inst.group,
+					"", &acc_func_type);
+	return  &fi_acc->func_inst;
+}
+
+static void acc_free(struct usb_function *f)
+{
+	struct acc_dev *dev = func_to_dev(f);
+
+	put_acc_dev(dev);
+}
+
+int acc_ctrlrequest_configfs(struct usb_function *f,
+			const struct usb_ctrlrequest *ctrl) {
+	if (f->config != NULL && f->config->cdev != NULL)
+		return acc_ctrlrequest(f->config->cdev, ctrl);
+	else
+		return -1;
+}
+
+static struct usb_function *acc_alloc(struct usb_function_instance *fi)
+{
+	struct acc_dev *dev = get_acc_dev();
+
+	dev->function.name = "accessory";
+	dev->function.strings = acc_strings,
+	dev->function.fs_descriptors = fs_acc_descs;
+	dev->function.hs_descriptors = hs_acc_descs;
+	dev->function.bind = acc_function_bind_configfs;
+	dev->function.unbind = acc_function_unbind;
+	dev->function.set_alt = acc_function_set_alt;
+	dev->function.disable = acc_function_disable;
+	dev->function.free_func = acc_free;
+	dev->function.setup = acc_ctrlrequest_configfs;
+
+	return &dev->function;
+}
+DECLARE_USB_FUNCTION_INIT(accessory, acc_alloc_inst, acc_alloc);
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_acm.c b/marvell/linux/drivers/usb/gadget/function/f_acm.c
new file mode 100644
index 0000000..1c46757
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_acm.c
@@ -0,0 +1,848 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_acm.c -- USB CDC serial (ACM) function driver
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 2009 by Samsung Electronics
+ * Author: Michal Nazarewicz (mina86@mina86.com)
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+
+#include "u_serial.h"
+#include "gadget_chips.h"
+
+
+/*
+ * This CDC ACM function support just wraps control functions and
+ * notifications around the generic serial-over-usb code.
+ *
+ * Because CDC ACM is standardized by the USB-IF, many host operating
+ * systems have drivers for it.  Accordingly, ACM is the preferred
+ * interop solution for serial-port type connections.  The control
+ * models are often not necessary, and in any case don't do much in
+ * this bare-bones implementation.
+ *
+ * Note that even MS-Windows has some support for ACM.  However, that
+ * support is somewhat broken because when you use ACM in a composite
+ * device, having multiple interfaces confuses the poor OS.  It doesn't
+ * seem to understand CDC Union descriptors.  The new "association"
+ * descriptors (roughly equivalent to CDC Unions) may sometimes help.
+ */
+
+struct f_acm {
+	struct gserial			port;
+	u8				ctrl_id, data_id;
+	u8				port_num;
+
+	u8				pending;
+
+	/* lock is mostly for pending and notify_req ... they get accessed
+	 * by callbacks both from tty (open/close/break) under its spinlock,
+	 * and notify_req.complete() which can't use that lock.
+	 */
+	spinlock_t			lock;
+
+	struct usb_ep			*notify;
+	struct usb_request		*notify_req;
+
+	struct usb_cdc_line_coding	port_line_coding;	/* 8-N-1 etc */
+
+	/* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */
+	u16				port_handshake_bits;
+#define ACM_CTRL_RTS	(1 << 1)	/* unused with full duplex */
+#define ACM_CTRL_DTR	(1 << 0)	/* host is ready for data r/w */
+
+	/* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */
+	u16				serial_state;
+#define ACM_CTRL_OVERRUN	(1 << 6)
+#define ACM_CTRL_PARITY		(1 << 5)
+#define ACM_CTRL_FRAMING	(1 << 4)
+#define ACM_CTRL_RI		(1 << 3)
+#define ACM_CTRL_BRK		(1 << 2)
+#define ACM_CTRL_DSR		(1 << 1)
+#define ACM_CTRL_DCD		(1 << 0)
+};
+
+static inline struct f_acm *func_to_acm(struct usb_function *f)
+{
+	return container_of(f, struct f_acm, port.func);
+}
+
+static inline struct f_acm *port_to_acm(struct gserial *p)
+{
+	return container_of(p, struct f_acm, port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* notification endpoint uses smallish and infrequent fixed-size messages */
+
+#define GS_NOTIFY_INTERVAL_MS		32
+#define GS_NOTIFY_MAXPACKET		10	/* notification + 2 bytes */
+#define ACM_INTERFACE_NUMBER		2
+
+/* interface and class descriptors: */
+
+static struct usb_interface_assoc_descriptor
+acm_iad_descriptor = {
+	.bLength =		sizeof acm_iad_descriptor,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount = 	2,	// control + data
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_ACM,
+	.bFunctionProtocol =	USB_CDC_ACM_PROTO_AT_V25TER,
+	/* .iFunction =		DYNAMIC */
+};
+
+
+static struct usb_interface_descriptor acm_control_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_ACM,
+	.bInterfaceProtocol =	USB_CDC_ACM_PROTO_AT_V25TER,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor acm_data_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc acm_header_desc = {
+	.bLength =		sizeof(acm_header_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor
+acm_call_mgmt_descriptor = {
+	.bLength =		sizeof(acm_call_mgmt_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_CALL_MANAGEMENT_TYPE,
+	.bmCapabilities =	0,
+	/* .bDataInterface = DYNAMIC */
+};
+
+static struct usb_cdc_acm_descriptor acm_descriptor = {
+	.bLength =		sizeof(acm_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ACM_TYPE,
+	.bmCapabilities =	USB_CDC_CAP_LINE,
+};
+
+static struct usb_cdc_union_desc acm_union_desc = {
+	.bLength =		sizeof(acm_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor acm_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET),
+	.bInterval =		GS_NOTIFY_INTERVAL_MS,
+};
+
+static struct usb_endpoint_descriptor acm_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor acm_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *acm_fs_function[] = {
+	(struct usb_descriptor_header *) &acm_iad_descriptor,
+	(struct usb_descriptor_header *) &acm_control_interface_desc,
+	(struct usb_descriptor_header *) &acm_header_desc,
+	(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &acm_descriptor,
+	(struct usb_descriptor_header *) &acm_union_desc,
+	(struct usb_descriptor_header *) &acm_fs_notify_desc,
+	(struct usb_descriptor_header *) &acm_data_interface_desc,
+	(struct usb_descriptor_header *) &acm_fs_in_desc,
+	(struct usb_descriptor_header *) &acm_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+static struct usb_endpoint_descriptor acm_hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS),
+};
+
+static struct usb_endpoint_descriptor acm_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acm_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *acm_hs_function[] = {
+	(struct usb_descriptor_header *) &acm_iad_descriptor,
+	(struct usb_descriptor_header *) &acm_control_interface_desc,
+	(struct usb_descriptor_header *) &acm_header_desc,
+	(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &acm_descriptor,
+	(struct usb_descriptor_header *) &acm_union_desc,
+	(struct usb_descriptor_header *) &acm_hs_notify_desc,
+	(struct usb_descriptor_header *) &acm_data_interface_desc,
+	(struct usb_descriptor_header *) &acm_hs_in_desc,
+	(struct usb_descriptor_header *) &acm_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor acm_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor acm_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor acm_ss_bulk_comp_desc = {
+	.bLength =              sizeof acm_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *acm_ss_function[] = {
+	(struct usb_descriptor_header *) &acm_iad_descriptor,
+	(struct usb_descriptor_header *) &acm_control_interface_desc,
+	(struct usb_descriptor_header *) &acm_header_desc,
+	(struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &acm_descriptor,
+	(struct usb_descriptor_header *) &acm_union_desc,
+	(struct usb_descriptor_header *) &acm_hs_notify_desc,
+	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &acm_data_interface_desc,
+	(struct usb_descriptor_header *) &acm_ss_in_desc,
+	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &acm_ss_out_desc,
+	(struct usb_descriptor_header *) &acm_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+#define ACM_CTRL_IDX	0
+#define ACM_DATA_IDX	1
+#define ACM_IAD_IDX	2
+
+/* static strings, in UTF-8 */
+static struct usb_string acm_string_defs[] = {
+	[ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",
+	[ACM_DATA_IDX].s = "CDC ACM Data",
+	[ACM_IAD_IDX ].s = "CDC Serial",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings acm_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		acm_string_defs,
+};
+
+static struct usb_gadget_strings *acm_strings[] = {
+	&acm_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM control ... data handling is delegated to tty library code.
+ * The main task of this function is to activate and deactivate
+ * that code based on device state; track parameters like line
+ * speed, handshake state, and so on; and issue notifications.
+ */
+
+static void acm_complete_set_line_coding(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct f_acm	*acm = ep->driver_data;
+	struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+
+	if (req->status != 0) {
+		dev_info(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n",
+			acm->port_num, req->status);
+		return;
+	}
+
+	/* normal completion */
+	if (req->actual != sizeof(acm->port_line_coding)) {
+		dev_info(&cdev->gadget->dev, "acm ttyGS%d short resp, len %d\n",
+			acm->port_num, req->actual);
+		usb_ep_set_halt(ep);
+	} else {
+		struct usb_cdc_line_coding	*value = req->buf;
+
+		/* REVISIT:  we currently just remember this data.
+		 * If we change that, (a) validate it first, then
+		 * (b) update whatever hardware needs updating,
+		 * (c) worry about locking.  This is information on
+		 * the order of 9600-8-N-1 ... most of which means
+		 * nothing unless we control a real RS232 line.
+		 */
+		acm->port_line_coding = *value;
+	}
+}
+
+static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_acm		*acm = func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 *
+	 * Note CDC spec table 4 lists the ACM request profile.  It requires
+	 * encapsulated command support ... we don't handle any, and respond
+	 * to them by stalling.  Options include get/set/clear comm features
+	 * (not that useful) and SEND_BREAK.
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+	/* SET_LINE_CODING ... just read and save what the host sends */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_LINE_CODING:
+		if (w_length != sizeof(struct usb_cdc_line_coding)
+				|| w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = w_length;
+		cdev->gadget->ep0->driver_data = acm;
+		req->complete = acm_complete_set_line_coding;
+		break;
+
+	/* GET_LINE_CODING ... return what host sent, or initial value */
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_GET_LINE_CODING:
+		if (w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = min_t(unsigned, w_length,
+				sizeof(struct usb_cdc_line_coding));
+		memcpy(req->buf, &acm->port_line_coding, value);
+		break;
+
+	/* SET_CONTROL_LINE_STATE ... save what the host sent */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+		if (w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = 0;
+
+		/* FIXME we should not allow data to flow until the
+		 * host sets the ACM_CTRL_DTR bit; and when it clears
+		 * that bit, we should return to that no-flow state.
+		 */
+		acm->port_handshake_bits = w_value;
+		break;
+
+	default:
+invalid:
+		dev_info(&cdev->gadget->dev,
+			 "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			 ctrl->bRequestType, ctrl->bRequest,
+			 w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		dev_info(&cdev->gadget->dev,
+			"acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
+			acm->port_num, ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "acm response on ttyGS%d, err %d\n",
+					acm->port_num, value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_acm		*acm = func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (intf == acm->ctrl_id) {
+		dev_vdbg(&cdev->gadget->dev,
+				"reset acm control interface %d\n", intf);
+		usb_ep_disable(acm->notify);
+
+		if (!acm->notify->desc)
+			if (config_ep_by_speed(cdev->gadget, f, acm->notify))
+				return -EINVAL;
+
+		usb_ep_enable(acm->notify);
+
+	} else if (intf == acm->data_id) {
+		if (acm->notify->enabled) {
+			dev_dbg(&cdev->gadget->dev,
+				"reset acm ttyGS%d\n", acm->port_num);
+			gserial_disconnect(&acm->port);
+		}
+		if (!acm->port.in->desc || !acm->port.out->desc) {
+			dev_dbg(&cdev->gadget->dev,
+				"activate acm ttyGS%d\n", acm->port_num);
+			if (config_ep_by_speed(cdev->gadget, f,
+					       acm->port.in) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       acm->port.out)) {
+				acm->port.in->desc = NULL;
+				acm->port.out->desc = NULL;
+				return -EINVAL;
+			}
+		}
+		gserial_connect(&acm->port, acm->port_num);
+
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+static void acm_disable(struct usb_function *f)
+{
+	struct f_acm	*acm = func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	dev_info(&cdev->gadget->dev, "acm ttyGS%d deactivated\n", acm->port_num);
+	gserial_disconnect(&acm->port);
+	usb_ep_disable(acm->notify);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * acm_cdc_notify - issue CDC notification to host
+ * @acm: wraps host to be notified
+ * @type: notification type
+ * @value: Refer to cdc specs, wValue field.
+ * @data: data to be sent
+ * @length: size of data
+ * Context: irqs blocked, acm->lock held, acm_notify_req non-null
+ *
+ * Returns zero on success or a negative errno.
+ *
+ * See section 6.3.5 of the CDC 1.1 specification for information
+ * about the only notification we issue:  SerialState change.
+ */
+static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value,
+		void *data, unsigned length)
+{
+	struct usb_ep			*ep = acm->notify;
+	struct usb_request		*req;
+	struct usb_cdc_notification	*notify;
+	const unsigned			len = sizeof(*notify) + length;
+	void				*buf;
+	int				status;
+
+	req = acm->notify_req;
+	acm->notify_req = NULL;
+	acm->pending = false;
+
+	req->length = len;
+	notify = req->buf;
+	buf = notify + 1;
+
+	notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+			| USB_RECIP_INTERFACE;
+	notify->bNotificationType = type;
+	notify->wValue = cpu_to_le16(value);
+	notify->wIndex = cpu_to_le16(acm->ctrl_id);
+	notify->wLength = cpu_to_le16(length);
+	memcpy(buf, data, length);
+
+	/* ep_queue() can complete immediately if it fills the fifo... */
+	spin_unlock(&acm->lock);
+	status = usb_ep_queue(ep, req, GFP_ATOMIC);
+	spin_lock(&acm->lock);
+
+	if (status < 0) {
+		ERROR(acm->port.func.config->cdev,
+				"acm ttyGS%d can't notify serial state, %d\n",
+				acm->port_num, status);
+		acm->notify_req = req;
+	}
+
+	return status;
+}
+
+static int acm_notify_serial_state(struct f_acm *acm)
+{
+	struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+	int			status;
+	__le16			serial_state;
+
+	spin_lock(&acm->lock);
+	if (acm->notify_req) {
+		dev_info(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n",
+			acm->port_num, acm->serial_state);
+		serial_state = cpu_to_le16(acm->serial_state);
+		status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
+				0, &serial_state, sizeof(acm->serial_state));
+	} else {
+		acm->pending = true;
+		status = 0;
+	}
+	spin_unlock(&acm->lock);
+	return status;
+}
+
+static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_acm		*acm = req->context;
+	u8			doit = false;
+
+	/* on this call path we do NOT hold the port spinlock,
+	 * which is why ACM needs its own spinlock
+	 */
+	spin_lock(&acm->lock);
+	if (req->status != -ESHUTDOWN)
+		doit = acm->pending;
+	acm->notify_req = req;
+	spin_unlock(&acm->lock);
+
+	if (doit)
+		acm_notify_serial_state(acm);
+}
+
+/* connect == the TTY link is open */
+
+static void acm_connect(struct gserial *port)
+{
+	struct f_acm		*acm = port_to_acm(port);
+
+	acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
+	acm_notify_serial_state(acm);
+}
+
+static void acm_disconnect(struct gserial *port)
+{
+	struct f_acm		*acm = port_to_acm(port);
+
+	acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
+	acm_notify_serial_state(acm);
+}
+
+static int acm_send_break(struct gserial *port, int duration)
+{
+	struct f_acm		*acm = port_to_acm(port);
+	u16			state;
+
+	state = acm->serial_state;
+	state &= ~ACM_CTRL_BRK;
+	if (duration)
+		state |= ACM_CTRL_BRK;
+
+	acm->serial_state = state;
+	return acm_notify_serial_state(acm);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM function driver setup/binding */
+static int
+acm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_acm		*acm = func_to_acm(f);
+	struct usb_string	*us;
+	int			status;
+	struct usb_ep		*ep;
+
+	/* REVISIT might want instance-specific strings to help
+	 * distinguish instances ...
+	 */
+
+	if (acm_string_defs[0].id == 0) {
+		/* maybe allocate device-global string IDs, and patch descriptors */
+		us = usb_gstrings_attach(cdev, acm_strings,
+				ARRAY_SIZE(acm_string_defs));
+		if (IS_ERR(us)) {
+			ERROR(cdev, "usb_gstrings_attach failed %ld\n", PTR_ERR(us));
+			return PTR_ERR(us);
+		}
+		acm_string_defs[0].id = us->id;
+
+		acm_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id;
+		acm_data_interface_desc.iInterface = us[ACM_DATA_IDX].id;
+		acm_iad_descriptor.iFunction = us[ACM_IAD_IDX].id;
+
+	}
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0) {
+		ERROR(cdev, "usb_interface_id failed %d\n", status);
+		goto fail;
+	}
+
+	acm->ctrl_id = status;
+	acm_iad_descriptor.bFirstInterface = status;
+
+	acm_control_interface_desc.bInterfaceNumber = status;
+	acm_union_desc .bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0) {
+		ERROR(cdev, "usb_interface_id2 failed %d\n", status);
+		goto fail;
+	}
+	acm->data_id = status;
+
+	acm_data_interface_desc.bInterfaceNumber = status;
+	acm_union_desc.bSlaveInterface0 = status;
+	acm_call_mgmt_descriptor.bDataInterface = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
+	if (!ep) {
+		ERROR(cdev, "usb_ep_autoconfig failed %d\n", status);
+		goto fail;
+	}
+	acm->port.in = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
+	if (!ep)
+		goto fail;
+	acm->port.out = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
+	if (!ep) {
+		ERROR(cdev, "usb_ep_autoconfig2 failed %d\n", status);
+		goto fail;
+	}
+	acm->notify = ep;
+
+	/* allocate notification */
+	acm->notify_req = gs_alloc_req(ep,
+			sizeof(struct usb_cdc_notification) + 2,
+			GFP_KERNEL);
+	if (!acm->notify_req) {
+		ERROR(cdev, "acm->notify_req == NULL\n");
+		goto fail;
+	}
+	acm->notify_req->complete = acm_cdc_notify_complete;
+	acm->notify_req->context = acm;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress;
+	acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress;
+	acm_hs_notify_desc.bEndpointAddress =
+		acm_fs_notify_desc.bEndpointAddress;
+
+	acm_ss_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress;
+	acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function,
+			acm_ss_function, acm_ss_function);
+	if (status) {
+		ERROR(cdev, "usb_assign_descriptors failed %d\n", status);
+		goto fail;
+	}
+	dev_info(&cdev->gadget->dev,
+		"acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+		acm->port_num,
+		gadget_is_superspeed(c->cdev->gadget) ? "super" :
+		gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+		acm->port.in->name, acm->port.out->name,
+		acm->notify->name);
+	return 0;
+
+fail:
+	if (acm->notify_req)
+		gs_free_req(acm->notify, acm->notify_req);
+
+	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+	return status;
+}
+
+static void acm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_acm		*acm = func_to_acm(f);
+
+	/* acm_string_defs[0].id = 0; */
+	usb_free_all_descriptors(f);
+	if (acm->notify_req)
+		gs_free_req(acm->notify, acm->notify_req);
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		acm_string_defs[0].id = 0;
+#endif
+
+}
+
+static void acm_free_func(struct usb_function *f)
+{
+	struct f_acm		*acm = func_to_acm(f);
+
+	kfree(acm);
+
+}
+
+static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
+{
+	struct f_serial_opts *opts;
+	struct f_acm *acm;
+
+	acm = kzalloc(sizeof(*acm), GFP_KERNEL);
+	if (!acm)
+		return ERR_PTR(-ENOMEM);
+
+	spin_lock_init(&acm->lock);
+
+	acm->port.connect = acm_connect;
+	acm->port.disconnect = acm_disconnect;
+	acm->port.send_break = acm_send_break;
+
+	acm->port.func.name = "acm";
+	acm->port.func.strings = acm_strings;
+	/* descriptors are per-instance copies */
+	acm->port.func.bind = acm_bind;
+	acm->port.func.set_alt = acm_set_alt;
+	acm->port.func.setup = acm_setup;
+	acm->port.func.disable = acm_disable;
+
+	opts = container_of(fi, struct f_serial_opts, func_inst);
+	acm->port_num = opts->port_num;
+	acm->port.func.unbind = acm_unbind;
+	acm->port.func.free_func = acm_free_func;
+
+	return &acm->port.func;
+}
+
+static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_serial_opts,
+			func_inst.group);
+}
+
+static void acm_attr_release(struct config_item *item)
+{
+	struct f_serial_opts *opts = to_f_serial_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations acm_item_ops = {
+	.release                = acm_attr_release,
+};
+
+static ssize_t f_acm_port_num_show(struct config_item *item, char *page)
+{
+	return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
+}
+
+CONFIGFS_ATTR_RO(f_acm_, port_num);
+
+static struct configfs_attribute *acm_attrs[] = {
+	&f_acm_attr_port_num,
+	NULL,
+};
+
+static const struct config_item_type acm_func_type = {
+	.ct_item_ops    = &acm_item_ops,
+	.ct_attrs	= acm_attrs,
+	.ct_owner       = THIS_MODULE,
+};
+
+static void acm_free_instance(struct usb_function_instance *fi)
+{
+	struct f_serial_opts *opts;
+
+	opts = container_of(fi, struct f_serial_opts, func_inst);
+	gserial_free_line(opts->port_num);
+	kfree(opts);
+}
+
+static struct usb_function_instance *acm_alloc_instance(void)
+{
+	struct f_serial_opts *opts;
+	int ret;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	opts->func_inst.free_func_inst = acm_free_instance;
+	ret = gserial_alloc_line(&opts->port_num);
+	if (ret) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+	pr_info("%s: gserial_alloc_line port_num: %d\n", __func__, opts->port_num);
+	config_group_init_type_name(&opts->func_inst.group, "",
+			&acm_func_type);
+	return &opts->func_inst;
+}
+DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func);
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_adb.c b/marvell/linux/drivers/usb/gadget/function/f_adb.c
new file mode 100644
index 0000000..7dcc207
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_adb.c
@@ -0,0 +1,772 @@
+/*
+ * Gadget Driver for Android ADB
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#define ADB_BULK_BUFFER_SIZE           4096
+
+/* number of tx requests to allocate */
+#define TX_REQ_MAX 4
+
+#define ADB_ADD_STATISTICS_INTERFACE
+
+static const char adb_shortname[] = "android_adb";
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+static int adb_inited;
+#endif
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+int nr_adb_dummy_interface = 0;
+#define ADB_INTERFACE_NUMBER 6
+#endif
+
+struct adb_dev {
+	struct usb_function function;
+	struct usb_composite_dev *cdev;
+	spinlock_t lock;
+
+	struct usb_ep *ep_in;
+	struct usb_ep *ep_out;
+
+	int online;
+	int error;
+
+	atomic_t read_excl;
+	atomic_t write_excl;
+	atomic_t open_excl;
+
+	struct list_head tx_idle;
+
+	wait_queue_head_t read_wq;
+	wait_queue_head_t write_wq;
+	struct usb_request *rx_req;
+	int rx_done;
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+	u32 adb_nr_rx;
+	u32 adb_nr_rx_bytes;
+	u32 adb_nr_tx;
+	u32 adb_nr_tx_bytes;
+	struct proc_dir_entry *pde;	
+#endif
+};
+
+static struct usb_interface_descriptor adb_interface_desc = {
+	.bLength                = USB_DT_INTERFACE_SIZE,
+	.bDescriptorType        = USB_DT_INTERFACE,
+	.bInterfaceNumber       = 0,
+	.bNumEndpoints          = 2,
+	.bInterfaceClass        = 0xFF,
+	.bInterfaceSubClass     = 0x42,
+	.bInterfaceProtocol     = 1,
+};
+
+static struct usb_endpoint_descriptor adb_superspeed_in_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_IN,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor adb_superspeed_out_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_OUT,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor adb_ss_bulk_comp_desc = {
+	.bLength =              sizeof adb_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_in_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_IN,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_out_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_OUT,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_in_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_IN,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_out_desc = {
+	.bLength                = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType        = USB_DT_ENDPOINT,
+	.bEndpointAddress       = USB_DIR_OUT,
+	.bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_adb_descs[] = {
+	(struct usb_descriptor_header *) &adb_interface_desc,
+	(struct usb_descriptor_header *) &adb_fullspeed_in_desc,
+	(struct usb_descriptor_header *) &adb_fullspeed_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_adb_descs[] = {
+	(struct usb_descriptor_header *) &adb_interface_desc,
+	(struct usb_descriptor_header *) &adb_highspeed_in_desc,
+	(struct usb_descriptor_header *) &adb_highspeed_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ss_adb_descs[] = {
+	(struct usb_descriptor_header *) &adb_interface_desc,
+	(struct usb_descriptor_header *) &adb_superspeed_in_desc,
+	(struct usb_descriptor_header *) &adb_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &adb_superspeed_out_desc,
+	(struct usb_descriptor_header *) &adb_ss_bulk_comp_desc,
+	NULL,
+};
+
+static void adb_ready_callback(void);
+static void adb_closed_callback(void);
+
+/* temporary variable used between adb_open() and adb_gadget_bind() */
+static struct adb_dev *_adb_dev;
+
+static inline struct adb_dev *func_to_adb(struct usb_function *f)
+{
+	return container_of(f, struct adb_dev, function);
+}
+
+
+static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size)
+{
+	struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!req)
+		return NULL;
+
+	/* now allocate buffers for the requests */
+#ifdef CONFIG_PXA910_1G_DDR_WORKAROUND
+	req->buf = kmalloc(buffer_size, GFP_KERNEL | GFP_DMA);
+#else
+	req->buf = kmalloc(buffer_size, GFP_KERNEL);
+#endif
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+
+	return req;
+}
+
+static void adb_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(ep, req);
+	}
+}
+
+static inline int adb_lock(atomic_t *excl)
+{
+	if (atomic_inc_return(excl) == 1) {
+		return 0;
+	} else {
+		atomic_dec(excl);
+		return -1;
+	}
+}
+
+static inline void adb_unlock(atomic_t *excl)
+{
+	atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+void adb_req_put(struct adb_dev *dev, struct list_head *head,
+		struct usb_request *req)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	list_add_tail(&req->list, head);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head)
+{
+	unsigned long flags;
+	struct usb_request *req;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (list_empty(head)) {
+		req = 0;
+	} else {
+		req = list_first_entry(head, struct usb_request, list);
+		list_del(&req->list);
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+	return req;
+}
+
+static void adb_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+	struct adb_dev *dev = _adb_dev;
+
+	if (req->status != 0)
+		dev->error = 1;
+
+	adb_req_put(dev, &dev->tx_idle, req);
+
+	wake_up(&dev->write_wq);
+}
+
+static void adb_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+	struct adb_dev *dev = _adb_dev;
+
+	dev->rx_done = 1;
+	if (req->status != 0 && req->status != -ECONNRESET)
+		dev->error = 1;
+
+	wake_up(&dev->read_wq);
+}
+
+static int adb_create_bulk_endpoints(struct adb_dev *dev,
+				struct usb_endpoint_descriptor *in_desc,
+				struct usb_endpoint_descriptor *out_desc)
+{
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	int i;
+
+	DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+	ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+	if (!ep) {
+		DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+		return -ENODEV;
+	}
+	DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+	ep->driver_data = dev;		/* claim the endpoint */
+	dev->ep_in = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+	if (!ep) {
+		DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+		return -ENODEV;
+	}
+	DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name);
+	ep->driver_data = dev;		/* claim the endpoint */
+	dev->ep_out = ep;
+
+	/* now allocate requests for our endpoints */
+	req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE);
+	if (!req)
+		goto fail;
+	req->complete = adb_complete_out;
+	dev->rx_req = req;
+
+	for (i = 0; i < TX_REQ_MAX; i++) {
+		req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE);
+		if (!req)
+			goto fail;
+		req->complete = adb_complete_in;
+		adb_req_put(dev, &dev->tx_idle, req);
+	}
+
+	return 0;
+
+fail:
+	printk(KERN_ERR "adb_bind() could not allocate requests\n");
+	return -1;
+}
+
+static ssize_t adb_read(struct file *fp, char __user *buf,
+				size_t count, loff_t *pos)
+{
+	struct adb_dev *dev = fp->private_data;
+	struct usb_request *req;
+	int r = count, xfer;
+	int ret;
+
+	pr_debug("adb_read(%zu)\n", count);
+	if (!_adb_dev)
+		return -ENODEV;
+
+	if (count > ADB_BULK_BUFFER_SIZE)
+		return -EINVAL;
+
+	if (adb_lock(&dev->read_excl))
+		return -EBUSY;
+
+	/* we will block until we're online */
+	while (!(dev->online || dev->error)) {
+		pr_debug("adb_read: waiting for online state\n");
+		ret = wait_event_interruptible(dev->read_wq,
+				(dev->online || dev->error));
+		if (ret < 0) {
+			adb_unlock(&dev->read_excl);
+			return ret;
+		}
+	}
+	if (dev->error) {
+		r = -EIO;
+		goto done;
+	}
+
+requeue_req:
+	/* queue a request */
+	req = dev->rx_req;
+#if defined(CONFIG_USB_DWC3) || defined(CONFIG_USB_DWC2)
+	req->length = ADB_BULK_BUFFER_SIZE;
+#else
+	req->length = count;
+#endif
+	dev->rx_done = 0;
+	ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC);
+	if (ret < 0) {
+		pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret);
+		r = -EIO;
+		dev->error = 1;
+		if (((-ESHUTDOWN) == ret) || ((-EAGAIN) == ret)) {
+			usleep_range(5000, 5000);
+		}
+		goto done;
+	} else {
+		pr_debug("rx %p queue\n", req);
+	}
+
+	/* wait for a request to complete */
+	ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+	if (ret < 0) {
+		if (ret != -ERESTARTSYS)
+			dev->error = 1;
+		r = ret;
+		usb_ep_dequeue(dev->ep_out, req);
+		goto done;
+	}
+	if (!dev->error) {
+		/* If we got a 0-len packet, throw it back and try again. */
+		if (req->actual == 0)
+			goto requeue_req;
+
+		pr_debug("rx %p %d\n", req, req->actual);
+		xfer = (req->actual < count) ? req->actual : count;
+		if (copy_to_user(buf, req->buf, xfer))
+			r = -EFAULT;
+
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+		dev->adb_nr_rx++;
+		dev->adb_nr_rx_bytes += xfer;
+#endif
+	} else
+		r = -EIO;
+
+done:
+	adb_unlock(&dev->read_excl);
+	pr_debug("adb_read returning %d\n", r);
+	return r;
+}
+
+static ssize_t adb_write(struct file *fp, const char __user *buf,
+				 size_t count, loff_t *pos)
+{
+	struct adb_dev *dev = fp->private_data;
+	struct usb_request *req = 0;
+	int r = count, xfer;
+	int ret;
+
+	if (!_adb_dev)
+		return -ENODEV;
+	pr_debug("adb_write(%zu)\n", count);
+
+	if (adb_lock(&dev->write_excl))
+		return -EBUSY;
+
+	while (count > 0) {
+		if (dev->error) {
+			pr_debug("adb_write dev->error\n");
+			r = -EIO;
+			break;
+		}
+
+		/* get an idle tx request to use */
+		req = 0;
+		ret = wait_event_interruptible(dev->write_wq,
+			(req = adb_req_get(dev, &dev->tx_idle)) || dev->error);
+
+		if (ret < 0) {
+			r = ret;
+			break;
+		}
+
+		if (req != 0) {
+			if (count > ADB_BULK_BUFFER_SIZE)
+				xfer = ADB_BULK_BUFFER_SIZE;
+			else
+				xfer = count;
+			if (copy_from_user(req->buf, buf, xfer)) {
+				r = -EFAULT;
+				break;
+			}
+
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+			dev->adb_nr_tx++;
+			dev->adb_nr_tx_bytes += xfer;
+#endif
+			req->length = xfer;
+			ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC);
+			if (ret < 0) {
+				pr_debug("adb_write: xfer error %d\n", ret);
+				dev->error = 1;
+				r = -EIO;
+				break;
+			}
+
+			buf += xfer;
+			count -= xfer;
+
+			/* zero this so we don't try to free it on error exit */
+			req = 0;
+		}
+	}
+
+	if (req)
+		adb_req_put(dev, &dev->tx_idle, req);
+
+	adb_unlock(&dev->write_excl);
+	pr_debug("adb_write returning %d\n", r);
+	return r;
+}
+
+static int adb_open(struct inode *ip, struct file *fp)
+{
+	pr_info_ratelimited("adb_open\n");
+	if (!_adb_dev)
+		return -ENODEV;
+
+	if (adb_lock(&_adb_dev->open_excl))
+		return -EBUSY;
+
+	fp->private_data = _adb_dev;
+
+	/* clear the error latch */
+	_adb_dev->error = 0;
+
+	if (system_state == SYSTEM_BOOTING || system_state == SYSTEM_RUNNING)
+		adb_ready_callback();
+
+	return 0;
+}
+
+static int adb_release(struct inode *ip, struct file *fp)
+{
+	pr_info_ratelimited("adb_release\n");
+
+	adb_closed_callback();
+
+	adb_unlock(&_adb_dev->open_excl);
+	return 0;
+}
+
+/* file operations for ADB device /dev/android_adb */
+static const struct file_operations adb_fops = {
+	.owner = THIS_MODULE,
+	.read = adb_read,
+	.write = adb_write,
+	.open = adb_open,
+	.release = adb_release,
+};
+
+static struct miscdevice adb_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = adb_shortname,
+	.fops = &adb_fops,
+};
+
+
+static int
+adb_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct adb_dev	*dev = func_to_adb(f);
+	int			id;
+	int			ret;
+#if defined(CONFIG_FIXED_USB_IFACE)
+	int			num = 0, i;
+#endif
+	dev->cdev = cdev;
+	DBG(cdev, "adb_function_bind dev: %p\n", dev);
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+	id = get_usb_interface_id(c);
+	if (id < 0) {
+		return id;
+	} else if(id < ADB_INTERFACE_NUMBER) {
+		num = ADB_INTERFACE_NUMBER - id;
+		nr_adb_dummy_interface = num;
+	} else
+		num = 0;
+
+	if (num > 0) {
+		for(i = 0; i < num; i++) {
+			id = usb_interface_id(c, NULL);
+			if (id < 0)
+				return id;
+			else
+				pr_info("%s: dummy interface: %d\n",
+						__func__, id);
+		}
+	}
+#endif
+
+	/* allocate interface ID(s) */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	adb_interface_desc.bInterfaceNumber = id;
+
+	/* allocate endpoints */
+	ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc,
+			&adb_fullspeed_out_desc);
+	if (ret)
+		return ret;
+
+	/* support high speed hardware */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		adb_highspeed_in_desc.bEndpointAddress =
+			adb_fullspeed_in_desc.bEndpointAddress;
+		adb_highspeed_out_desc.bEndpointAddress =
+			adb_fullspeed_out_desc.bEndpointAddress;
+	}
+
+	/* support super speed hardware */
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		adb_superspeed_in_desc.bEndpointAddress =
+			adb_fullspeed_in_desc.bEndpointAddress;
+		adb_superspeed_out_desc.bEndpointAddress =
+			adb_fullspeed_out_desc.bEndpointAddress;
+	}
+
+	DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			f->name, dev->ep_in->name, dev->ep_out->name);
+	return 0;
+}
+
+static void
+adb_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct adb_dev	*dev = func_to_adb(f);
+	struct usb_request *req;
+
+
+	dev->online = 0;
+	dev->error = 1;
+
+	wake_up(&dev->read_wq);
+
+	adb_request_free(dev->rx_req, dev->ep_out);
+	while ((req = adb_req_get(dev, &dev->tx_idle)))
+		adb_request_free(req, dev->ep_in);
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+	nr_adb_dummy_interface = 0;
+#endif
+}
+
+static int adb_function_set_alt(struct usb_function *f,
+		unsigned intf, unsigned alt)
+{
+	struct adb_dev	*dev = func_to_adb(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int ret;
+
+	DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt);
+
+	ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in);
+	if (ret)
+		return ret;
+
+	ret = usb_ep_enable(dev->ep_in);
+	if (ret)
+		return ret;
+
+	ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out);
+	if (ret)
+		return ret;
+
+	ret = usb_ep_enable(dev->ep_out);
+	if (ret) {
+		usb_ep_disable(dev->ep_in);
+		return ret;
+	}
+	dev->online = 1;
+
+	/* readers may be blocked waiting for us to go online */
+	wake_up(&dev->read_wq);
+	return 0;
+}
+
+static void adb_function_disable(struct usb_function *f)
+{
+	struct adb_dev	*dev = func_to_adb(f);
+	struct usb_composite_dev	*cdev = dev->cdev;
+
+	DBG(cdev, "adb_function_disable cdev %p\n", cdev);
+	dev->online = 0;
+	dev->error = 1;
+	usb_ep_disable(dev->ep_in);
+	usb_ep_disable(dev->ep_out);
+
+	/* readers may be blocked waiting for us to go online */
+	wake_up(&dev->read_wq);
+
+	VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int adb_bind_config(struct usb_configuration *c)
+{
+	struct adb_dev *dev = _adb_dev;
+
+	printk(KERN_INFO "adb_bind_config\n");
+
+	dev->cdev = c->cdev;
+	dev->function.name = "adb";
+	dev->function.fs_descriptors = fs_adb_descs;
+	dev->function.hs_descriptors = hs_adb_descs;
+	dev->function.ss_descriptors = ss_adb_descs;
+	dev->function.ssp_descriptors = ss_adb_descs;
+
+	dev->function.bind = adb_function_bind;
+	dev->function.unbind = adb_function_unbind;
+	dev->function.set_alt = adb_function_set_alt;
+	dev->function.disable = adb_function_disable;
+
+	return usb_add_function(c, &dev->function);
+}
+
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+/*
+ * Info exported via "/proc/driver/adb".
+ */
+static int adb_proc_show(struct seq_file *seq, void *v)
+{
+        seq_printf(seq, "rx-tx: %10d:%10d:%10d:%10d\n",
+					_adb_dev->adb_nr_rx,
+					_adb_dev->adb_nr_rx_bytes,
+					_adb_dev->adb_nr_tx,
+					_adb_dev->adb_nr_tx_bytes);
+        return  0;
+}
+
+static int adb_proc_open(struct inode *inode, struct file *file)
+{
+        return single_open(file, adb_proc_show, NULL);
+}
+
+static const struct file_operations adb_proc_fops = {
+        .owner          = THIS_MODULE,
+        .open           = adb_proc_open,
+        .read           = seq_read,
+        .llseek         = seq_lseek,
+        .release        = single_release,
+};
+#endif
+
+static int adb_setup(void)
+{
+	struct adb_dev *dev;
+	int ret;
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (adb_inited)
+		return 0;
+	else
+		adb_inited = 1;
+#endif
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	spin_lock_init(&dev->lock);
+
+	init_waitqueue_head(&dev->read_wq);
+	init_waitqueue_head(&dev->write_wq);
+
+	atomic_set(&dev->open_excl, 0);
+	atomic_set(&dev->read_excl, 0);
+	atomic_set(&dev->write_excl, 0);
+
+	INIT_LIST_HEAD(&dev->tx_idle);
+
+	_adb_dev = dev;
+	ret = misc_register(&adb_device);
+	if (ret)
+		goto err;
+
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+	_adb_dev->pde = proc_create("driver/adb", S_IRUSR, NULL, &adb_proc_fops);
+	if (!_adb_dev->pde)
+		printk(KERN_WARNING "adb: Failed to register procfs.\n");
+#endif
+
+	return 0;
+
+err:
+	kfree(dev);
+	printk(KERN_ERR "adb gadget driver failed to initialize\n");
+	return ret;
+}
+
+static void adb_cleanup(void)
+{
+#if !defined(CONFIG_CPU_ASR18XX) || !defined(CONFIG_USB_MVC2)
+
+#if defined(ADB_ADD_STATISTICS_INTERFACE) && defined(CONFIG_PROC_FS)
+	if (_adb_dev->pde)
+		proc_remove(_adb_dev->pde);
+#endif
+	misc_deregister(&adb_device);
+	adb_device.minor = MISC_DYNAMIC_MINOR;
+	kfree(_adb_dev);
+	_adb_dev = NULL;
+#endif
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.c b/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.c
new file mode 100644
index 0000000..538cbc5
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.c
@@ -0,0 +1,917 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
+ *
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * This driver doesn't expect any real Audio codec to be present
+ * on the device - the audio streams are simply sinked to and
+ * sourced from a virtual ALSA sound card created.
+ *
+ * This file is based on f_uac1.c which is
+ *   Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ *   Copyright (C) 2008 Analog Devices, Inc
+ */
+
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+
+#include "u_asr_uac1.h"
+#include "f_asr_uac1.h"
+
+/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
+#define UAC1_CHANNEL_MASK 0x0FFF
+
+struct f_uac1 {
+	struct g_audio g_audio;
+	u8 ac_intf, as_in_intf, as_out_intf;
+	u8 ac_alt, as_in_alt, as_out_alt;	/* needed for get_alt() */
+};
+
+static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
+{
+	return container_of(f, struct f_uac1, g_audio.func);
+}
+
+static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
+{
+	return container_of(audio->func.fi, struct f_uac1_opts, func_inst);
+}
+
+/*
+ * DESCRIPTORS ... most are static, but strings and full
+ * configuration descriptors are built on demand.
+ */
+
+/*
+ * We have three interfaces - one AudioControl and two AudioStreaming
+ *
+ * The driver implements a simple UAC_1 topology.
+ * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture
+ * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN
+ */
+#define F_AUDIO_AC_INTERFACE		0
+#define F_AUDIO_AS_OUT_INTERFACE	1
+#define F_AUDIO_AS_IN_INTERFACE		2
+/* Number of streaming interfaces */
+#define F_AUDIO_NUM_INTERFACES		2
+
+#define AUDIO_AC_INTERFACE	0
+#define AUDIO_AS_INTERFACE	1
+#define AUDIO_NUM_INTERFACES	3
+
+static struct usb_interface_assoc_descriptor
+audio_iad_descriptor = {
+	.bLength =		sizeof audio_iad_descriptor,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount = 	AUDIO_NUM_INTERFACES,
+	.bFunctionClass =	USB_CLASS_AUDIO,
+	.bFunctionSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+	.bFunctionProtocol =	0,
+	/* .iFunction =		DYNAMIC */
+};
+
+/* B.3.1  Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+};
+
+/*
+ * The number of AudioStreaming and MIDIStreaming interfaces
+ * in the Audio Interface Collection
+ */
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
+
+#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
+/* 2 input terminals and 2 output terminals */
+#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
+	+ 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)
+/* B.3.2  Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_2 ac_header_desc = {
+	.bLength =		UAC_DT_AC_HEADER_LENGTH,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_HEADER,
+	.bcdADC =		cpu_to_le16(0x0100),
+	.wTotalLength =		cpu_to_le16(UAC_DT_TOTAL_LENGTH),
+	.bInCollection =	F_AUDIO_NUM_INTERFACES,
+	.baInterfaceNr = {
+	/* Interface number of the AudioStream interfaces */
+		[0] =		1,
+		[1] =		2,
+	}
+};
+
+#define USB_OUT_IT_ID	3
+static struct uac_input_terminal_descriptor usb_out_it_desc = {
+	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_INPUT_TERMINAL,
+	.bTerminalID =		USB_OUT_IT_ID,
+	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal =	0,
+	.wChannelConfig =	cpu_to_le16(UAC1_DEF_CCHMASK),
+};
+
+#define IO_OUT_OT_ID	4
+static struct uac1_output_terminal_descriptor io_out_ot_desc = {
+	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL,
+	.bTerminalID		= IO_OUT_OT_ID,
+	.wTerminalType		= cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
+	.bAssocTerminal		= 0,
+	.bSourceID		= USB_OUT_IT_ID,
+};
+
+#define IO_IN_IT_ID	0
+static struct uac_input_terminal_descriptor io_in_it_desc = {
+	.bLength		= UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_INPUT_TERMINAL,
+	.bTerminalID		= IO_IN_IT_ID,
+	.wTerminalType		= cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
+	.bAssocTerminal		= 0,
+	.wChannelConfig		= cpu_to_le16(UAC1_DEF_PCHMASK),
+};
+
+#define USB_IN_OT_ID	1
+static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
+	.bLength =		UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_OUTPUT_TERMINAL,
+	.bTerminalID =		USB_IN_OT_ID,
+	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal =	0,
+	.bSourceID =		IO_IN_IT_ID,
+};
+
+/* B.4.1  Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_out_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_out_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_in_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_in_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2  Class-Specific AS Interface Descriptor */
+static struct uac1_as_header_descriptor as_out_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	USB_OUT_IT_ID,
+	.bDelay =		0,
+	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
+};
+
+static struct uac1_as_header_descriptor as_in_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	USB_IN_OT_ID,
+	.bDelay =		0,
+	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
+};
+
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+
+static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor as_out_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_SYNC_ADAPTIVE
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize	=	cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+	.bInterval =		4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes =		1,
+	.bLockDelayUnits =	1,
+	.wLockDelay =		cpu_to_le16(1),
+};
+
+static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO IN Endpoint Descriptor */
+static struct usb_endpoint_descriptor as_in_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_SYNC_ASYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize	=	cpu_to_le16(UAC1_IN_EP_MAX_PACKET_SIZE),
+	.bInterval =		4,
+};
+
+/* Class-specific AS ISO IN Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes =		1,
+	.bLockDelayUnits =	0,
+	.wLockDelay =		0,
+};
+
+static struct usb_descriptor_header *f_audio_desc[] = {
+	(struct usb_descriptor_header *)&audio_iad_descriptor,
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_out_header_desc,
+
+	(struct usb_descriptor_header *)&as_out_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_out_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_in_header_desc,
+
+	(struct usb_descriptor_header *)&as_in_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_in_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct usb_ss_ep_comp_descriptor as_in_ss_comp_desc = {
+	.bLength                = sizeof(as_in_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+	.wBytesPerInterval =	cpu_to_le16(UAC1_IN_EP_MAX_PACKET_SIZE),
+};
+
+static struct usb_ss_ep_comp_descriptor as_out_ss_comp_desc = {
+	.bLength                = sizeof(as_out_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+	.wBytesPerInterval =	cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+};
+
+static struct usb_descriptor_header *f_audio_ss_desc[] = {
+	(struct usb_descriptor_header *)&audio_iad_descriptor,
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_out_header_desc,
+
+	(struct usb_descriptor_header *)&as_out_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_out_ep_desc,
+	(struct usb_descriptor_header *)&as_out_ss_comp_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_in_header_desc,
+
+	(struct usb_descriptor_header *)&as_in_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_in_ep_desc,
+	(struct usb_descriptor_header *)&as_in_ss_comp_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+enum {
+	STR_AC_IF,
+	STR_USB_OUT_IT,
+	STR_USB_OUT_IT_CH_NAMES,
+	STR_IO_OUT_OT,
+	STR_IO_IN_IT,
+	STR_IO_IN_IT_CH_NAMES,
+	STR_USB_IN_OT,
+	STR_AS_OUT_IF_ALT0,
+	STR_AS_OUT_IF_ALT1,
+	STR_AS_IN_IF_ALT0,
+	STR_AS_IN_IF_ALT1,
+};
+
+static struct usb_string strings_uac1[] = {
+	[STR_AC_IF].s = "AC Interface",
+	[STR_USB_OUT_IT].s = "Playback Input terminal",
+	[STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
+	[STR_IO_OUT_OT].s = "Playback Output terminal",
+	[STR_IO_IN_IT].s = "Capture Input terminal",
+	[STR_IO_IN_IT_CH_NAMES].s = "Capture Channels",
+	[STR_USB_IN_OT].s = "Capture Output terminal",
+	[STR_AS_OUT_IF_ALT0].s = "Playback Inactive",
+	[STR_AS_OUT_IF_ALT1].s = "Playback Active",
+	[STR_AS_IN_IF_ALT0].s = "Capture Inactive",
+	[STR_AS_IN_IF_ALT1].s = "Capture Active",
+	{ },
+};
+
+static struct usb_gadget_strings str_uac1 = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_uac1,
+};
+
+static struct usb_gadget_strings *uac1_strings[] = {
+	&str_uac1,
+	NULL,
+};
+
+/*
+ * This function is an ALSA sound card following USB Audio Class Spec 1.0.
+ */
+
+
+/*-------------------------------------------------------------------------*/
+static int audio_set_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	int			value = -EOPNOTSUPP;
+	u16			ep = le16_to_cpu(ctrl->wIndex);
+	u16			len = le16_to_cpu(ctrl->wLength);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+
+	pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_SET_CUR:
+		value = len;
+		break;
+
+	case UAC_SET_MIN:
+		break;
+
+	case UAC_SET_MAX:
+		break;
+
+	case UAC_SET_RES:
+		break;
+
+	case UAC_SET_MEM:
+		break;
+
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int audio_get_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	int value = -EOPNOTSUPP;
+	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16 len = le16_to_cpu(ctrl->wLength);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+
+	pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_GET_CUR:
+	case UAC_GET_MIN:
+	case UAC_GET_MAX:
+	case UAC_GET_RES:
+		value = len;
+		break;
+	case UAC_GET_MEM:
+		break;
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int
+f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything; interface
+	 * activation uses set_alt().
+	 */
+	switch (ctrl->bRequestType) {
+	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_set_endpoint_req(f, ctrl);
+		break;
+
+	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_get_endpoint_req(f, ctrl);
+		break;
+
+	default:
+		pr_err("invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		pr_debug("audio req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			pr_err("audio response on err %d\n", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	struct f_uac1 *uac1 = func_to_uac1(f);
+	int ret = 0;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == uac1->ac_intf) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (intf == uac1->as_out_intf) {
+		uac1->as_out_alt = alt;
+
+		if (alt) {
+			ret = u_audio_start_playback(&uac1->g_audio);
+		} else {
+			u_audio_stop_playback(&uac1->g_audio);
+		}
+	} else if (intf == uac1->as_in_intf) {
+		uac1->as_in_alt = alt;
+		if (alt) {
+			ret = u_audio_start_capture(&uac1->g_audio);
+		} else {
+			u_audio_stop_capture(&uac1->g_audio);
+		}
+	} else {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int f_audio_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	struct f_uac1 *uac1 = func_to_uac1(f);
+
+	if (intf == uac1->ac_intf)
+		return uac1->ac_alt;
+	else if (intf == uac1->as_out_intf)
+		return uac1->as_out_alt;
+	else if (intf == uac1->as_in_intf)
+		return uac1->as_in_alt;
+	else
+		dev_err(dev, "%s:%d Invalid Interface %d!\n",
+			__func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+
+static void f_audio_disable(struct usb_function *f)
+{
+	struct f_uac1 *uac1 = func_to_uac1(f);
+
+	uac1->as_out_alt = 0;
+	uac1->as_in_alt = 0;
+
+	u_audio_stop_playback(&uac1->g_audio);
+	u_audio_stop_capture(&uac1->g_audio);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
+{
+	struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+#if 0
+	if (!opts->p_chmask && !opts->c_chmask) {
+		dev_err(dev, "Error: no playback and capture channels\n");
+		return -EINVAL;
+	} else
+#endif
+    if (opts->p_chmask & ~UAC1_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported playback channels mask\n");
+		return -EINVAL;
+	} else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported capture channels mask\n");
+		return -EINVAL;
+	} else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
+		dev_err(dev, "Error: incorrect playback sample size\n");
+		return -EINVAL;
+	} else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
+		dev_err(dev, "Error: incorrect capture sample size\n");
+		return -EINVAL;
+	} else if (!opts->p_srate) {
+		dev_err(dev, "Error: incorrect playback sampling rate\n");
+		return -EINVAL;
+	} else if (!opts->c_srate) {
+		dev_err(dev, "Error: incorrect capture sampling rate\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* audio function driver setup/binding */
+static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev	*cdev = c->cdev;
+	struct usb_gadget		*gadget = cdev->gadget;
+	struct device			*dev = &gadget->dev;
+	struct f_uac1			*uac1 = func_to_uac1(f);
+	struct g_audio			*audio = func_to_g_audio(f);
+	struct f_uac1_opts		*audio_opts;
+	struct usb_ep			*ep = NULL;
+	struct usb_string		*us;
+	u8				*sam_freq;
+	int				rate;
+	int				status;
+
+	status = f_audio_validate_opts(audio, dev);
+	if (status) {
+		pr_err("f_audio_validate_opts failed\n");
+		return status;
+	}
+	audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+
+	us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	ac_interface_desc.iInterface = us[STR_AC_IF].id;
+	usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
+	usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
+	io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id;
+	as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id;
+	as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id;
+	io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id;
+	io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id;
+	usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id;
+	as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
+	as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
+
+	/* Set channel numbers */
+	usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+	usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask);
+	as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+	as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize;
+	as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8;
+	io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+	io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask);
+	as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+	as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize;
+	as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
+
+	/* Set sample rates */
+	rate = audio_opts->c_srate;
+	sam_freq = as_out_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+	rate = audio_opts->p_srate;
+	sam_freq = as_in_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ac_interface_desc.bInterfaceNumber = status;
+	uac1->ac_intf = status;
+	uac1->ac_alt = 0;
+	audio_iad_descriptor.bFirstInterface = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_out_interface_alt_0_desc.bInterfaceNumber = status;
+	as_out_interface_alt_1_desc.bInterfaceNumber = status;
+	ac_header_desc.baInterfaceNr[0] = status;
+	uac1->as_out_intf = status;
+	uac1->as_out_alt = 0;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_in_interface_alt_0_desc.bInterfaceNumber = status;
+	as_in_interface_alt_1_desc.bInterfaceNumber = status;
+	ac_header_desc.baInterfaceNr[1] = status;
+	uac1->as_in_intf = status;
+	uac1->as_in_alt = 0;
+
+	audio->gadget = gadget;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->out_ep = ep;
+	audio->out_ep->desc = &as_out_ep_desc;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->in_ep = ep;
+	audio->in_ep->desc = &as_in_ep_desc;
+
+	/* copy descriptors, and track endpoint copies */
+	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, f_audio_ss_desc, f_audio_ss_desc);
+	if (status)
+		goto fail;
+
+	audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
+	audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
+	audio->params.c_chmask = audio_opts->c_chmask;
+	audio->params.c_srate = audio_opts->c_srate;
+	audio->params.c_ssize = audio_opts->c_ssize;
+	audio->params.p_chmask = audio_opts->p_chmask;
+	audio->params.p_srate = audio_opts->p_srate;
+	audio->params.p_ssize = audio_opts->p_ssize;
+	audio->params.req_number = audio_opts->req_number;
+
+	status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget");
+	if (status)
+		goto err_card_register;
+
+	return 0;
+
+err_card_register:
+	usb_free_all_descriptors(f);
+fail:
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+#if 0
+static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_uac1_opts,
+			    func_inst.group);
+}
+
+static void f_uac1_attr_release(struct config_item *item)
+{
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_uac1_item_ops = {
+	.release	= f_uac1_attr_release,
+};
+
+#define UAC1_ATTRIBUTE(name)						\
+static ssize_t f_uac1_opts_##name##_show(				\
+					  struct config_item *item,	\
+					  char *page)			\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac1_opts_##name##_store(				\
+					  struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);		\
+	int ret;							\
+	u32 num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou32(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_ATTRIBUTE(c_chmask);
+UAC1_ATTRIBUTE(c_srate);
+UAC1_ATTRIBUTE(c_ssize);
+UAC1_ATTRIBUTE(p_chmask);
+UAC1_ATTRIBUTE(p_srate);
+UAC1_ATTRIBUTE(p_ssize);
+UAC1_ATTRIBUTE(req_number);
+
+static struct configfs_attribute *f_uac1_attrs[] = {
+	&f_uac1_opts_attr_c_chmask,
+	&f_uac1_opts_attr_c_srate,
+	&f_uac1_opts_attr_c_ssize,
+	&f_uac1_opts_attr_p_chmask,
+	&f_uac1_opts_attr_p_srate,
+	&f_uac1_opts_attr_p_ssize,
+	&f_uac1_opts_attr_req_number,
+	NULL,
+};
+
+static const struct config_item_type f_uac1_func_type = {
+	.ct_item_ops	= &f_uac1_item_ops,
+	.ct_attrs	= f_uac1_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+#endif
+static void f_audio_free_inst(struct usb_function_instance *f)
+{
+	struct f_uac1_opts *opts;
+
+	opts = container_of(f, struct f_uac1_opts, func_inst);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_audio_alloc_inst(void)
+{
+	struct f_uac1_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_audio_free_inst;
+
+#if 0
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &f_uac1_func_type);
+#endif
+
+	opts->c_chmask = UAC1_DEF_CCHMASK;
+	opts->c_srate = UAC1_DEF_CSRATE;
+	opts->c_ssize = UAC1_DEF_CSSIZE;
+	opts->p_chmask = UAC1_DEF_PCHMASK;
+	opts->p_srate = UAC1_DEF_PSRATE;
+	opts->p_ssize = UAC1_DEF_PSSIZE;
+	opts->req_number = UAC1_DEF_REQ_NUM;
+
+	return &opts->func_inst;
+}
+
+static void f_audio_free(struct usb_function *f)
+{
+	struct g_audio *audio;
+	struct f_uac1_opts *opts;
+
+	audio = func_to_g_audio(f);
+	opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+	kfree(audio);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct g_audio *audio = func_to_g_audio(f);
+
+	g_audio_cleanup(audio);
+	usb_free_all_descriptors(f);
+
+	audio->gadget = NULL;
+}
+
+static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
+{
+	struct f_uac1 *uac1;
+	struct f_uac1_opts *opts;
+
+	/* allocate and initialize one new instance */
+	uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL);
+	if (!uac1)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_uac1_opts, func_inst);
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	uac1->g_audio.func.name = "uac1";
+	uac1->g_audio.func.bind = f_audio_bind;
+	uac1->g_audio.func.unbind = f_audio_unbind;
+	uac1->g_audio.func.set_alt = f_audio_set_alt;
+	uac1->g_audio.func.get_alt = f_audio_get_alt;
+	uac1->g_audio.func.setup = f_audio_setup;
+	uac1->g_audio.func.disable = f_audio_disable;
+	uac1->g_audio.func.free_func = f_audio_free;
+
+	return &uac1->g_audio.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.h b/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.h
new file mode 100644
index 0000000..3da4943
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_asr_uac1.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_uac1.h - Utility definitions for UAC1 function
+ *
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ */
+
+#ifndef __F_ASR_UAC1_H
+#define __F_ASR_UAC1_H
+
+#include <linux/usb/composite.h>
+
+#define UAC1_DEF_PCHMASK	0x0
+#define UAC1_DEF_PSRATE		16000
+#define UAC1_DEF_PSSIZE		2
+#define UAC1_DEF_CCHMASK	(UAC1_DEF_PCHMASK)
+#define UAC1_DEF_CSRATE		(UAC1_DEF_PSRATE)
+#define UAC1_DEF_CSSIZE	    (UAC1_DEF_PSSIZE)
+#define UAC1_DEF_REQ_NUM	2
+#define UAC1_OUT_EP_MAX_PACKET_SIZE	((UAC1_DEF_PCHMASK == 3 ? 2 : 1) * UAC1_DEF_PSRATE / 1000 * 2)
+#define UAC1_IN_EP_MAX_PACKET_SIZE	((UAC1_DEF_CCHMASK == 3 ? 2 : 1) * UAC1_DEF_CSRATE / 1000 * 2)
+
+
+struct f_uac1_opts {
+	struct usb_function_instance	func_inst;
+	int				c_chmask;
+	int				c_srate;
+	int				c_ssize;
+	int				p_chmask;
+	int				p_srate;
+	int				p_ssize;
+	int				req_number;
+	unsigned			bound:1;
+
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* __U_UAC1_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/f_audio_source.c b/marvell/linux/drivers/usb/gadget/function/f_audio_source.c
new file mode 100644
index 0000000..8124af3
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_audio_source.c
@@ -0,0 +1,1071 @@
+/*
+ * Gadget Function Driver for USB audio source device
+ *
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/usb/audio.h>
+#include <linux/wait.h>
+#include <linux/pm_qos.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/configfs.h>
+#include <linux/usb/composite.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#define SAMPLE_RATE 44100
+#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000)
+
+#define IN_EP_MAX_PACKET_SIZE 256
+
+/* Number of requests to allocate */
+#define IN_EP_REQ_COUNT 4
+
+#define AUDIO_AC_INTERFACE	0
+#define AUDIO_AS_INTERFACE	1
+#define AUDIO_NUM_INTERFACES	2
+#define MAX_INST_NAME_LEN     40
+
+/* B.3.1  Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+};
+
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
+
+#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES)
+/* 1 input terminal, 1 output terminal and 1 feature unit */
+#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
+	+ UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \
+	+ UAC_DT_FEATURE_UNIT_SIZE(0))
+/* B.3.2  Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_2 ac_header_desc = {
+	.bLength =		UAC_DT_AC_HEADER_LENGTH,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_HEADER,
+	.bcdADC =		__constant_cpu_to_le16(0x0100),
+	.wTotalLength =		__constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH),
+	.bInCollection =	AUDIO_NUM_INTERFACES,
+	.baInterfaceNr = {
+		[0] =		AUDIO_AC_INTERFACE,
+		[1] =		AUDIO_AS_INTERFACE,
+	}
+};
+
+#define INPUT_TERMINAL_ID	1
+static struct uac_input_terminal_descriptor input_terminal_desc = {
+	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_INPUT_TERMINAL,
+	.bTerminalID =		INPUT_TERMINAL_ID,
+	.wTerminalType =	UAC_INPUT_TERMINAL_MICROPHONE,
+	.bAssocTerminal =	0,
+	.wChannelConfig =	0x3,
+};
+
+DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
+
+#define FEATURE_UNIT_ID		2
+static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
+	.bLength		= UAC_DT_FEATURE_UNIT_SIZE(0),
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_FEATURE_UNIT,
+	.bUnitID		= FEATURE_UNIT_ID,
+	.bSourceID		= INPUT_TERMINAL_ID,
+	.bControlSize		= 2,
+};
+
+#define OUTPUT_TERMINAL_ID	3
+static struct uac1_output_terminal_descriptor output_terminal_desc = {
+	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL,
+	.bTerminalID		= OUTPUT_TERMINAL_ID,
+	.wTerminalType		= UAC_TERMINAL_STREAMING,
+	.bAssocTerminal		= FEATURE_UNIT_ID,
+	.bSourceID		= FEATURE_UNIT_ID,
+};
+
+/* B.4.1  Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2  Class-Specific AS Interface Descriptor */
+static struct uac1_as_header_descriptor as_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	INPUT_TERMINAL_ID,
+	.bDelay =		1,
+	.wFormatTag =		UAC_FORMAT_TYPE_I_PCM,
+};
+
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+
+static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO IN Endpoint Descriptor for highspeed */
+static struct usb_endpoint_descriptor hs_as_in_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_SYNC_SYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	__constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
+	.bInterval =		4, /* poll 1 per millisecond */
+};
+
+/* Standard ISO IN Endpoint Descriptor for highspeed */
+static struct usb_endpoint_descriptor fs_as_in_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_SYNC_SYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	__constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
+	.bInterval =		1, /* poll 1 per millisecond */
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes =		1,
+	.bLockDelayUnits =	1,
+	.wLockDelay =		__constant_cpu_to_le16(1),
+};
+
+static struct usb_descriptor_header *hs_audio_desc[] = {
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&input_terminal_desc,
+	(struct usb_descriptor_header *)&output_terminal_desc,
+	(struct usb_descriptor_header *)&feature_unit_desc,
+
+	(struct usb_descriptor_header *)&as_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_header_desc,
+
+	(struct usb_descriptor_header *)&as_type_i_desc,
+
+	(struct usb_descriptor_header *)&hs_as_in_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *fs_audio_desc[] = {
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&input_terminal_desc,
+	(struct usb_descriptor_header *)&output_terminal_desc,
+	(struct usb_descriptor_header *)&feature_unit_desc,
+
+	(struct usb_descriptor_header *)&as_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_header_desc,
+
+	(struct usb_descriptor_header *)&as_type_i_desc,
+
+	(struct usb_descriptor_header *)&fs_as_in_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct snd_pcm_hardware audio_hw_info = {
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER,
+
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.rate_min		= SAMPLE_RATE,
+	.rate_max		= SAMPLE_RATE,
+
+	.buffer_bytes_max =	1024 * 1024,
+	.period_bytes_min =	64,
+	.period_bytes_max =	512 * 1024,
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+/*-------------------------------------------------------------------------*/
+
+struct audio_source_config {
+	int	card;
+	int	device;
+};
+
+struct audio_dev {
+	struct usb_function		func;
+	struct snd_card			*card;
+	struct snd_pcm			*pcm;
+	struct snd_pcm_substream *substream;
+
+	struct list_head		idle_reqs;
+	struct usb_ep			*in_ep;
+
+	spinlock_t			lock;
+
+	/* beginning, end and current position in our buffer */
+	void				*buffer_start;
+	void				*buffer_end;
+	void				*buffer_pos;
+
+	/* byte size of a "period" */
+	unsigned int			period;
+	/* bytes sent since last call to snd_pcm_period_elapsed */
+	unsigned int			period_offset;
+	/* time we started playing */
+	ktime_t				start_time;
+	/* number of frames sent since start_time */
+	s64				frames_sent;
+	struct audio_source_config	*config;
+	/* for creating and issuing QoS requests */
+	struct pm_qos_request pm_qos;
+};
+
+static inline struct audio_dev *func_to_audio(struct usb_function *f)
+{
+	return container_of(f, struct audio_dev, func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+struct audio_source_instance {
+	struct usb_function_instance func_inst;
+	const char *name;
+	struct audio_source_config *config;
+	struct device *audio_device;
+};
+
+static void audio_source_attr_release(struct config_item *item);
+
+static struct configfs_item_operations audio_source_item_ops = {
+	.release        = audio_source_attr_release,
+};
+
+static struct config_item_type audio_source_func_type = {
+	.ct_item_ops    = &audio_source_item_ops,
+	.ct_owner       = THIS_MODULE,
+};
+
+static ssize_t audio_source_pcm_show(struct device *dev,
+		struct device_attribute *attr, char *buf);
+
+static DEVICE_ATTR(pcm, S_IRUGO, audio_source_pcm_show, NULL);
+
+static struct device_attribute *audio_source_function_attributes[] = {
+	&dev_attr_pcm,
+	NULL
+};
+
+/*--------------------------------------------------------------------------*/
+
+static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size)
+{
+	struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+
+	if (!req)
+		return NULL;
+
+	req->buf = kmalloc(buffer_size, GFP_KERNEL);
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+	req->length = buffer_size;
+	return req;
+}
+
+static void audio_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(ep, req);
+	}
+}
+
+static void audio_req_put(struct audio_dev *audio, struct usb_request *req)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&audio->lock, flags);
+	list_add_tail(&req->list, &audio->idle_reqs);
+	spin_unlock_irqrestore(&audio->lock, flags);
+}
+
+static struct usb_request *audio_req_get(struct audio_dev *audio)
+{
+	unsigned long flags;
+	struct usb_request *req;
+
+	spin_lock_irqsave(&audio->lock, flags);
+	if (list_empty(&audio->idle_reqs)) {
+		req = 0;
+	} else {
+		req = list_first_entry(&audio->idle_reqs, struct usb_request,
+				list);
+		list_del(&req->list);
+	}
+	spin_unlock_irqrestore(&audio->lock, flags);
+	return req;
+}
+
+/* send the appropriate number of packets to match our bitrate */
+static void audio_send(struct audio_dev *audio)
+{
+	struct snd_pcm_runtime *runtime;
+	struct usb_request *req;
+	int length, length1, length2, ret;
+	s64 msecs;
+	s64 frames;
+	ktime_t now;
+
+	/* audio->substream will be null if we have been closed */
+	if (!audio->substream)
+		return;
+	/* audio->buffer_pos will be null if we have been stopped */
+	if (!audio->buffer_pos)
+		return;
+
+	runtime = audio->substream->runtime;
+
+	/* compute number of frames to send */
+	now = ktime_get();
+	msecs = div_s64((ktime_to_ns(now) - ktime_to_ns(audio->start_time)),
+			1000000);
+	frames = div_s64((msecs * SAMPLE_RATE), 1000);
+
+	/* Readjust our frames_sent if we fall too far behind.
+	 * If we get too far behind it is better to drop some frames than
+	 * to keep sending data too fast in an attempt to catch up.
+	 */
+	if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC)
+		audio->frames_sent = frames - FRAMES_PER_MSEC;
+
+	frames -= audio->frames_sent;
+
+	/* We need to send something to keep the pipeline going */
+	if (frames <= 0)
+		frames = FRAMES_PER_MSEC;
+
+	while (frames > 0) {
+		req = audio_req_get(audio);
+		if (!req)
+			break;
+
+		length = frames_to_bytes(runtime, frames);
+		if (length > IN_EP_MAX_PACKET_SIZE)
+			length = IN_EP_MAX_PACKET_SIZE;
+
+		if (audio->buffer_pos + length > audio->buffer_end)
+			length1 = audio->buffer_end - audio->buffer_pos;
+		else
+			length1 = length;
+		memcpy(req->buf, audio->buffer_pos, length1);
+		if (length1 < length) {
+			/* Wrap around and copy remaining length
+			 * at beginning of buffer.
+			 */
+			length2 = length - length1;
+			memcpy(req->buf + length1, audio->buffer_start,
+					length2);
+			audio->buffer_pos = audio->buffer_start + length2;
+		} else {
+			audio->buffer_pos += length1;
+			if (audio->buffer_pos >= audio->buffer_end)
+				audio->buffer_pos = audio->buffer_start;
+		}
+
+		req->length = length;
+		ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC);
+		if (ret < 0) {
+			pr_err("usb_ep_queue failed ret: %d\n", ret);
+			audio_req_put(audio, req);
+			break;
+		}
+
+		frames -= bytes_to_frames(runtime, length);
+		audio->frames_sent += bytes_to_frames(runtime, length);
+	}
+}
+
+static void audio_control_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	/* nothing to do here */
+}
+
+static void audio_data_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct audio_dev *audio = req->context;
+
+	pr_debug("audio_data_complete req->status %d req->actual %d\n",
+		req->status, req->actual);
+
+	audio_req_put(audio, req);
+
+	if (!audio->buffer_start || req->status)
+		return;
+
+	audio->period_offset += req->actual;
+	if (audio->period_offset >= audio->period) {
+		snd_pcm_period_elapsed(audio->substream);
+		audio->period_offset = 0;
+	}
+	audio_send(audio);
+}
+
+static int audio_set_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	int value = -EOPNOTSUPP;
+	u16 ep = le16_to_cpu(ctrl->wIndex);
+	u16 len = le16_to_cpu(ctrl->wLength);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+
+	pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_SET_CUR:
+	case UAC_SET_MIN:
+	case UAC_SET_MAX:
+	case UAC_SET_RES:
+		value = len;
+		break;
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int audio_get_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int value = -EOPNOTSUPP;
+	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16 len = le16_to_cpu(ctrl->wLength);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u8 *buf = cdev->req->buf;
+
+	pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) {
+		switch (ctrl->bRequest) {
+		case UAC_GET_CUR:
+		case UAC_GET_MIN:
+		case UAC_GET_MAX:
+		case UAC_GET_RES:
+			/* return our sample rate */
+			buf[0] = (u8)SAMPLE_RATE;
+			buf[1] = (u8)(SAMPLE_RATE >> 8);
+			buf[2] = (u8)(SAMPLE_RATE >> 16);
+			value = 3;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return value;
+}
+
+static int
+audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request *req = cdev->req;
+	int value = -EOPNOTSUPP;
+	u16 w_index = le16_to_cpu(ctrl->wIndex);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u16 w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything; interface
+	 * activation uses set_alt().
+	 */
+	switch (ctrl->bRequestType) {
+	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_set_endpoint_req(f, ctrl);
+		break;
+
+	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_get_endpoint_req(f, ctrl);
+		break;
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		pr_debug("audio req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		req->complete = audio_control_complete;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			pr_err("audio response on err %d\n", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct audio_dev *audio = func_to_audio(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int ret;
+
+	pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt);
+
+	ret = config_ep_by_speed(cdev->gadget, f, audio->in_ep);
+	if (ret)
+		return ret;
+
+	usb_ep_enable(audio->in_ep);
+	return 0;
+}
+
+static void audio_disable(struct usb_function *f)
+{
+	struct audio_dev	*audio = func_to_audio(f);
+
+	pr_debug("audio_disable\n");
+	usb_ep_disable(audio->in_ep);
+}
+
+static void audio_free_func(struct usb_function *f)
+{
+	/* no-op */
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void audio_build_desc(struct audio_dev *audio)
+{
+	u8 *sam_freq;
+	int rate;
+
+	/* Set channel numbers */
+	input_terminal_desc.bNrChannels = 2;
+	as_type_i_desc.bNrChannels = 2;
+
+	/* Set sample rates */
+	rate = SAMPLE_RATE;
+	sam_freq = as_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+}
+
+
+static int snd_card_setup(struct usb_configuration *c,
+	struct audio_source_config *config);
+static struct audio_source_instance *to_fi_audio_source(
+	const struct usb_function_instance *fi);
+
+
+/* audio function driver setup/binding */
+static int
+audio_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct audio_dev *audio = func_to_audio(f);
+	int status;
+	struct usb_ep *ep;
+	struct usb_request *req;
+	int i;
+	int err;
+
+	if (IS_ENABLED(CONFIG_USB_CONFIGFS)) {
+		struct audio_source_instance *fi_audio =
+				to_fi_audio_source(f->fi);
+		struct audio_source_config *config =
+				fi_audio->config;
+
+		err = snd_card_setup(c, config);
+		if (err)
+			return err;
+	}
+
+	audio_build_desc(audio);
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ac_interface_desc.bInterfaceNumber = status;
+
+	/* AUDIO_AC_INTERFACE */
+	ac_header_desc.baInterfaceNr[0] = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_interface_alt_0_desc.bInterfaceNumber = status;
+	as_interface_alt_1_desc.bInterfaceNumber = status;
+
+	/* AUDIO_AS_INTERFACE */
+	ac_header_desc.baInterfaceNr[1] = status;
+
+	status = -ENODEV;
+
+	/* allocate our endpoint */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->in_ep = ep;
+	ep->driver_data = audio; /* claim */
+
+	if (gadget_is_dualspeed(c->cdev->gadget))
+		hs_as_in_ep_desc.bEndpointAddress =
+			fs_as_in_ep_desc.bEndpointAddress;
+
+	f->fs_descriptors = fs_audio_desc;
+	f->hs_descriptors = hs_audio_desc;
+
+	for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) {
+		req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE);
+		if (req) {
+			req->context = audio;
+			req->complete = audio_data_complete;
+			audio_req_put(audio, req);
+		} else
+			status = -ENOMEM;
+	}
+
+fail:
+	return status;
+}
+
+static void
+audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct audio_dev *audio = func_to_audio(f);
+	struct usb_request *req;
+
+	while ((req = audio_req_get(audio)))
+		audio_request_free(req, audio->in_ep);
+
+	snd_card_free_when_closed(audio->card);
+	audio->card = NULL;
+	audio->pcm = NULL;
+	audio->substream = NULL;
+	audio->in_ep = NULL;
+
+	if (IS_ENABLED(CONFIG_USB_CONFIGFS)) {
+		struct audio_source_instance *fi_audio =
+				to_fi_audio_source(f->fi);
+		struct audio_source_config *config =
+				fi_audio->config;
+
+		config->card = -1;
+		config->device = -1;
+	}
+}
+
+static void audio_pcm_playback_start(struct audio_dev *audio)
+{
+	audio->start_time = ktime_get();
+	audio->frames_sent = 0;
+	audio_send(audio);
+}
+
+static void audio_pcm_playback_stop(struct audio_dev *audio)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&audio->lock, flags);
+	audio->buffer_start = 0;
+	audio->buffer_end = 0;
+	audio->buffer_pos = 0;
+	spin_unlock_irqrestore(&audio->lock, flags);
+}
+
+static int audio_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_dev *audio = substream->private_data;
+
+	runtime->private_data = audio;
+	runtime->hw = audio_hw_info;
+	snd_pcm_limit_hw_rates(runtime);
+	runtime->hw.channels_max = 2;
+
+	audio->substream = substream;
+
+	/* Add the QoS request and set the latency to 0 */
+	pm_qos_add_request(&audio->pm_qos, PM_QOS_CPU_DMA_LATENCY, 0);
+
+	return 0;
+}
+
+static int audio_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct audio_dev *audio = substream->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&audio->lock, flags);
+
+	/* Remove the QoS request */
+	pm_qos_remove_request(&audio->pm_qos);
+
+	audio->substream = NULL;
+	spin_unlock_irqrestore(&audio->lock, flags);
+
+	return 0;
+}
+
+static int audio_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	unsigned int channels = params_channels(params);
+	unsigned int rate = params_rate(params);
+
+	if (rate != SAMPLE_RATE)
+		return -EINVAL;
+	if (channels != 2)
+		return -EINVAL;
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+		params_buffer_bytes(params));
+}
+
+static int audio_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int audio_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_dev *audio = runtime->private_data;
+
+	audio->period = snd_pcm_lib_period_bytes(substream);
+	audio->period_offset = 0;
+	audio->buffer_start = runtime->dma_area;
+	audio->buffer_end = audio->buffer_start
+		+ snd_pcm_lib_buffer_bytes(substream);
+	audio->buffer_pos = audio->buffer_start;
+
+	return 0;
+}
+
+static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_dev *audio = runtime->private_data;
+	ssize_t bytes = audio->buffer_pos - audio->buffer_start;
+
+	/* return offset of next frame to fill in our buffer */
+	return bytes_to_frames(runtime, bytes);
+}
+
+static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct audio_dev *audio = substream->runtime->private_data;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		audio_pcm_playback_start(audio);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		audio_pcm_playback_stop(audio);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static struct audio_dev _audio_dev = {
+	.func = {
+		.name = "audio_source",
+		.bind = audio_bind,
+		.unbind = audio_unbind,
+		.set_alt = audio_set_alt,
+		.setup = audio_setup,
+		.disable = audio_disable,
+		.free_func = audio_free_func,
+	},
+	.lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock),
+	.idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs),
+};
+
+static struct snd_pcm_ops audio_playback_ops = {
+	.open		= audio_pcm_open,
+	.close		= audio_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= audio_pcm_hw_params,
+	.hw_free	= audio_pcm_hw_free,
+	.prepare	= audio_pcm_prepare,
+	.trigger	= audio_pcm_playback_trigger,
+	.pointer	= audio_pcm_pointer,
+};
+
+int audio_source_bind_config(struct usb_configuration *c,
+		struct audio_source_config *config)
+{
+	struct audio_dev *audio;
+	int err;
+
+	config->card = -1;
+	config->device = -1;
+
+	audio = &_audio_dev;
+
+	err = snd_card_setup(c, config);
+	if (err)
+		return err;
+
+	err = usb_add_function(c, &audio->func);
+	if (err)
+		goto add_fail;
+
+	return 0;
+
+add_fail:
+	snd_card_free(audio->card);
+	return err;
+}
+
+static int snd_card_setup(struct usb_configuration *c,
+		struct audio_source_config *config)
+{
+	struct audio_dev *audio;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	int err;
+
+	audio = &_audio_dev;
+
+	err = snd_card_new(&c->cdev->gadget->dev,
+			SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			THIS_MODULE, 0, &card);
+	if (err)
+		return err;
+
+	err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm);
+	if (err)
+		goto pcm_fail;
+
+	pcm->private_data = audio;
+	pcm->info_flags = 0;
+	audio->pcm = pcm;
+
+	strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name));
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops);
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+				NULL, 0, 64 * 1024);
+
+	strlcpy(card->driver, "audio_source", sizeof(card->driver));
+	strlcpy(card->shortname, card->driver, sizeof(card->shortname));
+	strlcpy(card->longname, "USB accessory audio source",
+		sizeof(card->longname));
+
+	err = snd_card_register(card);
+	if (err)
+		goto register_fail;
+
+	config->card = pcm->card->number;
+	config->device = pcm->device;
+	audio->card = card;
+	return 0;
+
+register_fail:
+pcm_fail:
+	snd_card_free(audio->card);
+	return err;
+}
+
+static struct audio_source_instance *to_audio_source_instance(
+					struct config_item *item)
+{
+	return container_of(to_config_group(item), struct audio_source_instance,
+		func_inst.group);
+}
+
+static struct audio_source_instance *to_fi_audio_source(
+					const struct usb_function_instance *fi)
+{
+	return container_of(fi, struct audio_source_instance, func_inst);
+}
+
+static void audio_source_attr_release(struct config_item *item)
+{
+	struct audio_source_instance *fi_audio = to_audio_source_instance(item);
+
+	usb_put_function_instance(&fi_audio->func_inst);
+}
+
+static int audio_source_set_inst_name(struct usb_function_instance *fi,
+					const char *name)
+{
+	struct audio_source_instance *fi_audio;
+	char *ptr;
+	int name_len;
+
+	name_len = strlen(name) + 1;
+	if (name_len > MAX_INST_NAME_LEN)
+		return -ENAMETOOLONG;
+
+	ptr = kstrndup(name, name_len, GFP_KERNEL);
+	if (!ptr)
+		return -ENOMEM;
+
+	fi_audio = to_fi_audio_source(fi);
+	fi_audio->name = ptr;
+
+	return 0;
+}
+
+static void audio_source_free_inst(struct usb_function_instance *fi)
+{
+	struct audio_source_instance *fi_audio;
+
+	fi_audio = to_fi_audio_source(fi);
+	device_destroy(fi_audio->audio_device->class,
+			fi_audio->audio_device->devt);
+	kfree(fi_audio->name);
+	kfree(fi_audio->config);
+}
+
+static ssize_t audio_source_pcm_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct audio_source_instance *fi_audio = dev_get_drvdata(dev);
+	struct audio_source_config *config = fi_audio->config;
+
+	/* print PCM card and device numbers */
+	return sprintf(buf, "%d %d\n", config->card, config->device);
+}
+
+struct device *create_function_device(char *name);
+
+static struct usb_function_instance *audio_source_alloc_inst(void)
+{
+	struct audio_source_instance *fi_audio;
+	struct device_attribute **attrs;
+	struct device_attribute *attr;
+	struct device *dev;
+	void *err_ptr;
+	int err = 0;
+
+	fi_audio = kzalloc(sizeof(*fi_audio), GFP_KERNEL);
+	if (!fi_audio)
+		return ERR_PTR(-ENOMEM);
+
+	fi_audio->func_inst.set_inst_name = audio_source_set_inst_name;
+	fi_audio->func_inst.free_func_inst = audio_source_free_inst;
+
+	fi_audio->config = kzalloc(sizeof(struct audio_source_config),
+							GFP_KERNEL);
+	if (!fi_audio->config) {
+		err_ptr = ERR_PTR(-ENOMEM);
+		goto fail_audio;
+	}
+
+	config_group_init_type_name(&fi_audio->func_inst.group, "",
+						&audio_source_func_type);
+	dev = create_function_device("f_audio_source");
+
+	if (IS_ERR(dev)) {
+		err_ptr = dev;
+		goto fail_audio_config;
+	}
+
+	fi_audio->config->card = -1;
+	fi_audio->config->device = -1;
+	fi_audio->audio_device = dev;
+
+	attrs = audio_source_function_attributes;
+	if (attrs) {
+		while ((attr = *attrs++) && !err)
+			err = device_create_file(dev, attr);
+		if (err) {
+			err_ptr = ERR_PTR(-EINVAL);
+			goto fail_device;
+		}
+	}
+
+	dev_set_drvdata(dev, fi_audio);
+	_audio_dev.config = fi_audio->config;
+
+	return  &fi_audio->func_inst;
+
+fail_device:
+	device_destroy(dev->class, dev->devt);
+fail_audio_config:
+	kfree(fi_audio->config);
+fail_audio:
+	kfree(fi_audio);
+	return err_ptr;
+
+}
+
+static struct usb_function *audio_source_alloc(struct usb_function_instance *fi)
+{
+	return &_audio_dev.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(audio_source, audio_source_alloc_inst,
+			audio_source_alloc);
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_ecm.c b/marvell/linux/drivers/usb/gadget/function/f_ecm.c
new file mode 100644
index 0000000..4001093
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_ecm.c
@@ -0,0 +1,1031 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_ecm.c -- USB CDC Ethernet (ECM) link function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/completion.h>
+#include <linux/string_helpers.h>
+
+#include "u_ether.h"
+
+
+/*
+ * This function is a "CDC Ethernet Networking Control Model" (CDC ECM)
+ * Ethernet link.  The data transfer model is simple (packets sent and
+ * received over bulk endpoints using normal short packet termination),
+ * and the control model exposes various data and optional notifications.
+ *
+ * ECM is well standardized and (except for Microsoft) supported by most
+ * operating systems with USB host support.  It's the preferred interop
+ * solution for Ethernet over USB, at least for firmware based solutions.
+ * (Hardware solutions tend to be more minimalist.)  A newer and simpler
+ * "Ethernet Emulation Model" (CDC EEM) hasn't yet caught on.
+ *
+ * Note that ECM requires the use of "alternate settings" for its data
+ * interface.  This means that the set_alt() method has real work to do,
+ * and also means that a get_alt() method is required.
+ */
+
+
+enum ecm_notify_state {
+	ECM_NOTIFY_NONE,		/* don't notify */
+	ECM_NOTIFY_CONNECT,		/* issue CONNECT next */
+	ECM_NOTIFY_SPEED,		/* issue SPEED_CHANGE next */
+};
+
+struct f_ecm {
+	struct gether			port;
+	u8				ctrl_id, data_id;
+
+	char				ethaddr[14];
+
+	struct usb_ep			*notify;
+	struct usb_request		*notify_req;
+	u8				notify_state;
+	atomic_t			notify_count;
+	bool				is_open;
+
+	/* FIXME is_open needs some irq-ish locking
+	 * ... possibly the same as port.ioport
+	 */
+};
+
+static inline struct f_ecm *func_to_ecm(struct usb_function *f)
+{
+	return container_of(f, struct f_ecm, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static inline unsigned ecm_bitrate(struct usb_gadget *g)
+{
+	if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+		return 13 * 1024 * 8 * 1000 * 8;
+	else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return 13 * 512 * 8 * 1000 * 8;
+	else
+		return 19 * 64 * 1 * 1000 * 8;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Include the status endpoint if we can, even though it's optional.
+ *
+ * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
+ * packet, to simplify cancellation; and a big transfer interval, to
+ * waste less bandwidth.
+ *
+ * Some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even
+ * if they ignore the connect/disconnect notifications that real aether
+ * can provide.  More advanced cdc configurations might want to support
+ * encapsulated commands (vendor-specific, using control-OUT).
+ */
+
+#define ECM_STATUS_INTERVAL_MS		32
+#define ECM_STATUS_BYTECOUNT		16	/* 8 byte header + data */
+
+
+/* interface descriptor: */
+
+static struct usb_interface_assoc_descriptor
+ecm_iad_descriptor = {
+	.bLength =		sizeof ecm_iad_descriptor,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,	/* control + data */
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_ETHERNET,
+	.bFunctionProtocol =	USB_CDC_PROTO_NONE,
+	/* .iFunction =		DYNAMIC */
+};
+
+
+static struct usb_interface_descriptor ecm_control_intf = {
+	.bLength =		sizeof ecm_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	/* status endpoint is optional; this could be patched later */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_ETHERNET,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc ecm_header_desc = {
+	.bLength =		sizeof ecm_header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_union_desc ecm_union_desc = {
+	.bLength =		sizeof(ecm_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+static struct usb_cdc_ether_desc ecm_desc = {
+	.bLength =		sizeof ecm_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ETHERNET_TYPE,
+
+	/* this descriptor actually adds value, surprise! */
+	/* .iMACAddress = DYNAMIC */
+	.bmEthernetStatistics =	cpu_to_le32(0), /* no statistics */
+	.wMaxSegmentSize =	cpu_to_le16(ETH_FRAME_LEN),
+	.wNumberMCFilters =	cpu_to_le16(0),
+	.bNumberPowerFilters =	0,
+};
+
+/* the default data interface has no endpoints ... */
+
+static struct usb_interface_descriptor ecm_data_nop_intf = {
+	.bLength =		sizeof ecm_data_nop_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/* ... but the "real" data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor ecm_data_intf = {
+	.bLength =		sizeof ecm_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_ecm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_STATUS_BYTECOUNT),
+	.bInterval =		ECM_STATUS_INTERVAL_MS,
+};
+
+static struct usb_endpoint_descriptor fs_ecm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_ecm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *ecm_fs_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_iad_descriptor,
+	(struct usb_descriptor_header *) &ecm_control_intf,
+	(struct usb_descriptor_header *) &ecm_header_desc,
+	(struct usb_descriptor_header *) &ecm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &fs_ecm_notify_desc,
+
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_data_intf,
+	(struct usb_descriptor_header *) &fs_ecm_in_desc,
+	(struct usb_descriptor_header *) &fs_ecm_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_ecm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS),
+};
+
+static struct usb_endpoint_descriptor hs_ecm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_ecm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *ecm_hs_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_iad_descriptor,
+	(struct usb_descriptor_header *) &ecm_control_intf,
+	(struct usb_descriptor_header *) &ecm_header_desc,
+	(struct usb_descriptor_header *) &ecm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &hs_ecm_notify_desc,
+
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_data_intf,
+	(struct usb_descriptor_header *) &hs_ecm_in_desc,
+	(struct usb_descriptor_header *) &hs_ecm_out_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_ecm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(ECM_STATUS_INTERVAL_MS),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ecm_intr_comp_desc = {
+	.bLength =		sizeof ss_ecm_intr_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(ECM_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ss_ecm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_ecm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ecm_bulk_comp_desc = {
+	.bLength =		sizeof ss_ecm_bulk_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *ecm_ss_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_iad_descriptor,
+	(struct usb_descriptor_header *) &ecm_control_intf,
+	(struct usb_descriptor_header *) &ecm_header_desc,
+	(struct usb_descriptor_header *) &ecm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &ss_ecm_notify_desc,
+	(struct usb_descriptor_header *) &ss_ecm_intr_comp_desc,
+
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_data_intf,
+	(struct usb_descriptor_header *) &ss_ecm_in_desc,
+	(struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_ecm_out_desc,
+	(struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string ecm_string_defs[] = {
+	[0].s = "CDC Ethernet Control Model (ECM)",
+	[1].s = "",
+	[2].s = "CDC Ethernet Data",
+	[3].s = "CDC ECM",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings ecm_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		ecm_string_defs,
+};
+
+static struct usb_gadget_strings *ecm_strings[] = {
+	&ecm_string_table,
+	NULL,
+};
+
+#define ENABLE_USB_RESTART_ON_MAC_RESUM	1
+
+static struct f_ecm *g_ecm;
+static bool ecm_suspended = false;
+static void ecm_close(struct gether *geth);
+static void ecm_open(struct gether *geth);
+static void ecm_restart_work_fn(struct work_struct *ecm_work);
+static DECLARE_DELAYED_WORK(ecm_restart_work, ecm_restart_work_fn);
+
+#if defined(CONFIG_USB_MV_UDC) || defined(CONFIG_USB_DWC3)
+#ifdef ENABLE_USB_RESTART_ON_MAC_RESUM
+static void usb_restart_work_fn(struct work_struct *ecm_work);
+static DECLARE_DELAYED_WORK(usb_restart_work, usb_restart_work_fn);
+
+/*-------------------------------------------------------------------------*/
+extern void android_dev_enable(uint8_t enabled);
+extern int asr_udc_register_resume_notifier(struct notifier_block *nb);
+extern int asr_udc_unregister_resume_notifier(struct notifier_block *nb);
+static int
+ecm_resume_notify(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	pr_info("%s: suspended: %d\n", __func__, ecm_suspended);
+#ifdef CONFIG_USB_ANDROID_DETECT_HOST_OS
+	if (host_os_is_apple())
+		schedule_delayed_work(&usb_restart_work, (3 * HZ));
+#endif
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ecm_notifier = {
+	.notifier_call = ecm_resume_notify,
+};
+
+static void usb_restart_work_fn(struct work_struct *ecm_work)
+{
+	pr_info("%s\n", __func__);
+	android_dev_enable(0);
+	android_dev_enable(1);
+	ecm_suspended = false;
+}
+#endif
+#endif
+
+static void ecm_restart_work_fn(struct work_struct *ecm_work)
+{
+	pr_info("%s, ecm_suspended: %d\n", __func__, ecm_suspended);
+	ecm_close(&g_ecm->port);
+	msleep(1000);
+	ecm_open(&g_ecm->port);
+}
+
+static void ecm_do_notify(struct f_ecm *ecm)
+{
+	struct usb_request		*req = ecm->notify_req;
+	struct usb_cdc_notification	*event;
+	struct usb_composite_dev	*cdev = ecm->port.func.config->cdev;
+	__le32				*data;
+	int				status;
+
+	pr_info("%s state=%d, req=0x%x\n", __func__,
+		ecm->notify_state, (u32)req);
+	/* notification already in flight? */
+	if (!req)
+		return;
+
+	if (atomic_read(&ecm->notify_count)) {
+		pr_emerg("%s notify_count=%d\n", __func__, atomic_read(&ecm->notify_count));
+		return;
+	}
+	event = req->buf;
+	switch (ecm->notify_state) {
+	case ECM_NOTIFY_NONE:
+		return;
+
+	case ECM_NOTIFY_CONNECT:
+		event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+		if (ecm->is_open)
+			event->wValue = cpu_to_le16(1);
+		else
+			event->wValue = cpu_to_le16(0);
+		event->wLength = 0;
+		req->length = sizeof *event;
+
+		INFO(cdev, "notify connect %s\n",
+				ecm->is_open ? "true" : "false");
+		ecm->notify_state = ECM_NOTIFY_SPEED;
+		break;
+
+	case ECM_NOTIFY_SPEED:
+		event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(8);
+		req->length = ECM_STATUS_BYTECOUNT;
+
+		/* SPEED_CHANGE data is up/down speeds in bits/sec */
+		data = req->buf + sizeof *event;
+		data[0] = cpu_to_le32(ecm_bitrate(cdev->gadget));
+		data[1] = data[0];
+
+		INFO(cdev, "notify speed %d\n", ecm_bitrate(cdev->gadget));
+		ecm->notify_state = ECM_NOTIFY_NONE;
+		break;
+	}
+	event->bmRequestType = 0xA1;
+	event->wIndex = cpu_to_le16(ecm->ctrl_id);
+
+	/* ecm->notify_req = NULL; */
+	atomic_inc(&ecm->notify_count);
+	status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC);
+	if (status < 0) {
+		atomic_dec(&ecm->notify_count);
+		/* ecm->notify_req = req; */
+		INFO(cdev, "notify --> %d\n", status);
+	}
+}
+
+static void ecm_notify(struct f_ecm *ecm)
+{
+	/* NOTE on most versions of Linux, host side cdc-ethernet
+	 * won't listen for notifications until its netdevice opens.
+	 * The first notification then sits in the FIFO for a long
+	 * time, and the second one is queued.
+	 */
+	ecm->notify_state = ECM_NOTIFY_CONNECT;
+	ecm_do_notify(ecm);
+}
+
+static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_ecm			*ecm = req->context;
+	struct usb_composite_dev	*cdev = ecm->port.func.config->cdev;
+	struct usb_cdc_notification	*event = req->buf;
+
+	switch (req->status) {
+	case 0:
+		INFO(cdev, "notify: 0x%x done\n", event->bNotificationType);
+		/* no fault */
+		atomic_dec(&ecm->notify_count);
+		break;
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		INFO(cdev, "notify: 0x%x dev shutdown\n",
+			event->bNotificationType);
+		atomic_set(&ecm->notify_count, 0);
+		ecm->notify_state = ECM_NOTIFY_NONE;
+		break;
+	default:
+		INFO(cdev, "event %02x --> %d\n",
+			event->bNotificationType, req->status);
+		atomic_dec(&ecm->notify_count);
+		break;
+	}
+	/* ecm->notify_req = req; */
+	ecm_do_notify(ecm);
+}
+
+static int ecm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_ecm		*ecm = func_to_ecm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SET_ETHERNET_PACKET_FILTER:
+		/* see 6.2.30: no data, wIndex = interface,
+		 * wValue = packet filter bitmap
+		 */
+		if (w_length != 0 || w_index != ecm->ctrl_id)
+			goto invalid;
+		INFO(cdev, "packet filter %02x, ecm_suspended: %d\n", w_value, ecm_suspended);
+		/* REVISIT locking of cdc_filter.  This assumes the UDC
+		 * driver won't have a concurrent packet TX irq running on
+		 * another CPU; or that if it does, this write is atomic...
+		 */
+		ecm->port.cdc_filter = w_value;
+		value = 0;
+#ifdef CONFIG_USB_ANDROID_DETECT_HOST_OS
+		if (host_os_is_apple()) {
+			if (w_value == 0)
+				ecm_suspended = true;
+
+#if defined(CONFIG_USB_MV_UDC) || defined(CONFIG_USB_DWC3)
+#ifdef ENABLE_USB_RESTART_ON_MAC_RESUM
+			if (w_value && ecm_suspended)
+				cancel_delayed_work(&usb_restart_work);
+#endif
+#endif
+
+			if ((w_value && (w_value != 0x1e) && ecm_suspended) || w_value == 0xc) {
+				INFO(cdev, "restart ecm network\n");
+				schedule_delayed_work(&ecm_restart_work, HZ);
+				ecm_suspended = false;
+			}
+		}
+#endif
+		break;
+
+	/* and optionally:
+	 * case USB_CDC_SEND_ENCAPSULATED_COMMAND:
+	 * case USB_CDC_GET_ENCAPSULATED_RESPONSE:
+	 * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
+	 * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_STATISTIC:
+	 */
+
+	default:
+invalid:
+		INFO(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "ecm req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "ecm req %02x.%02x response err %d\n",
+					ctrl->bRequestType, ctrl->bRequest,
+					value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+
+static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_ecm		*ecm = func_to_ecm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* Control interface has only altsetting 0 */
+	if (intf == ecm->ctrl_id) {
+		if (alt != 0)
+			goto fail;
+
+		if (ecm->notify->driver_data) {
+			INFO(cdev, "reset ecm control %d\n", intf);
+			usb_ep_disable(ecm->notify);
+		}
+		if (!(ecm->notify->desc)) {
+			INFO(cdev, "init ecm ctrl %d\n", intf);
+			if (config_ep_by_speed(cdev->gadget, f, ecm->notify))
+				goto fail;
+		}
+		usb_ep_enable(ecm->notify);
+		ecm->notify->driver_data = ecm;
+
+	/* Data interface has two altsettings, 0 and 1 */
+	} else if (intf == ecm->data_id) {
+		if (alt > 1)
+			goto fail;
+
+		if (ecm->port.in_ep->driver_data) {
+			INFO(cdev, "reset ecm\n");
+			gether_disconnect(&ecm->port);
+		}
+
+		if (!ecm->port.in_ep->desc ||
+		    !ecm->port.out_ep->desc) {
+			INFO(cdev, "init ecm\n");
+			if (config_ep_by_speed(cdev->gadget, f,
+					       ecm->port.in_ep) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       ecm->port.out_ep)) {
+				ecm->port.in_ep->desc = NULL;
+				ecm->port.out_ep->desc = NULL;
+				goto fail;
+			}
+		}
+
+		/* CDC Ethernet only sends data in non-default altsettings.
+		 * Changing altsettings resets filters, statistics, etc.
+		 */
+		if (alt == 1) {
+			struct net_device	*net;
+
+			/* Enable zlps by default for ECM conformance;
+			 * override for musb_hdrc (avoids txdma ovhead).
+			 */
+			ecm->port.is_zlp_ok = !(gadget_is_musbhdrc(cdev->gadget)
+				);
+			ecm->port.cdc_filter = DEFAULT_FILTER;
+#ifdef CONFIG_ASR_TOE
+			ecm->port.ueth_type = UETHER_ECM;
+#endif
+			INFO(cdev, "activate ecm\n");
+			net = gether_connect(&ecm->port);
+			if (IS_ERR(net))
+				return PTR_ERR(net);
+		}
+
+		/* NOTE this can be a minor disagreement with the ECM spec,
+		 * which says speed notifications will "always" follow
+		 * connection notifications.  But we allow one connect to
+		 * follow another (if the first is in flight), and instead
+		 * just guarantee that a speed notification is always sent.
+		 */
+		ecm_notify(ecm);
+	} else
+		goto fail;
+
+	return 0;
+fail:
+	return -EINVAL;
+}
+
+/* Because the data interface supports multiple altsettings,
+ * this ECM function *MUST* implement a get_alt() method.
+ */
+static int ecm_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_ecm		*ecm = func_to_ecm(f);
+
+	if (intf == ecm->ctrl_id)
+		return 0;
+	return ecm->port.in_ep->driver_data ? 1 : 0;
+}
+
+static void ecm_disable(struct usb_function *f)
+{
+	struct f_ecm		*ecm = func_to_ecm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	INFO(cdev, "ecm deactivated\n");
+#ifdef CONFIG_ASR_TOE
+	ecm->port.ueth_type = UETHER_UNKNOWN;
+#endif
+
+	if (ecm->port.in_ep->driver_data)
+		gether_disconnect(&ecm->port);
+
+	if (ecm->notify->driver_data) {
+		usb_ep_disable(ecm->notify);
+		ecm->notify->driver_data = NULL;
+		ecm->notify->desc = NULL;
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Callbacks let us notify the host about connect/disconnect when the
+ * net device is opened or closed.
+ *
+ * For testing, note that link states on this side include both opened
+ * and closed variants of:
+ *
+ *   - disconnected/unconfigured
+ *   - configured but inactive (data alt 0)
+ *   - configured and active (data alt 1)
+ *
+ * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
+ * SET_INTERFACE (altsetting).  Remember also that "configured" doesn't
+ * imply the host is actually polling the notification endpoint, and
+ * likewise that "active" doesn't imply it's actually using the data
+ * endpoints for traffic.
+ */
+
+static void ecm_open(struct gether *geth)
+{
+	struct f_ecm		*ecm = func_to_ecm(&geth->func);
+
+	DBG(ecm->port.func.config->cdev, "%s\n", __func__);
+
+	ecm->is_open = true;
+	ecm_notify(ecm);
+}
+
+static void ecm_close(struct gether *geth)
+{
+	struct f_ecm		*ecm = func_to_ecm(&geth->func);
+
+	DBG(ecm->port.func.config->cdev, "%s\n", __func__);
+
+	ecm->is_open = false;
+	ecm_notify(ecm);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int
+ecm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_ecm		*ecm = func_to_ecm(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ecm->ctrl_id = status;
+	ecm_iad_descriptor.bFirstInterface = status;
+
+	ecm_control_intf.bInterfaceNumber = status;
+	ecm_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ecm->data_id = status;
+
+	ecm_data_nop_intf.bInterfaceNumber = status;
+	ecm_data_intf.bInterfaceNumber = status;
+	ecm_union_desc.bSlaveInterface0 = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc);
+	if (!ep)
+		goto fail;
+	ecm->port.in_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc);
+	if (!ep)
+		goto fail;
+	ecm->port.out_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	/* NOTE:  a status/notification endpoint is *OPTIONAL* but we
+	 * don't treat it that way.  It's simpler, and some newer CDC
+	 * profiles (wireless handsets) no longer treat it as optional.
+	 */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc);
+	if (!ep)
+		goto fail;
+	ecm->notify = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	status = -ENOMEM;
+
+	/* allocate notification request and buffer */
+	ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!ecm->notify_req)
+		goto fail;
+	ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL);
+	if (!ecm->notify_req->buf)
+		goto fail;
+	ecm->notify_req->context = ecm;
+	ecm->notify_req->complete = ecm_notify_complete;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	hs_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress;
+	hs_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress;
+	hs_ecm_notify_desc.bEndpointAddress =
+		fs_ecm_notify_desc.bEndpointAddress;
+
+	ss_ecm_in_desc.bEndpointAddress = fs_ecm_in_desc.bEndpointAddress;
+	ss_ecm_out_desc.bEndpointAddress = fs_ecm_out_desc.bEndpointAddress;
+	ss_ecm_notify_desc.bEndpointAddress =
+		fs_ecm_notify_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function,
+			ecm_ss_function, ecm_ss_function);
+	if (status)
+		goto fail;
+
+	/* NOTE:  all that is done without knowing or caring about
+	 * the network link ... which is unavailable to this code
+	 * until we're activated via set_alt().
+	 */
+
+	ecm->port.open = ecm_open;
+	ecm->port.close = ecm_close;
+#if defined(CONFIG_USB_MV_UDC) || defined(CONFIG_USB_DWC3)
+#ifdef ENABLE_USB_RESTART_ON_MAC_RESUM
+	asr_udc_register_resume_notifier(&ecm_notifier);
+#endif
+#endif
+	ecm_suspended = false;
+
+	DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			ecm->port.in_ep->name, ecm->port.out_ep->name,
+			ecm->notify->name);
+
+#ifdef CONFIG_ASR_TOE
+	ecm->port.ueth_type = UETHER_ECM;
+#endif
+
+	return 0;
+
+fail:
+	if (ecm->notify_req) {
+		kfree(ecm->notify_req->buf);
+		usb_ep_free_request(ecm->notify, ecm->notify_req);
+		ecm->notify_req = NULL;
+	}
+
+	/* we might as well release our claims on endpoints */
+	if (ecm->notify)
+		ecm->notify->driver_data = NULL;
+	if (ecm->port.out_ep)
+		ecm->port.out_ep->driver_data = NULL;
+	if (ecm->port.in_ep)
+		ecm->port.in_ep->driver_data = NULL;
+
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static void
+ecm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_ecm		*ecm = func_to_ecm(f);
+
+	DBG(c->cdev, "ecm unbind\n");
+
+#ifdef CONFIG_ASR_TOE
+	ecm->port.ueth_type = UETHER_UNKNOWN;
+#endif
+
+#if defined(CONFIG_USB_MV_UDC) || defined(CONFIG_USB_DWC3)
+#ifdef ENABLE_USB_RESTART_ON_MAC_RESUM
+	asr_udc_unregister_resume_notifier(&ecm_notifier);
+	cancel_delayed_work(&usb_restart_work);
+#endif
+#endif
+	cancel_delayed_work(&ecm_restart_work);
+	ecm_suspended = false;
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		ecm_string_defs[0].id = 0;
+#endif
+	usb_free_all_descriptors(f);
+	if (atomic_read(&ecm->notify_count)) {
+		pr_info("%s: dq nreq %d\n", __func__, atomic_read(&ecm->notify_count));
+		usb_ep_dequeue(ecm->notify, ecm->notify_req);
+		atomic_set(&ecm->notify_count, 0);
+	}
+	kfree(ecm->notify_req->buf);
+	usb_ep_free_request(ecm->notify, ecm->notify_req);
+	ecm->notify_req = NULL;
+	kfree(ecm);
+}
+#ifdef CONFIG_ASR_TOE
+#define CONFIG_ECM_INPUT_AS_RNDIS	1
+#ifdef CONFIG_ECM_INPUT_AS_RNDIS
+static void ecm_add_rndis_header(struct sk_buff *skb)
+{
+	struct rndis_packet_msg_type *header;
+
+	if (!skb)
+		return;
+	header = (void *)skb_push(skb, sizeof(*header));
+	memset(header, 0, sizeof *header);
+	header->MessageType = cpu_to_le32(RNDIS_MSG_PACKET);
+	header->MessageLength = cpu_to_le32(skb->len);
+	header->DataOffset = cpu_to_le32(36);
+	header->DataLength = cpu_to_le32(skb->len - sizeof(*header));
+	v7_dma_flush_range((void *)header, (void *)((u32)header + sizeof(*header)));
+}
+
+static int ecm_unwrap_skb(struct gether *port,
+			  struct sk_buff*skb,
+			  struct sk_buff_head *list)
+{
+	ecm_add_rndis_header(skb);
+	skb_queue_tail(list, skb);
+	return 0;
+}
+#endif
+#endif
+/**
+ * ecm_bind_config - add CDC Ethernet network link to a configuration
+ * @c: the configuration to support the network link
+ * @ethaddr: a buffer in which the ethernet address of the host side
+ *	side of the link was recorded
+ * @dev: eth_dev structure
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gether_setup().  Caller is also responsible
+ * for calling @gether_cleanup() before module unload.
+ */
+int
+ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		struct eth_dev *dev)
+{
+	struct f_ecm	*ecm;
+	int		status;
+
+	if (!can_support_ecm(c->cdev->gadget) || !ethaddr)
+		return -EINVAL;
+
+	if (ecm_string_defs[0].id == 0) {
+		status = usb_string_ids_tab(c->cdev, ecm_string_defs);
+		if (status)
+			return status;
+
+		ecm_control_intf.iInterface = ecm_string_defs[0].id;
+		ecm_data_intf.iInterface = ecm_string_defs[2].id;
+		ecm_desc.iMACAddress = ecm_string_defs[1].id;
+		ecm_iad_descriptor.iFunction = ecm_string_defs[3].id;
+	}
+
+	/* allocate and initialize one new instance */
+	ecm = kzalloc(sizeof *ecm, GFP_KERNEL);
+	if (!ecm)
+		return -ENOMEM;
+
+	/* export host's Ethernet address in CDC format */
+	snprintf(ecm->ethaddr, sizeof ecm->ethaddr, "%pm", ethaddr);
+	string_upper(ecm->ethaddr, ecm->ethaddr);
+	ecm_string_defs[1].s = ecm->ethaddr;
+
+	ecm->port.ioport = dev;
+	ecm->port.cdc_filter = DEFAULT_FILTER;
+
+	ecm->port.func.name = "cdc_ethernet";
+	ecm->port.func.strings = ecm_strings;
+	/* descriptors are per-instance copies */
+	ecm->port.func.bind = ecm_bind;
+	ecm->port.func.unbind = ecm_unbind;
+	ecm->port.func.set_alt = ecm_set_alt;
+	ecm->port.func.get_alt = ecm_get_alt;
+	ecm->port.func.setup = ecm_setup;
+	ecm->port.func.disable = ecm_disable;
+
+#ifdef CONFIG_ASR_TOE
+#ifdef CONFIG_ECM_INPUT_AS_RNDIS
+	ecm->port.unwrap = ecm_unwrap_skb;
+#endif
+#endif
+
+	status = usb_add_function(c, &ecm->port.func);
+	if (status)
+		kfree(ecm);
+
+	g_ecm = ecm;
+	ecm_suspended = false;
+	return status;
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_eem.c b/marvell/linux/drivers/usb/gadget/function/f_eem.c
new file mode 100644
index 0000000..95ef8bc
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_eem.c
@@ -0,0 +1,682 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_eem.c -- USB CDC Ethernet (EEM) link function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ * Copyright (C) 2009 EF Johnson Technologies
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/crc32.h>
+#include <linux/slab.h>
+
+#include "u_ether.h"
+#include "u_ether_configfs.h"
+#include "u_eem.h"
+
+#define EEM_HLEN 2
+
+/*
+ * This function is a "CDC Ethernet Emulation Model" (CDC EEM)
+ * Ethernet link.
+ */
+
+struct f_eem {
+	struct gether			port;
+	u8				ctrl_id;
+};
+
+struct in_context {
+	struct sk_buff	*skb;
+	struct usb_ep	*ep;
+};
+
+static inline struct f_eem *func_to_eem(struct usb_function *f)
+{
+	return container_of(f, struct f_eem, port.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor eem_intf = {
+	.bLength =		sizeof eem_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_EEM,
+	.bInterfaceProtocol =	USB_CDC_PROTO_EEM,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor eem_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor eem_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *eem_fs_function[] = {
+	/* CDC EEM control descriptors */
+	(struct usb_descriptor_header *) &eem_intf,
+	(struct usb_descriptor_header *) &eem_fs_in_desc,
+	(struct usb_descriptor_header *) &eem_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor eem_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor eem_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *eem_hs_function[] = {
+	/* CDC EEM control descriptors */
+	(struct usb_descriptor_header *) &eem_intf,
+	(struct usb_descriptor_header *) &eem_hs_in_desc,
+	(struct usb_descriptor_header *) &eem_hs_out_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor eem_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor eem_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc = {
+	.bLength =		sizeof eem_ss_bulk_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *eem_ss_function[] = {
+	/* CDC EEM control descriptors */
+	(struct usb_descriptor_header *) &eem_intf,
+	(struct usb_descriptor_header *) &eem_ss_in_desc,
+	(struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &eem_ss_out_desc,
+	(struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string eem_string_defs[] = {
+	[0].s = "CDC Ethernet Emulation Model (EEM)",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings eem_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		eem_string_defs,
+};
+
+static struct usb_gadget_strings *eem_strings[] = {
+	&eem_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int eem_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+		ctrl->bRequestType, ctrl->bRequest,
+		w_value, w_index, w_length);
+
+	/* device either stalls (value < 0) or reports success */
+	return -EOPNOTSUPP;
+}
+
+
+static int eem_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_eem		*eem = func_to_eem(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct net_device	*net;
+
+	/* we know alt == 0, so this is an activation or a reset */
+	if (alt != 0)
+		goto fail;
+
+	if (intf == eem->ctrl_id) {
+		DBG(cdev, "reset eem\n");
+		gether_disconnect(&eem->port);
+
+		if (!eem->port.in_ep->desc || !eem->port.out_ep->desc) {
+			DBG(cdev, "init eem\n");
+			if (config_ep_by_speed(cdev->gadget, f,
+					       eem->port.in_ep) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       eem->port.out_ep)) {
+				eem->port.in_ep->desc = NULL;
+				eem->port.out_ep->desc = NULL;
+				goto fail;
+			}
+		}
+
+		/* zlps should not occur because zero-length EEM packets
+		 * will be inserted in those cases where they would occur
+		 */
+		eem->port.is_zlp_ok = 1;
+		eem->port.cdc_filter = DEFAULT_FILTER;
+		DBG(cdev, "activate eem\n");
+		net = gether_connect(&eem->port);
+		if (IS_ERR(net))
+			return PTR_ERR(net);
+	} else
+		goto fail;
+
+	return 0;
+fail:
+	return -EINVAL;
+}
+
+static void eem_disable(struct usb_function *f)
+{
+	struct f_eem		*eem = func_to_eem(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "eem deactivated\n");
+
+	if (eem->port.in_ep->enabled)
+		gether_disconnect(&eem->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* EEM function driver setup/binding */
+
+static int eem_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_eem		*eem = func_to_eem(f);
+	struct usb_string	*us;
+	int			status;
+	struct usb_ep		*ep;
+
+	struct f_eem_opts	*eem_opts;
+
+	eem_opts = container_of(f->fi, struct f_eem_opts, func_inst);
+	/*
+	 * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+	 * configurations are bound in sequence with list_for_each_entry,
+	 * in each configuration its functions are bound in sequence
+	 * with list_for_each_entry, so we assume no race condition
+	 * with regard to eem_opts->bound access
+	 */
+	if (!eem_opts->bound) {
+		mutex_lock(&eem_opts->lock);
+		gether_set_gadget(eem_opts->net, cdev->gadget);
+		status = gether_register_netdev(eem_opts->net);
+		mutex_unlock(&eem_opts->lock);
+		if (status)
+			return status;
+		eem_opts->bound = true;
+	}
+
+	us = usb_gstrings_attach(cdev, eem_strings,
+				 ARRAY_SIZE(eem_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	eem_intf.iInterface = us[0].id;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	eem->ctrl_id = status;
+	eem_intf.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc);
+	if (!ep)
+		goto fail;
+	eem->port.in_ep = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc);
+	if (!ep)
+		goto fail;
+	eem->port.out_ep = ep;
+
+	status = -ENOMEM;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	eem_hs_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress;
+	eem_hs_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress;
+
+	eem_ss_in_desc.bEndpointAddress = eem_fs_in_desc.bEndpointAddress;
+	eem_ss_out_desc.bEndpointAddress = eem_fs_out_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function,
+			eem_ss_function, eem_ss_function);
+	if (status)
+		goto fail;
+
+	DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			eem->port.in_ep->name, eem->port.out_ep->name);
+	return 0;
+
+fail:
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct in_context *ctx = req->context;
+
+	dev_kfree_skb_any(ctx->skb);
+	kfree(req->buf);
+	usb_ep_free_request(ctx->ep, req);
+	kfree(ctx);
+}
+
+/*
+ * Add the EEM header and ethernet checksum.
+ * We currently do not attempt to put multiple ethernet frames
+ * into a single USB transfer
+ */
+static struct sk_buff *eem_wrap(struct gether *port, struct sk_buff *skb)
+{
+	struct sk_buff	*skb2 = NULL;
+	struct usb_ep	*in = port->in_ep;
+	int		headroom, tailroom, padlen = 0;
+	u16		len;
+
+	if (!skb)
+		return NULL;
+
+	len = skb->len;
+	headroom = skb_headroom(skb);
+	tailroom = skb_tailroom(skb);
+
+	/* When (len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) is 0,
+	 * stick two bytes of zero-length EEM packet on the end.
+	 */
+	if (((len + EEM_HLEN + ETH_FCS_LEN) % in->maxpacket) == 0)
+		padlen += 2;
+
+	if ((tailroom >= (ETH_FCS_LEN + padlen)) &&
+			(headroom >= EEM_HLEN) && !skb_cloned(skb))
+		goto done;
+
+	skb2 = skb_copy_expand(skb, EEM_HLEN, ETH_FCS_LEN + padlen, GFP_ATOMIC);
+	dev_kfree_skb_any(skb);
+	skb = skb2;
+	if (!skb)
+		return skb;
+
+done:
+	/* use the "no CRC" option */
+	put_unaligned_be32(0xdeadbeef, skb_put(skb, 4));
+
+	/* EEM packet header format:
+	 * b0..13:	length of ethernet frame
+	 * b14:		bmCRC (0 == sentinel CRC)
+	 * b15:		bmType (0 == data)
+	 */
+	len = skb->len;
+	put_unaligned_le16(len & 0x3FFF, skb_push(skb, 2));
+
+	/* add a zero-length EEM packet, if needed */
+	if (padlen)
+		put_unaligned_le16(0, skb_put(skb, 2));
+
+	return skb;
+}
+
+/*
+ * Remove the EEM header.  Note that there can be many EEM packets in a single
+ * USB transfer, so we need to break them out and handle them independently.
+ */
+static int eem_unwrap(struct gether *port,
+			struct sk_buff *skb,
+			struct sk_buff_head *list)
+{
+	struct usb_composite_dev	*cdev = port->func.config->cdev;
+	int				status = 0;
+
+	do {
+		struct sk_buff	*skb2;
+		u16		header;
+		u16		len = 0;
+
+		if (skb->len < EEM_HLEN) {
+			status = -EINVAL;
+			DBG(cdev, "invalid EEM header\n");
+			goto error;
+		}
+
+		/* remove the EEM header */
+		header = get_unaligned_le16(skb->data);
+		skb_pull(skb, EEM_HLEN);
+
+		/* EEM packet header format:
+		 * b0..14:	EEM type dependent (data or command)
+		 * b15:		bmType (0 == data, 1 == command)
+		 */
+		if (header & BIT(15)) {
+			struct usb_request	*req;
+			struct in_context	*ctx;
+			struct usb_ep		*ep;
+			u16			bmEEMCmd;
+
+			/* EEM command packet format:
+			 * b0..10:	bmEEMCmdParam
+			 * b11..13:	bmEEMCmd
+			 * b14:		reserved (must be zero)
+			 * b15:		bmType (1 == command)
+			 */
+			if (header & BIT(14))
+				continue;
+
+			bmEEMCmd = (header >> 11) & 0x7;
+			switch (bmEEMCmd) {
+			case 0: /* echo */
+				len = header & 0x7FF;
+				if (skb->len < len) {
+					status = -EOVERFLOW;
+					goto error;
+				}
+
+				skb2 = skb_clone(skb, GFP_ATOMIC);
+				if (unlikely(!skb2)) {
+					DBG(cdev, "EEM echo response error\n");
+					goto next;
+				}
+				skb_trim(skb2, len);
+				put_unaligned_le16(BIT(15) | BIT(11) | len,
+							skb_push(skb2, 2));
+
+				ep = port->in_ep;
+				req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+				if (!req) {
+					dev_kfree_skb_any(skb2);
+					goto next;
+				}
+
+				req->buf = kmalloc(skb2->len, GFP_KERNEL);
+				if (!req->buf) {
+					usb_ep_free_request(ep, req);
+					dev_kfree_skb_any(skb2);
+					goto next;
+				}
+
+				ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
+				if (!ctx) {
+					kfree(req->buf);
+					usb_ep_free_request(ep, req);
+					dev_kfree_skb_any(skb2);
+					goto next;
+				}
+				ctx->skb = skb2;
+				ctx->ep = ep;
+
+				skb_copy_bits(skb2, 0, req->buf, skb2->len);
+				req->length = skb2->len;
+				req->complete = eem_cmd_complete;
+				req->zero = 1;
+				req->context = ctx;
+				if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC))
+					DBG(cdev, "echo response queue fail\n");
+				break;
+
+			case 1:  /* echo response */
+			case 2:  /* suspend hint */
+			case 3:  /* response hint */
+			case 4:  /* response complete hint */
+			case 5:  /* tickle */
+			default: /* reserved */
+				continue;
+			}
+		} else {
+			u32		crc, crc2;
+			struct sk_buff	*skb3;
+
+			/* check for zero-length EEM packet */
+			if (header == 0)
+				continue;
+
+			/* EEM data packet format:
+			 * b0..13:	length of ethernet frame
+			 * b14:		bmCRC (0 == sentinel, 1 == calculated)
+			 * b15:		bmType (0 == data)
+			 */
+			len = header & 0x3FFF;
+			if ((skb->len < len)
+					|| (len < (ETH_HLEN + ETH_FCS_LEN))) {
+				status = -EINVAL;
+				goto error;
+			}
+
+			/* validate CRC */
+			if (header & BIT(14)) {
+				crc = get_unaligned_le32(skb->data + len
+							- ETH_FCS_LEN);
+				crc2 = ~crc32_le(~0,
+						skb->data, len - ETH_FCS_LEN);
+			} else {
+				crc = get_unaligned_be32(skb->data + len
+							- ETH_FCS_LEN);
+				crc2 = 0xdeadbeef;
+			}
+			if (crc != crc2) {
+				DBG(cdev, "invalid EEM CRC\n");
+				goto next;
+			}
+
+			skb2 = skb_clone(skb, GFP_ATOMIC);
+			if (unlikely(!skb2)) {
+				DBG(cdev, "unable to unframe EEM packet\n");
+				goto next;
+			}
+			skb_trim(skb2, len - ETH_FCS_LEN);
+
+			skb3 = skb_copy_expand(skb2,
+						NET_IP_ALIGN,
+						0,
+						GFP_ATOMIC);
+			if (unlikely(!skb3)) {
+				dev_kfree_skb_any(skb2);
+				goto next;
+			}
+			dev_kfree_skb_any(skb2);
+			skb_queue_tail(list, skb3);
+		}
+next:
+		skb_pull(skb, len);
+	} while (skb->len);
+
+error:
+	dev_kfree_skb_any(skb);
+	return status;
+}
+
+static inline struct f_eem_opts *to_f_eem_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_eem_opts,
+			    func_inst.group);
+}
+
+/* f_eem_item_ops */
+USB_ETHERNET_CONFIGFS_ITEM(eem);
+
+/* f_eem_opts_dev_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(eem);
+
+/* f_eem_opts_host_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(eem);
+
+/* f_eem_opts_qmult */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(eem);
+
+/* f_eem_opts_ifname */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(eem);
+
+static struct configfs_attribute *eem_attrs[] = {
+	&eem_opts_attr_dev_addr,
+	&eem_opts_attr_host_addr,
+	&eem_opts_attr_qmult,
+	&eem_opts_attr_ifname,
+	NULL,
+};
+
+static const struct config_item_type eem_func_type = {
+	.ct_item_ops	= &eem_item_ops,
+	.ct_attrs	= eem_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void eem_free_inst(struct usb_function_instance *f)
+{
+	struct f_eem_opts *opts;
+
+	opts = container_of(f, struct f_eem_opts, func_inst);
+	if (opts->bound)
+		gether_cleanup(netdev_priv(opts->net));
+	else
+		free_netdev(opts->net);
+	kfree(opts);
+}
+
+static struct usb_function_instance *eem_alloc_inst(void)
+{
+	struct f_eem_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = eem_free_inst;
+	opts->net = gether_setup_default();
+	if (IS_ERR(opts->net)) {
+		struct net_device *net = opts->net;
+		kfree(opts);
+		return ERR_CAST(net);
+	}
+
+	config_group_init_type_name(&opts->func_inst.group, "", &eem_func_type);
+
+	return &opts->func_inst;
+}
+
+static void eem_free(struct usb_function *f)
+{
+	struct f_eem *eem;
+	struct f_eem_opts *opts;
+
+	eem = func_to_eem(f);
+	opts = container_of(f->fi, struct f_eem_opts, func_inst);
+	kfree(eem);
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+}
+
+static void eem_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	DBG(c->cdev, "eem unbind\n");
+
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *eem_alloc(struct usb_function_instance *fi)
+{
+	struct f_eem	*eem;
+	struct f_eem_opts *opts;
+
+	/* allocate and initialize one new instance */
+	eem = kzalloc(sizeof(*eem), GFP_KERNEL);
+	if (!eem)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_eem_opts, func_inst);
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+
+	eem->port.ioport = netdev_priv(opts->net);
+	mutex_unlock(&opts->lock);
+	eem->port.cdc_filter = DEFAULT_FILTER;
+
+	eem->port.func.name = "cdc_eem";
+	/* descriptors are per-instance copies */
+	eem->port.func.bind = eem_bind;
+	eem->port.func.unbind = eem_unbind;
+	eem->port.func.set_alt = eem_set_alt;
+	eem->port.func.setup = eem_setup;
+	eem->port.func.disable = eem_disable;
+	eem->port.func.free_func = eem_free;
+	eem->port.wrap = eem_wrap;
+	eem->port.unwrap = eem_unwrap;
+	eem->port.header_len = EEM_HLEN;
+
+	return &eem->port.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(eem, eem_alloc_inst, eem_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Brownell");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_fs.c b/marvell/linux/drivers/usb/gadget/function/f_fs.c
new file mode 100644
index 0000000..9b5f9d5
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_fs.c
@@ -0,0 +1,3911 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_fs.c -- user mode file system API for USB composite function controllers
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ * Author: Michal Nazarewicz <mina86@mina86.com>
+ *
+ * Based on inode.c (GadgetFS) which was:
+ * Copyright (C) 2003-2004 David Brownell
+ * Copyright (C) 2003 Agilent Technologies
+ */
+
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/blkdev.h>
+#include <linux/pagemap.h>
+#include <linux/export.h>
+#include <linux/fs_parser.h>
+#include <linux/hid.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/sched/signal.h>
+#include <linux/uio.h>
+#include <linux/vmalloc.h>
+#include <asm/unaligned.h>
+
+#include <linux/usb/ccid.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/functionfs.h>
+
+#include <linux/aio.h>
+#include <linux/mmu_context.h>
+#include <linux/poll.h>
+#include <linux/eventfd.h>
+
+#include "u_fs.h"
+#include "u_f.h"
+#include "u_os_desc.h"
+#include "configfs.h"
+
+#define FUNCTIONFS_MAGIC	0xa647361 /* Chosen by a honest dice roll ;) */
+
+/* Reference counter handling */
+static void ffs_data_get(struct ffs_data *ffs);
+static void ffs_data_put(struct ffs_data *ffs);
+/* Creates new ffs_data object. */
+static struct ffs_data *__must_check ffs_data_new(const char *dev_name)
+	__attribute__((malloc));
+
+/* Opened counter handling. */
+static void ffs_data_opened(struct ffs_data *ffs);
+static void ffs_data_closed(struct ffs_data *ffs);
+
+/* Called with ffs->mutex held; take over ownership of data. */
+static int __must_check
+__ffs_data_got_descs(struct ffs_data *ffs, char *data, size_t len);
+static int __must_check
+__ffs_data_got_strings(struct ffs_data *ffs, char *data, size_t len);
+
+
+/* The function structure ***************************************************/
+
+struct ffs_ep;
+
+struct ffs_function {
+	struct usb_configuration	*conf;
+	struct usb_gadget		*gadget;
+	struct ffs_data			*ffs;
+
+	struct ffs_ep			*eps;
+	u8				eps_revmap[16];
+	short				*interfaces_nums;
+
+	struct usb_function		function;
+};
+
+
+static struct ffs_function *ffs_func_from_usb(struct usb_function *f)
+{
+	return container_of(f, struct ffs_function, function);
+}
+
+
+static inline enum ffs_setup_state
+ffs_setup_state_clear_cancelled(struct ffs_data *ffs)
+{
+	return (enum ffs_setup_state)
+		cmpxchg(&ffs->setup_state, FFS_SETUP_CANCELLED, FFS_NO_SETUP);
+}
+
+
+static void ffs_func_eps_disable(struct ffs_function *func);
+static int __must_check ffs_func_eps_enable(struct ffs_function *func);
+
+static int ffs_func_bind(struct usb_configuration *,
+			 struct usb_function *);
+static int ffs_func_set_alt(struct usb_function *, unsigned, unsigned);
+static void ffs_func_disable(struct usb_function *);
+static int ffs_func_setup(struct usb_function *,
+			  const struct usb_ctrlrequest *);
+static bool ffs_func_req_match(struct usb_function *,
+			       const struct usb_ctrlrequest *,
+			       bool config0);
+static void ffs_func_suspend(struct usb_function *);
+static void ffs_func_resume(struct usb_function *);
+
+
+static int ffs_func_revmap_ep(struct ffs_function *func, u8 num);
+static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf);
+
+
+/* The endpoints structures *************************************************/
+
+struct ffs_ep {
+	struct usb_ep			*ep;	/* P: ffs->eps_lock */
+	struct usb_request		*req;	/* P: epfile->mutex */
+
+	/* [0]: full speed, [1]: high speed, [2]: super speed */
+	struct usb_endpoint_descriptor	*descs[3];
+
+	u8				num;
+
+	int				status;	/* P: epfile->mutex */
+};
+
+struct ffs_epfile {
+	/* Protects ep->ep and ep->req. */
+	struct mutex			mutex;
+
+	struct ffs_data			*ffs;
+	struct ffs_ep			*ep;	/* P: ffs->eps_lock */
+
+	struct dentry			*dentry;
+
+	/*
+	 * Buffer for holding data from partial reads which may happen since
+	 * we’re rounding user read requests to a multiple of a max packet size.
+	 *
+	 * The pointer is initialised with NULL value and may be set by
+	 * __ffs_epfile_read_data function to point to a temporary buffer.
+	 *
+	 * In normal operation, calls to __ffs_epfile_read_buffered will consume
+	 * data from said buffer and eventually free it.  Importantly, while the
+	 * function is using the buffer, it sets the pointer to NULL.  This is
+	 * all right since __ffs_epfile_read_data and __ffs_epfile_read_buffered
+	 * can never run concurrently (they are synchronised by epfile->mutex)
+	 * so the latter will not assign a new value to the pointer.
+	 *
+	 * Meanwhile ffs_func_eps_disable frees the buffer (if the pointer is
+	 * valid) and sets the pointer to READ_BUFFER_DROP value.  This special
+	 * value is crux of the synchronisation between ffs_func_eps_disable and
+	 * __ffs_epfile_read_data.
+	 *
+	 * Once __ffs_epfile_read_data is about to finish it will try to set the
+	 * pointer back to its old value (as described above), but seeing as the
+	 * pointer is not-NULL (namely READ_BUFFER_DROP) it will instead free
+	 * the buffer.
+	 *
+	 * == State transitions ==
+	 *
+	 * • ptr == NULL:  (initial state)
+	 *   â—¦ __ffs_epfile_read_buffer_free: go to ptr == DROP
+	 *   â—¦ __ffs_epfile_read_buffered:    nop
+	 *   â—¦ __ffs_epfile_read_data allocates temp buffer: go to ptr == buf
+	 *   â—¦ reading finishes:              n/a, not in ‘and reading’ state
+	 * • ptr == DROP:
+	 *   â—¦ __ffs_epfile_read_buffer_free: nop
+	 *   â—¦ __ffs_epfile_read_buffered:    go to ptr == NULL
+	 *   â—¦ __ffs_epfile_read_data allocates temp buffer: free buf, nop
+	 *   â—¦ reading finishes:              n/a, not in ‘and reading’ state
+	 * • ptr == buf:
+	 *   â—¦ __ffs_epfile_read_buffer_free: free buf, go to ptr == DROP
+	 *   â—¦ __ffs_epfile_read_buffered:    go to ptr == NULL and reading
+	 *   â—¦ __ffs_epfile_read_data:        n/a, __ffs_epfile_read_buffered
+	 *                                    is always called first
+	 *   â—¦ reading finishes:              n/a, not in ‘and reading’ state
+	 * • ptr == NULL and reading:
+	 *   â—¦ __ffs_epfile_read_buffer_free: go to ptr == DROP and reading
+	 *   â—¦ __ffs_epfile_read_buffered:    n/a, mutex is held
+	 *   â—¦ __ffs_epfile_read_data:        n/a, mutex is held
+	 *   â—¦ reading finishes and …
+	 *     … all data read:               free buf, go to ptr == NULL
+	 *     … otherwise:                   go to ptr == buf and reading
+	 * • ptr == DROP and reading:
+	 *   â—¦ __ffs_epfile_read_buffer_free: nop
+	 *   â—¦ __ffs_epfile_read_buffered:    n/a, mutex is held
+	 *   â—¦ __ffs_epfile_read_data:        n/a, mutex is held
+	 *   â—¦ reading finishes:              free buf, go to ptr == DROP
+	 */
+	struct ffs_buffer		*read_buffer;
+#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
+
+	char				name[5];
+
+	unsigned char			in;	/* P: ffs->eps_lock */
+	unsigned char			isoc;	/* P: ffs->eps_lock */
+
+	unsigned char			_pad;
+};
+
+struct ffs_buffer {
+	size_t length;
+	char *data;
+	char storage[];
+};
+
+/*  ffs_io_data structure ***************************************************/
+
+struct ffs_io_data {
+	bool aio;
+	bool read;
+
+	struct kiocb *kiocb;
+	struct iov_iter data;
+	const void *to_free;
+	char *buf;
+
+	struct mm_struct *mm;
+	struct work_struct work;
+
+	struct usb_ep *ep;
+	struct usb_request *req;
+	struct sg_table sgt;
+	bool use_sg;
+
+	struct ffs_data *ffs;
+};
+
+struct ffs_desc_helper {
+	struct ffs_data *ffs;
+	unsigned interfaces_count;
+	unsigned eps_count;
+};
+
+static int  __must_check ffs_epfiles_create(struct ffs_data *ffs);
+static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count);
+
+static struct dentry *
+ffs_sb_create_file(struct super_block *sb, const char *name, void *data,
+		   const struct file_operations *fops);
+
+/* Devices management *******************************************************/
+
+DEFINE_MUTEX(ffs_lock);
+EXPORT_SYMBOL_GPL(ffs_lock);
+
+static struct ffs_dev *_ffs_find_dev(const char *name);
+static struct ffs_dev *_ffs_alloc_dev(void);
+static void _ffs_free_dev(struct ffs_dev *dev);
+static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data);
+static void ffs_release_dev(struct ffs_dev *ffs_dev);
+static int ffs_ready(struct ffs_data *ffs);
+static void ffs_closed(struct ffs_data *ffs);
+
+/* Misc helper functions ****************************************************/
+
+static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock)
+	__attribute__((warn_unused_result, nonnull));
+static char *ffs_prepare_buffer(const char __user *buf, size_t len)
+	__attribute__((warn_unused_result, nonnull));
+
+
+/* Control file aka ep0 *****************************************************/
+
+static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct ffs_data *ffs = req->context;
+
+	complete(&ffs->ep0req_completion);
+}
+
+static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
+	__releases(&ffs->ev.waitq.lock)
+{
+	struct usb_request *req = ffs->ep0req;
+	int ret;
+
+	if (!req) {
+		spin_unlock_irq(&ffs->ev.waitq.lock);
+		return -EINVAL;
+	}
+
+	req->zero     = len < le16_to_cpu(ffs->ev.setup.wLength);
+
+	spin_unlock_irq(&ffs->ev.waitq.lock);
+
+	req->buf      = data;
+	req->length   = len;
+
+	/*
+	 * UDC layer requires to provide a buffer even for ZLP, but should
+	 * not use it at all. Let's provide some poisoned pointer to catch
+	 * possible bug in the driver.
+	 */
+	if (req->buf == NULL)
+		req->buf = (void *)0xDEADBABE;
+
+	reinit_completion(&ffs->ep0req_completion);
+
+	ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
+	if (unlikely(ret < 0))
+		return ret;
+
+	ret = wait_for_completion_interruptible(&ffs->ep0req_completion);
+	if (unlikely(ret)) {
+		usb_ep_dequeue(ffs->gadget->ep0, req);
+		return -EINTR;
+	}
+
+	ffs->setup_state = FFS_NO_SETUP;
+	return req->status ? req->status : req->actual;
+}
+
+static int __ffs_ep0_stall(struct ffs_data *ffs)
+{
+	if (ffs->ev.can_stall) {
+		pr_vdebug("ep0 stall\n");
+		usb_ep_set_halt(ffs->gadget->ep0);
+		ffs->setup_state = FFS_NO_SETUP;
+		return -EL2HLT;
+	} else {
+		pr_debug("bogus ep0 stall!\n");
+		return -ESRCH;
+	}
+}
+
+static ssize_t ffs_ep0_write(struct file *file, const char __user *buf,
+			     size_t len, loff_t *ptr)
+{
+	struct ffs_data *ffs = file->private_data;
+	ssize_t ret;
+	char *data;
+
+	ENTER();
+
+	/* Fast check if setup was canceled */
+	if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
+		return -EIDRM;
+
+	/* Acquire mutex */
+	ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
+	if (unlikely(ret < 0))
+		return ret;
+
+	/* Check state */
+	switch (ffs->state) {
+	case FFS_READ_DESCRIPTORS:
+	case FFS_READ_STRINGS:
+		/* Copy data */
+		if (unlikely(len < 16)) {
+			ret = -EINVAL;
+			break;
+		}
+
+		data = ffs_prepare_buffer(buf, len);
+		if (IS_ERR(data)) {
+			ret = PTR_ERR(data);
+			break;
+		}
+
+		/* Handle data */
+		if (ffs->state == FFS_READ_DESCRIPTORS) {
+			pr_info("read descriptors\n");
+			ret = __ffs_data_got_descs(ffs, data, len);
+			if (unlikely(ret < 0))
+				break;
+
+			ffs->state = FFS_READ_STRINGS;
+			ret = len;
+		} else {
+			pr_info("read strings\n");
+			ret = __ffs_data_got_strings(ffs, data, len);
+			if (unlikely(ret < 0))
+				break;
+
+			ret = ffs_epfiles_create(ffs);
+			if (unlikely(ret)) {
+				ffs->state = FFS_CLOSING;
+				break;
+			}
+
+			ffs->state = FFS_ACTIVE;
+			mutex_unlock(&ffs->mutex);
+
+			ret = ffs_ready(ffs);
+			if (unlikely(ret < 0)) {
+				ffs->state = FFS_CLOSING;
+				return ret;
+			}
+
+			return len;
+		}
+		break;
+
+	case FFS_ACTIVE:
+		data = NULL;
+		/*
+		 * We're called from user space, we can use _irq
+		 * rather then _irqsave
+		 */
+		spin_lock_irq(&ffs->ev.waitq.lock);
+		switch (ffs_setup_state_clear_cancelled(ffs)) {
+		case FFS_SETUP_CANCELLED:
+			ret = -EIDRM;
+			goto done_spin;
+
+		case FFS_NO_SETUP:
+			ret = -ESRCH;
+			goto done_spin;
+
+		case FFS_SETUP_PENDING:
+			break;
+		}
+
+		/* FFS_SETUP_PENDING */
+		if (!(ffs->ev.setup.bRequestType & USB_DIR_IN)) {
+			spin_unlock_irq(&ffs->ev.waitq.lock);
+			ret = __ffs_ep0_stall(ffs);
+			break;
+		}
+
+		/* FFS_SETUP_PENDING and not stall */
+		len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength));
+
+		spin_unlock_irq(&ffs->ev.waitq.lock);
+
+		data = ffs_prepare_buffer(buf, len);
+		if (IS_ERR(data)) {
+			ret = PTR_ERR(data);
+			break;
+		}
+
+		spin_lock_irq(&ffs->ev.waitq.lock);
+
+		/*
+		 * We are guaranteed to be still in FFS_ACTIVE state
+		 * but the state of setup could have changed from
+		 * FFS_SETUP_PENDING to FFS_SETUP_CANCELLED so we need
+		 * to check for that.  If that happened we copied data
+		 * from user space in vain but it's unlikely.
+		 *
+		 * For sure we are not in FFS_NO_SETUP since this is
+		 * the only place FFS_SETUP_PENDING -> FFS_NO_SETUP
+		 * transition can be performed and it's protected by
+		 * mutex.
+		 */
+		if (ffs_setup_state_clear_cancelled(ffs) ==
+		    FFS_SETUP_CANCELLED) {
+			ret = -EIDRM;
+done_spin:
+			spin_unlock_irq(&ffs->ev.waitq.lock);
+		} else {
+			/* unlocks spinlock */
+			ret = __ffs_ep0_queue_wait(ffs, data, len);
+		}
+		kfree(data);
+		break;
+
+	default:
+		ret = -EBADFD;
+		break;
+	}
+
+	mutex_unlock(&ffs->mutex);
+	return ret;
+}
+
+/* Called with ffs->ev.waitq.lock and ffs->mutex held, both released on exit. */
+static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf,
+				     size_t n)
+	__releases(&ffs->ev.waitq.lock)
+{
+	/*
+	 * n cannot be bigger than ffs->ev.count, which cannot be bigger than
+	 * size of ffs->ev.types array (which is four) so that's how much space
+	 * we reserve.
+	 */
+	struct usb_functionfs_event events[ARRAY_SIZE(ffs->ev.types)];
+	const size_t size = n * sizeof *events;
+	unsigned i = 0;
+
+	memset(events, 0, size);
+
+	do {
+		events[i].type = ffs->ev.types[i];
+		if (events[i].type == FUNCTIONFS_SETUP) {
+			events[i].u.setup = ffs->ev.setup;
+			ffs->setup_state = FFS_SETUP_PENDING;
+		}
+	} while (++i < n);
+
+	ffs->ev.count -= n;
+	if (ffs->ev.count)
+		memmove(ffs->ev.types, ffs->ev.types + n,
+			ffs->ev.count * sizeof *ffs->ev.types);
+
+	spin_unlock_irq(&ffs->ev.waitq.lock);
+	mutex_unlock(&ffs->mutex);
+
+	return unlikely(copy_to_user(buf, events, size)) ? -EFAULT : size;
+}
+
+static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
+			    size_t len, loff_t *ptr)
+{
+	struct ffs_data *ffs = file->private_data;
+	char *data = NULL;
+	size_t n;
+	int ret;
+
+	ENTER();
+
+	/* Fast check if setup was canceled */
+	if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
+		return -EIDRM;
+
+	/* Acquire mutex */
+	ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
+	if (unlikely(ret < 0))
+		return ret;
+
+	/* Check state */
+	if (ffs->state != FFS_ACTIVE) {
+		ret = -EBADFD;
+		goto done_mutex;
+	}
+
+	/*
+	 * We're called from user space, we can use _irq rather then
+	 * _irqsave
+	 */
+	spin_lock_irq(&ffs->ev.waitq.lock);
+
+	switch (ffs_setup_state_clear_cancelled(ffs)) {
+	case FFS_SETUP_CANCELLED:
+		ret = -EIDRM;
+		break;
+
+	case FFS_NO_SETUP:
+		n = len / sizeof(struct usb_functionfs_event);
+		if (unlikely(!n)) {
+			ret = -EINVAL;
+			break;
+		}
+
+		if ((file->f_flags & O_NONBLOCK) && !ffs->ev.count) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq,
+							ffs->ev.count)) {
+			ret = -EINTR;
+			break;
+		}
+
+		/* unlocks spinlock */
+		return __ffs_ep0_read_events(ffs, buf,
+					     min(n, (size_t)ffs->ev.count));
+
+	case FFS_SETUP_PENDING:
+		if (ffs->ev.setup.bRequestType & USB_DIR_IN) {
+			spin_unlock_irq(&ffs->ev.waitq.lock);
+			ret = __ffs_ep0_stall(ffs);
+			goto done_mutex;
+		}
+
+		len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength));
+
+		spin_unlock_irq(&ffs->ev.waitq.lock);
+
+		if (likely(len)) {
+			data = kmalloc(len, GFP_KERNEL);
+			if (unlikely(!data)) {
+				ret = -ENOMEM;
+				goto done_mutex;
+			}
+		}
+
+		spin_lock_irq(&ffs->ev.waitq.lock);
+
+		/* See ffs_ep0_write() */
+		if (ffs_setup_state_clear_cancelled(ffs) ==
+		    FFS_SETUP_CANCELLED) {
+			ret = -EIDRM;
+			break;
+		}
+
+		/* unlocks spinlock */
+		ret = __ffs_ep0_queue_wait(ffs, data, len);
+		if (likely(ret > 0) && unlikely(copy_to_user(buf, data, len)))
+			ret = -EFAULT;
+		goto done_mutex;
+
+	default:
+		ret = -EBADFD;
+		break;
+	}
+
+	spin_unlock_irq(&ffs->ev.waitq.lock);
+done_mutex:
+	mutex_unlock(&ffs->mutex);
+	kfree(data);
+	return ret;
+}
+
+static int ffs_ep0_open(struct inode *inode, struct file *file)
+{
+	struct ffs_data *ffs = inode->i_private;
+
+	ENTER();
+
+	if (unlikely(ffs->state == FFS_CLOSING))
+		return -EBUSY;
+
+	file->private_data = ffs;
+	ffs_data_opened(ffs);
+
+	return stream_open(inode, file);
+}
+
+static int ffs_ep0_release(struct inode *inode, struct file *file)
+{
+	struct ffs_data *ffs = file->private_data;
+
+	ENTER();
+
+	ffs_data_closed(ffs);
+
+	return 0;
+}
+
+static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value)
+{
+	struct ffs_data *ffs = file->private_data;
+	struct usb_gadget *gadget = ffs->gadget;
+	long ret;
+
+	ENTER();
+
+	if (code == FUNCTIONFS_INTERFACE_REVMAP) {
+		struct ffs_function *func = ffs->func;
+		ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV;
+	} else if (gadget && gadget->ops->ioctl) {
+		ret = gadget->ops->ioctl(gadget, code, value);
+	} else {
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static __poll_t ffs_ep0_poll(struct file *file, poll_table *wait)
+{
+	struct ffs_data *ffs = file->private_data;
+	__poll_t mask = EPOLLWRNORM;
+	int ret;
+
+	poll_wait(file, &ffs->ev.waitq, wait);
+
+	ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
+	if (unlikely(ret < 0))
+		return mask;
+
+	switch (ffs->state) {
+	case FFS_READ_DESCRIPTORS:
+	case FFS_READ_STRINGS:
+		mask |= EPOLLOUT;
+		break;
+
+	case FFS_ACTIVE:
+		switch (ffs->setup_state) {
+		case FFS_NO_SETUP:
+			if (ffs->ev.count)
+				mask |= EPOLLIN;
+			break;
+
+		case FFS_SETUP_PENDING:
+		case FFS_SETUP_CANCELLED:
+			mask |= (EPOLLIN | EPOLLOUT);
+			break;
+		}
+	case FFS_CLOSING:
+		break;
+	case FFS_DEACTIVATED:
+		break;
+	}
+
+	mutex_unlock(&ffs->mutex);
+
+	return mask;
+}
+
+static const struct file_operations ffs_ep0_operations = {
+	.llseek =	no_llseek,
+
+	.open =		ffs_ep0_open,
+	.write =	ffs_ep0_write,
+	.read =		ffs_ep0_read,
+	.release =	ffs_ep0_release,
+	.unlocked_ioctl =	ffs_ep0_ioctl,
+	.poll =		ffs_ep0_poll,
+};
+
+
+/* "Normal" endpoints operations ********************************************/
+
+static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req)
+{
+	ENTER();
+	if (likely(req->context)) {
+		struct ffs_ep *ep = _ep->driver_data;
+		ep->status = req->status ? req->status : req->actual;
+		complete(req->context);
+	}
+}
+
+static ssize_t ffs_copy_to_iter(void *data, int data_len, struct iov_iter *iter)
+{
+	ssize_t ret = copy_to_iter(data, data_len, iter);
+	if (likely(ret == data_len))
+		return ret;
+
+	if (unlikely(iov_iter_count(iter)))
+		return -EFAULT;
+
+	/*
+	 * Dear user space developer!
+	 *
+	 * TL;DR: To stop getting below error message in your kernel log, change
+	 * user space code using functionfs to align read buffers to a max
+	 * packet size.
+	 *
+	 * Some UDCs (e.g. dwc3) require request sizes to be a multiple of a max
+	 * packet size.  When unaligned buffer is passed to functionfs, it
+	 * internally uses a larger, aligned buffer so that such UDCs are happy.
+	 *
+	 * Unfortunately, this means that host may send more data than was
+	 * requested in read(2) system call.  f_fs doesn’t know what to do with
+	 * that excess data so it simply drops it.
+	 *
+	 * Was the buffer aligned in the first place, no such problem would
+	 * happen.
+	 *
+	 * Data may be dropped only in AIO reads.  Synchronous reads are handled
+	 * by splitting a request into multiple parts.  This splitting may still
+	 * be a problem though so it’s likely best to align the buffer
+	 * regardless of it being AIO or not..
+	 *
+	 * This only affects OUT endpoints, i.e. reading data with a read(2),
+	 * aio_read(2) etc. system calls.  Writing data to an IN endpoint is not
+	 * affected.
+	 */
+	pr_err("functionfs read size %d > requested size %zd, dropping excess data. "
+	       "Align read buffer size to max packet size to avoid the problem.\n",
+	       data_len, ret);
+
+	return ret;
+}
+
+/*
+ * allocate a virtually contiguous buffer and create a scatterlist describing it
+ * @sg_table	- pointer to a place to be filled with sg_table contents
+ * @size	- required buffer size
+ */
+static void *ffs_build_sg_list(struct sg_table *sgt, size_t sz)
+{
+	struct page **pages;
+	void *vaddr, *ptr;
+	unsigned int n_pages;
+	int i;
+
+	vaddr = vmalloc(sz);
+	if (!vaddr)
+		return NULL;
+
+	n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
+	pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL);
+	if (!pages) {
+		vfree(vaddr);
+
+		return NULL;
+	}
+	for (i = 0, ptr = vaddr; i < n_pages; ++i, ptr += PAGE_SIZE)
+		pages[i] = vmalloc_to_page(ptr);
+
+	if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) {
+		kvfree(pages);
+		vfree(vaddr);
+
+		return NULL;
+	}
+	kvfree(pages);
+
+	return vaddr;
+}
+
+static inline void *ffs_alloc_buffer(struct ffs_io_data *io_data,
+	size_t data_len)
+{
+	if (io_data->use_sg)
+		return ffs_build_sg_list(&io_data->sgt, data_len);
+
+	return kmalloc(data_len, GFP_KERNEL);
+}
+
+static inline void ffs_free_buffer(struct ffs_io_data *io_data)
+{
+	if (!io_data->buf)
+		return;
+
+	if (io_data->use_sg) {
+		sg_free_table(&io_data->sgt);
+		vfree(io_data->buf);
+	} else {
+		kfree(io_data->buf);
+	}
+}
+
+static void ffs_user_copy_worker(struct work_struct *work)
+{
+	struct ffs_io_data *io_data = container_of(work, struct ffs_io_data,
+						   work);
+	int ret = io_data->req->status ? io_data->req->status :
+					 io_data->req->actual;
+	bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD;
+	unsigned long flags;
+
+	if (io_data->read && ret > 0) {
+		mm_segment_t oldfs = get_fs();
+
+		set_fs(USER_DS);
+		use_mm(io_data->mm);
+		ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
+		unuse_mm(io_data->mm);
+		set_fs(oldfs);
+	}
+
+	io_data->kiocb->ki_complete(io_data->kiocb, ret, ret);
+
+	if (io_data->ffs->ffs_eventfd && !kiocb_has_eventfd)
+		eventfd_signal(io_data->ffs->ffs_eventfd, 1);
+
+	spin_lock_irqsave(&io_data->ffs->eps_lock, flags);
+	usb_ep_free_request(io_data->ep, io_data->req);
+	io_data->req = NULL;
+	spin_unlock_irqrestore(&io_data->ffs->eps_lock, flags);
+
+	if (io_data->read)
+		kfree(io_data->to_free);
+	ffs_free_buffer(io_data);
+	kfree(io_data);
+}
+
+static void ffs_epfile_async_io_complete(struct usb_ep *_ep,
+					 struct usb_request *req)
+{
+	struct ffs_io_data *io_data = req->context;
+	struct ffs_data *ffs = io_data->ffs;
+
+	ENTER();
+
+	INIT_WORK(&io_data->work, ffs_user_copy_worker);
+	queue_work(ffs->io_completion_wq, &io_data->work);
+}
+
+static void __ffs_epfile_read_buffer_free(struct ffs_epfile *epfile)
+{
+	/*
+	 * See comment in struct ffs_epfile for full read_buffer pointer
+	 * synchronisation story.
+	 */
+	struct ffs_buffer *buf = xchg(&epfile->read_buffer, READ_BUFFER_DROP);
+	if (buf && buf != READ_BUFFER_DROP)
+		kfree(buf);
+}
+
+/* Assumes epfile->mutex is held. */
+static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile,
+					  struct iov_iter *iter)
+{
+	/*
+	 * Null out epfile->read_buffer so ffs_func_eps_disable does not free
+	 * the buffer while we are using it.  See comment in struct ffs_epfile
+	 * for full read_buffer pointer synchronisation story.
+	 */
+	struct ffs_buffer *buf = xchg(&epfile->read_buffer, NULL);
+	ssize_t ret;
+	if (!buf || buf == READ_BUFFER_DROP)
+		return 0;
+
+	ret = copy_to_iter(buf->data, buf->length, iter);
+	if (buf->length == ret) {
+		kfree(buf);
+		return ret;
+	}
+
+	if (unlikely(iov_iter_count(iter))) {
+		ret = -EFAULT;
+	} else {
+		buf->length -= ret;
+		buf->data += ret;
+	}
+
+	if (cmpxchg(&epfile->read_buffer, NULL, buf))
+		kfree(buf);
+
+	return ret;
+}
+
+/* Assumes epfile->mutex is held. */
+static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
+				      void *data, int data_len,
+				      struct iov_iter *iter)
+{
+	struct ffs_buffer *buf;
+
+	ssize_t ret = copy_to_iter(data, data_len, iter);
+	if (likely(data_len == ret))
+		return ret;
+
+	if (unlikely(iov_iter_count(iter)))
+		return -EFAULT;
+
+	/* See ffs_copy_to_iter for more context. */
+	pr_warn("functionfs read size %d > requested size %zd, splitting request into multiple reads.",
+		data_len, ret);
+
+	data_len -= ret;
+	buf = kmalloc(sizeof(*buf) + data_len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	buf->length = data_len;
+	buf->data = buf->storage;
+	memcpy(buf->storage, data + ret, data_len);
+
+	/*
+	 * At this point read_buffer is NULL or READ_BUFFER_DROP (if
+	 * ffs_func_eps_disable has been called in the meanwhile).  See comment
+	 * in struct ffs_epfile for full read_buffer pointer synchronisation
+	 * story.
+	 */
+	if (unlikely(cmpxchg(&epfile->read_buffer, NULL, buf)))
+		kfree(buf);
+
+	return ret;
+}
+
+static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
+{
+	struct ffs_epfile *epfile = file->private_data;
+	struct usb_request *req;
+	struct ffs_ep *ep;
+	char *data = NULL;
+	ssize_t ret, data_len = -EINVAL;
+	int halt;
+
+	/* Are we still active? */
+	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
+		return -ENODEV;
+
+	/* Wait for endpoint to be enabled */
+	ep = epfile->ep;
+	if (!ep) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(
+				epfile->ffs->wait, (ep = epfile->ep));
+		if (ret)
+			return -EINTR;
+	}
+
+	/* Do we halt? */
+	halt = (!io_data->read == !epfile->in);
+	if (halt && epfile->isoc)
+		return -EINVAL;
+
+	/* We will be using request and read_buffer */
+	ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK);
+	if (unlikely(ret))
+		goto error;
+
+	/* Allocate & copy */
+	if (!halt) {
+		struct usb_gadget *gadget;
+
+		/*
+		 * Do we have buffered data from previous partial read?  Check
+		 * that for synchronous case only because we do not have
+		 * facility to ‘wake up’ a pending asynchronous read and push
+		 * buffered data to it which we would need to make things behave
+		 * consistently.
+		 */
+		if (!io_data->aio && io_data->read) {
+			ret = __ffs_epfile_read_buffered(epfile, &io_data->data);
+			if (ret)
+				goto error_mutex;
+		}
+
+		/*
+		 * if we _do_ wait above, the epfile->ffs->gadget might be NULL
+		 * before the waiting completes, so do not assign to 'gadget'
+		 * earlier
+		 */
+		gadget = epfile->ffs->gadget;
+
+		spin_lock_irq(&epfile->ffs->eps_lock);
+		/* In the meantime, endpoint got disabled or changed. */
+		if (epfile->ep != ep) {
+			ret = -ESHUTDOWN;
+			goto error_lock;
+		}
+		data_len = iov_iter_count(&io_data->data);
+		/*
+		 * Controller may require buffer size to be aligned to
+		 * maxpacketsize of an out endpoint.
+		 */
+		if (io_data->read)
+			data_len = usb_ep_align_maybe(gadget, ep->ep, data_len);
+
+		io_data->use_sg = gadget->sg_supported && data_len > PAGE_SIZE;
+		spin_unlock_irq(&epfile->ffs->eps_lock);
+
+		data = ffs_alloc_buffer(io_data, data_len);
+		if (unlikely(!data)) {
+			ret = -ENOMEM;
+			goto error_mutex;
+		}
+		if (!io_data->read &&
+		    !copy_from_iter_full(data, data_len, &io_data->data)) {
+			ret = -EFAULT;
+			goto error_mutex;
+		}
+	}
+
+	spin_lock_irq(&epfile->ffs->eps_lock);
+
+	if (epfile->ep != ep) {
+		/* In the meantime, endpoint got disabled or changed. */
+		ret = -ESHUTDOWN;
+	} else if (halt) {
+		ret = usb_ep_set_halt(ep->ep);
+		if (!ret)
+			ret = -EBADMSG;
+	} else if (unlikely(data_len == -EINVAL)) {
+		/*
+		 * Sanity Check: even though data_len can't be used
+		 * uninitialized at the time I write this comment, some
+		 * compilers complain about this situation.
+		 * In order to keep the code clean from warnings, data_len is
+		 * being initialized to -EINVAL during its declaration, which
+		 * means we can't rely on compiler anymore to warn no future
+		 * changes won't result in data_len being used uninitialized.
+		 * For such reason, we're adding this redundant sanity check
+		 * here.
+		 */
+		WARN(1, "%s: data_len == -EINVAL\n", __func__);
+		ret = -EINVAL;
+	} else if (!io_data->aio) {
+		DECLARE_COMPLETION_ONSTACK(done);
+		bool interrupted = false;
+
+		req = ep->req;
+		if (io_data->use_sg) {
+			req->buf = NULL;
+			req->sg	= io_data->sgt.sgl;
+			req->num_sgs = io_data->sgt.nents;
+		} else {
+			req->buf = data;
+			req->num_sgs = 0;
+		}
+		req->length = data_len;
+
+		io_data->buf = data;
+
+		req->context  = &done;
+		req->complete = ffs_epfile_io_complete;
+
+		ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
+		if (unlikely(ret < 0))
+			goto error_lock;
+
+		spin_unlock_irq(&epfile->ffs->eps_lock);
+
+		if (unlikely(wait_for_completion_interruptible(&done))) {
+			/*
+			 * To avoid race condition with ffs_epfile_io_complete,
+			 * dequeue the request first then check
+			 * status. usb_ep_dequeue API should guarantee no race
+			 * condition with req->complete callback.
+			 */
+			usb_ep_dequeue(ep->ep, req);
+			wait_for_completion(&done);
+			interrupted = ep->status < 0;
+		}
+
+		if (interrupted)
+			ret = -EINTR;
+		else if (io_data->read && ep->status > 0)
+			ret = __ffs_epfile_read_data(epfile, data, ep->status,
+						     &io_data->data);
+		else
+			ret = ep->status;
+		goto error_mutex;
+	} else if (!(req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC))) {
+		ret = -ENOMEM;
+	} else {
+		if (io_data->use_sg) {
+			req->buf = NULL;
+			req->sg	= io_data->sgt.sgl;
+			req->num_sgs = io_data->sgt.nents;
+		} else {
+			req->buf = data;
+			req->num_sgs = 0;
+		}
+		req->length = data_len;
+
+		io_data->buf = data;
+		io_data->ep = ep->ep;
+		io_data->req = req;
+		io_data->ffs = epfile->ffs;
+
+		req->context  = io_data;
+		req->complete = ffs_epfile_async_io_complete;
+
+		ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
+		if (unlikely(ret)) {
+			io_data->req = NULL;
+			usb_ep_free_request(ep->ep, req);
+			goto error_lock;
+		}
+
+		ret = -EIOCBQUEUED;
+		/*
+		 * Do not kfree the buffer in this function.  It will be freed
+		 * by ffs_user_copy_worker.
+		 */
+		data = NULL;
+	}
+
+error_lock:
+	spin_unlock_irq(&epfile->ffs->eps_lock);
+error_mutex:
+	mutex_unlock(&epfile->mutex);
+error:
+	if (ret != -EIOCBQUEUED) /* don't free if there is iocb queued */
+		ffs_free_buffer(io_data);
+	return ret;
+}
+
+static int
+ffs_epfile_open(struct inode *inode, struct file *file)
+{
+	struct ffs_epfile *epfile = inode->i_private;
+
+	ENTER();
+
+	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
+		return -ENODEV;
+
+	file->private_data = epfile;
+	ffs_data_opened(epfile->ffs);
+
+	return stream_open(inode, file);
+}
+
+static int ffs_aio_cancel(struct kiocb *kiocb)
+{
+	struct ffs_io_data *io_data = kiocb->private;
+	struct ffs_epfile *epfile = kiocb->ki_filp->private_data;
+	unsigned long flags;
+	int value;
+
+	ENTER();
+
+	spin_lock_irqsave(&epfile->ffs->eps_lock, flags);
+
+	if (likely(io_data && io_data->ep && io_data->req))
+		value = usb_ep_dequeue(io_data->ep, io_data->req);
+	else
+		value = -EINVAL;
+
+	spin_unlock_irqrestore(&epfile->ffs->eps_lock, flags);
+
+	return value;
+}
+
+static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from)
+{
+	struct ffs_io_data io_data, *p = &io_data;
+	ssize_t res;
+
+	ENTER();
+
+	if (!is_sync_kiocb(kiocb)) {
+		p = kzalloc(sizeof(io_data), GFP_KERNEL);
+		if (unlikely(!p))
+			return -ENOMEM;
+		p->aio = true;
+	} else {
+		memset(p, 0, sizeof(*p));
+		p->aio = false;
+	}
+
+	p->read = false;
+	p->kiocb = kiocb;
+	p->data = *from;
+	p->mm = current->mm;
+
+	kiocb->private = p;
+
+	if (p->aio)
+		kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
+
+	res = ffs_epfile_io(kiocb->ki_filp, p);
+	if (res == -EIOCBQUEUED)
+		return res;
+	if (p->aio)
+		kfree(p);
+	else
+		*from = p->data;
+	return res;
+}
+
+static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
+{
+	struct ffs_io_data io_data, *p = &io_data;
+	ssize_t res;
+
+	ENTER();
+
+	if (!is_sync_kiocb(kiocb)) {
+		p = kzalloc(sizeof(io_data), GFP_KERNEL);
+		if (unlikely(!p))
+			return -ENOMEM;
+		p->aio = true;
+	} else {
+		memset(p, 0, sizeof(*p));
+		p->aio = false;
+	}
+
+	p->read = true;
+	p->kiocb = kiocb;
+	if (p->aio) {
+		p->to_free = dup_iter(&p->data, to, GFP_KERNEL);
+		if (!p->to_free) {
+			kfree(p);
+			return -ENOMEM;
+		}
+	} else {
+		p->data = *to;
+		p->to_free = NULL;
+	}
+	p->mm = current->mm;
+
+	kiocb->private = p;
+
+	if (p->aio)
+		kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);
+
+	res = ffs_epfile_io(kiocb->ki_filp, p);
+	if (res == -EIOCBQUEUED)
+		return res;
+
+	if (p->aio) {
+		kfree(p->to_free);
+		kfree(p);
+	} else {
+		*to = p->data;
+	}
+	return res;
+}
+
+static int
+ffs_epfile_release(struct inode *inode, struct file *file)
+{
+	struct ffs_epfile *epfile = inode->i_private;
+
+	ENTER();
+
+	__ffs_epfile_read_buffer_free(epfile);
+	ffs_data_closed(epfile->ffs);
+
+	return 0;
+}
+
+static long ffs_epfile_ioctl(struct file *file, unsigned code,
+			     unsigned long value)
+{
+	struct ffs_epfile *epfile = file->private_data;
+	struct ffs_ep *ep;
+	int ret;
+
+	ENTER();
+
+	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
+		return -ENODEV;
+
+	/* Wait for endpoint to be enabled */
+	ep = epfile->ep;
+	if (!ep) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(
+				epfile->ffs->wait, (ep = epfile->ep));
+		if (ret)
+			return -EINTR;
+	}
+
+	spin_lock_irq(&epfile->ffs->eps_lock);
+
+	/* In the meantime, endpoint got disabled or changed. */
+	if (epfile->ep != ep) {
+		spin_unlock_irq(&epfile->ffs->eps_lock);
+		return -ESHUTDOWN;
+	}
+
+	switch (code) {
+	case FUNCTIONFS_FIFO_STATUS:
+		ret = usb_ep_fifo_status(epfile->ep->ep);
+		break;
+	case FUNCTIONFS_FIFO_FLUSH:
+		usb_ep_fifo_flush(epfile->ep->ep);
+		ret = 0;
+		break;
+	case FUNCTIONFS_CLEAR_HALT:
+		ret = usb_ep_clear_halt(epfile->ep->ep);
+		break;
+	case FUNCTIONFS_ENDPOINT_REVMAP:
+		ret = epfile->ep->num;
+		break;
+	case FUNCTIONFS_ENDPOINT_DESC:
+	{
+		int desc_idx;
+		struct usb_endpoint_descriptor desc1, *desc;
+
+		switch (epfile->ffs->gadget->speed) {
+		case USB_SPEED_SUPER:
+		case USB_SPEED_SUPER_PLUS:
+			desc_idx = 2;
+			break;
+		case USB_SPEED_HIGH:
+			desc_idx = 1;
+			break;
+		default:
+			desc_idx = 0;
+		}
+
+		desc = epfile->ep->descs[desc_idx];
+		memcpy(&desc1, desc, desc->bLength);
+
+		spin_unlock_irq(&epfile->ffs->eps_lock);
+		ret = copy_to_user((void __user *)value, &desc1, desc1.bLength);
+		if (ret)
+			ret = -EFAULT;
+		return ret;
+	}
+	default:
+		ret = -ENOTTY;
+	}
+	spin_unlock_irq(&epfile->ffs->eps_lock);
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long ffs_epfile_compat_ioctl(struct file *file, unsigned code,
+		unsigned long value)
+{
+	return ffs_epfile_ioctl(file, code, value);
+}
+#endif
+
+static const struct file_operations ffs_epfile_operations = {
+	.llseek =	no_llseek,
+
+	.open =		ffs_epfile_open,
+	.write_iter =	ffs_epfile_write_iter,
+	.read_iter =	ffs_epfile_read_iter,
+	.release =	ffs_epfile_release,
+	.unlocked_ioctl =	ffs_epfile_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = ffs_epfile_compat_ioctl,
+#endif
+};
+
+
+/* File system and super block operations ***********************************/
+
+/*
+ * Mounting the file system creates a controller file, used first for
+ * function configuration then later for event monitoring.
+ */
+
+static struct inode *__must_check
+ffs_sb_make_inode(struct super_block *sb, void *data,
+		  const struct file_operations *fops,
+		  const struct inode_operations *iops,
+		  struct ffs_file_perms *perms)
+{
+	struct inode *inode;
+
+	ENTER();
+
+	inode = new_inode(sb);
+
+	if (likely(inode)) {
+		struct timespec64 ts = current_time(inode);
+
+		inode->i_ino	 = get_next_ino();
+		inode->i_mode    = perms->mode;
+		inode->i_uid     = perms->uid;
+		inode->i_gid     = perms->gid;
+		inode->i_atime   = ts;
+		inode->i_mtime   = ts;
+		inode->i_ctime   = ts;
+		inode->i_private = data;
+		if (fops)
+			inode->i_fop = fops;
+		if (iops)
+			inode->i_op  = iops;
+	}
+
+	return inode;
+}
+
+/* Create "regular" file */
+static struct dentry *ffs_sb_create_file(struct super_block *sb,
+					const char *name, void *data,
+					const struct file_operations *fops)
+{
+	struct ffs_data	*ffs = sb->s_fs_info;
+	struct dentry	*dentry;
+	struct inode	*inode;
+
+	ENTER();
+
+	dentry = d_alloc_name(sb->s_root, name);
+	if (unlikely(!dentry))
+		return NULL;
+
+	inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms);
+	if (unlikely(!inode)) {
+		dput(dentry);
+		return NULL;
+	}
+
+	d_add(dentry, inode);
+	return dentry;
+}
+
+/* Super block */
+static const struct super_operations ffs_sb_operations = {
+	.statfs =	simple_statfs,
+	.drop_inode =	generic_delete_inode,
+};
+
+struct ffs_sb_fill_data {
+	struct ffs_file_perms perms;
+	umode_t root_mode;
+	const char *dev_name;
+	bool no_disconnect;
+	struct ffs_data *ffs_data;
+};
+
+static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc)
+{
+	struct ffs_sb_fill_data *data = fc->fs_private;
+	struct inode	*inode;
+	struct ffs_data	*ffs = data->ffs_data;
+
+	ENTER();
+
+	ffs->sb              = sb;
+	data->ffs_data       = NULL;
+	sb->s_fs_info        = ffs;
+	sb->s_blocksize      = PAGE_SIZE;
+	sb->s_blocksize_bits = PAGE_SHIFT;
+	sb->s_magic          = FUNCTIONFS_MAGIC;
+	sb->s_op             = &ffs_sb_operations;
+	sb->s_time_gran      = 1;
+
+	/* Root inode */
+	data->perms.mode = data->root_mode;
+	inode = ffs_sb_make_inode(sb, NULL,
+				  &simple_dir_operations,
+				  &simple_dir_inode_operations,
+				  &data->perms);
+	sb->s_root = d_make_root(inode);
+	if (unlikely(!sb->s_root))
+		return -ENOMEM;
+
+	/* EP0 file */
+	if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs,
+					 &ffs_ep0_operations)))
+		return -ENOMEM;
+
+	return 0;
+}
+
+enum {
+	Opt_no_disconnect,
+	Opt_rmode,
+	Opt_fmode,
+	Opt_mode,
+	Opt_uid,
+	Opt_gid,
+};
+
+static const struct fs_parameter_spec ffs_fs_param_specs[] = {
+	fsparam_bool	("no_disconnect",	Opt_no_disconnect),
+	fsparam_u32	("rmode",		Opt_rmode),
+	fsparam_u32	("fmode",		Opt_fmode),
+	fsparam_u32	("mode",		Opt_mode),
+	fsparam_u32	("uid",			Opt_uid),
+	fsparam_u32	("gid",			Opt_gid),
+	{}
+};
+
+static const struct fs_parameter_description ffs_fs_fs_parameters = {
+	.name		= "kAFS",
+	.specs		= ffs_fs_param_specs,
+};
+
+static int ffs_fs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+	struct ffs_sb_fill_data *data = fc->fs_private;
+	struct fs_parse_result result;
+	int opt;
+
+	ENTER();
+
+	opt = fs_parse(fc, &ffs_fs_fs_parameters, param, &result);
+	if (opt < 0)
+		return opt;
+
+	switch (opt) {
+	case Opt_no_disconnect:
+		data->no_disconnect = result.boolean;
+		break;
+	case Opt_rmode:
+		data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;
+		break;
+	case Opt_fmode:
+		data->perms.mode = (result.uint_32 & 0666) | S_IFREG;
+		break;
+	case Opt_mode:
+		data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;
+		data->perms.mode = (result.uint_32 & 0666) | S_IFREG;
+		break;
+
+	case Opt_uid:
+		data->perms.uid = make_kuid(current_user_ns(), result.uint_32);
+		if (!uid_valid(data->perms.uid))
+			goto unmapped_value;
+		break;
+	case Opt_gid:
+		data->perms.gid = make_kgid(current_user_ns(), result.uint_32);
+		if (!gid_valid(data->perms.gid))
+			goto unmapped_value;
+		break;
+
+	default:
+		return -ENOPARAM;
+	}
+
+	return 0;
+
+unmapped_value:
+	return invalf(fc, "%s: unmapped value: %u", param->key, result.uint_32);
+}
+
+/*
+ * Set up the superblock for a mount.
+ */
+static int ffs_fs_get_tree(struct fs_context *fc)
+{
+	struct ffs_sb_fill_data *ctx = fc->fs_private;
+	struct ffs_data	*ffs;
+	int ret;
+
+	ENTER();
+
+	if (!fc->source)
+		return invalf(fc, "No source specified");
+
+	ffs = ffs_data_new(fc->source);
+	if (unlikely(!ffs))
+		return -ENOMEM;
+	ffs->file_perms = ctx->perms;
+	ffs->no_disconnect = ctx->no_disconnect;
+
+	ffs->dev_name = kstrdup(fc->source, GFP_KERNEL);
+	if (unlikely(!ffs->dev_name)) {
+		ffs_data_put(ffs);
+		return -ENOMEM;
+	}
+
+	ret = ffs_acquire_dev(ffs->dev_name, ffs);
+	if (ret) {
+		ffs_data_put(ffs);
+		return ret;
+	}
+
+	ctx->ffs_data = ffs;
+	return get_tree_nodev(fc, ffs_sb_fill);
+}
+
+static void ffs_fs_free_fc(struct fs_context *fc)
+{
+	struct ffs_sb_fill_data *ctx = fc->fs_private;
+
+	if (ctx) {
+		if (ctx->ffs_data) {
+			ffs_data_put(ctx->ffs_data);
+		}
+
+		kfree(ctx);
+	}
+}
+
+static const struct fs_context_operations ffs_fs_context_ops = {
+	.free		= ffs_fs_free_fc,
+	.parse_param	= ffs_fs_parse_param,
+	.get_tree	= ffs_fs_get_tree,
+};
+
+static int ffs_fs_init_fs_context(struct fs_context *fc)
+{
+	struct ffs_sb_fill_data *ctx;
+
+	ctx = kzalloc(sizeof(struct ffs_sb_fill_data), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->perms.mode = S_IFREG | 0600;
+	ctx->perms.uid = GLOBAL_ROOT_UID;
+	ctx->perms.gid = GLOBAL_ROOT_GID;
+	ctx->root_mode = S_IFDIR | 0500;
+	ctx->no_disconnect = false;
+
+	fc->fs_private = ctx;
+	fc->ops = &ffs_fs_context_ops;
+	return 0;
+}
+
+static void
+ffs_fs_kill_sb(struct super_block *sb)
+{
+	ENTER();
+
+	kill_litter_super(sb);
+	if (sb->s_fs_info)
+		ffs_data_closed(sb->s_fs_info);
+}
+
+static struct file_system_type ffs_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "functionfs",
+	.init_fs_context = ffs_fs_init_fs_context,
+	.parameters	= &ffs_fs_fs_parameters,
+	.kill_sb	= ffs_fs_kill_sb,
+};
+MODULE_ALIAS_FS("functionfs");
+
+
+/* Driver's main init/cleanup functions *************************************/
+
+static int functionfs_init(void)
+{
+	int ret;
+
+	ENTER();
+
+	ret = register_filesystem(&ffs_fs_type);
+	if (likely(!ret))
+		pr_info("file system registered\n");
+	else
+		pr_err("failed registering file system (%d)\n", ret);
+
+	return ret;
+}
+
+static void functionfs_cleanup(void)
+{
+	ENTER();
+
+	pr_info("unloading\n");
+	unregister_filesystem(&ffs_fs_type);
+}
+
+
+/* ffs_data and ffs_function construction and destruction code **************/
+
+static void ffs_data_clear(struct ffs_data *ffs);
+static void ffs_data_reset(struct ffs_data *ffs);
+
+static void ffs_data_get(struct ffs_data *ffs)
+{
+	ENTER();
+
+	refcount_inc(&ffs->ref);
+}
+
+static void ffs_data_opened(struct ffs_data *ffs)
+{
+	ENTER();
+
+	refcount_inc(&ffs->ref);
+	if (atomic_add_return(1, &ffs->opened) == 1 &&
+			ffs->state == FFS_DEACTIVATED) {
+		ffs->state = FFS_CLOSING;
+		ffs_data_reset(ffs);
+	}
+}
+
+static void ffs_data_put(struct ffs_data *ffs)
+{
+	ENTER();
+
+	if (unlikely(refcount_dec_and_test(&ffs->ref))) {
+		pr_info("%s(): freeing\n", __func__);
+		ffs_data_clear(ffs);
+		ffs_release_dev(ffs->private_data);
+		BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
+		       waitqueue_active(&ffs->ep0req_completion.wait) ||
+		       waitqueue_active(&ffs->wait));
+		destroy_workqueue(ffs->io_completion_wq);
+		kfree(ffs->dev_name);
+		kfree(ffs);
+	}
+}
+
+static void ffs_data_closed(struct ffs_data *ffs)
+{
+	struct ffs_epfile *epfiles;
+	unsigned long flags;
+
+	ENTER();
+
+	if (atomic_dec_and_test(&ffs->opened)) {
+		if (ffs->no_disconnect) {
+			ffs->state = FFS_DEACTIVATED;
+			spin_lock_irqsave(&ffs->eps_lock, flags);
+			epfiles = ffs->epfiles;
+			ffs->epfiles = NULL;
+			spin_unlock_irqrestore(&ffs->eps_lock,
+							flags);
+
+			if (epfiles)
+				ffs_epfiles_destroy(epfiles,
+						 ffs->eps_count);
+
+			if (ffs->setup_state == FFS_SETUP_PENDING)
+				__ffs_ep0_stall(ffs);
+		} else {
+			ffs->state = FFS_CLOSING;
+			ffs_data_reset(ffs);
+		}
+	}
+	if (atomic_read(&ffs->opened) < 0) {
+		ffs->state = FFS_CLOSING;
+		ffs_data_reset(ffs);
+	}
+
+	ffs_data_put(ffs);
+}
+
+static struct ffs_data *ffs_data_new(const char *dev_name)
+{
+	struct ffs_data *ffs = kzalloc(sizeof *ffs, GFP_KERNEL);
+	if (unlikely(!ffs))
+		return NULL;
+
+	ENTER();
+
+	ffs->io_completion_wq = alloc_ordered_workqueue("%s", 0, dev_name);
+	if (!ffs->io_completion_wq) {
+		kfree(ffs);
+		return NULL;
+	}
+
+	refcount_set(&ffs->ref, 1);
+	atomic_set(&ffs->opened, 0);
+	ffs->state = FFS_READ_DESCRIPTORS;
+	mutex_init(&ffs->mutex);
+	spin_lock_init(&ffs->eps_lock);
+	init_waitqueue_head(&ffs->ev.waitq);
+	init_waitqueue_head(&ffs->wait);
+	init_completion(&ffs->ep0req_completion);
+
+	/* XXX REVISIT need to update it in some places, or do we? */
+	ffs->ev.can_stall = 1;
+
+	return ffs;
+}
+
+static void ffs_data_clear(struct ffs_data *ffs)
+{
+	struct ffs_epfile *epfiles;
+	unsigned long flags;
+
+	ENTER();
+
+	ffs_closed(ffs);
+
+	BUG_ON(ffs->gadget);
+
+	spin_lock_irqsave(&ffs->eps_lock, flags);
+	epfiles = ffs->epfiles;
+	ffs->epfiles = NULL;
+	spin_unlock_irqrestore(&ffs->eps_lock, flags);
+
+	/*
+	 * potential race possible between ffs_func_eps_disable
+	 * & ffs_epfile_release therefore maintaining a local
+	 * copy of epfile will save us from use-after-free.
+	 */
+	if (epfiles) {
+		ffs_epfiles_destroy(epfiles, ffs->eps_count);
+		ffs->epfiles = NULL;
+	}
+
+	if (ffs->ffs_eventfd) {
+		eventfd_ctx_put(ffs->ffs_eventfd);
+		ffs->ffs_eventfd = NULL;
+	}
+
+	kfree(ffs->raw_descs_data);
+	kfree(ffs->raw_strings);
+	kfree(ffs->stringtabs);
+}
+
+static void ffs_data_reset(struct ffs_data *ffs)
+{
+	ENTER();
+
+	ffs_data_clear(ffs);
+
+	ffs->raw_descs_data = NULL;
+	ffs->raw_descs = NULL;
+	ffs->raw_strings = NULL;
+	ffs->stringtabs = NULL;
+
+	ffs->raw_descs_length = 0;
+	ffs->fs_descs_count = 0;
+	ffs->hs_descs_count = 0;
+	ffs->ss_descs_count = 0;
+
+	ffs->strings_count = 0;
+	ffs->interfaces_count = 0;
+	ffs->eps_count = 0;
+
+	ffs->ev.count = 0;
+
+	ffs->state = FFS_READ_DESCRIPTORS;
+	ffs->setup_state = FFS_NO_SETUP;
+	ffs->flags = 0;
+
+	ffs->ms_os_descs_ext_prop_count = 0;
+	ffs->ms_os_descs_ext_prop_name_len = 0;
+	ffs->ms_os_descs_ext_prop_data_len = 0;
+}
+
+
+static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev)
+{
+	struct usb_gadget_strings **lang;
+	int first_id;
+
+	ENTER();
+
+	if ((ffs->state != FFS_ACTIVE
+		 || test_and_set_bit(FFS_FL_BOUND, &ffs->flags)))
+		return -EBADFD;
+
+	first_id = usb_string_ids_n(cdev, ffs->strings_count);
+	if (unlikely(first_id < 0))
+		return first_id;
+
+	ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+	if (unlikely(!ffs->ep0req))
+		return -ENOMEM;
+	ffs->ep0req->complete = ffs_ep0_complete;
+	ffs->ep0req->context = ffs;
+
+	lang = ffs->stringtabs;
+	if (lang) {
+		for (; *lang; ++lang) {
+			struct usb_string *str = (*lang)->strings;
+			int id = first_id;
+			for (; str->s; ++id, ++str)
+				str->id = id;
+		}
+	}
+
+	ffs->gadget = cdev->gadget;
+	ffs_data_get(ffs);
+	return 0;
+}
+
+static void functionfs_unbind(struct ffs_data *ffs)
+{
+	ENTER();
+
+	if (!WARN_ON(!ffs->gadget)) {
+		/* dequeue before freeing ep0req */
+		usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req);
+		mutex_lock(&ffs->mutex);
+		usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
+		ffs->ep0req = NULL;
+		ffs->gadget = NULL;
+		clear_bit(FFS_FL_BOUND, &ffs->flags);
+		mutex_unlock(&ffs->mutex);
+		ffs_data_put(ffs);
+	}
+}
+
+static int ffs_epfiles_create(struct ffs_data *ffs)
+{
+	struct ffs_epfile *epfile, *epfiles;
+	unsigned i, count;
+
+	ENTER();
+
+	count = ffs->eps_count;
+	epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL);
+	if (!epfiles)
+		return -ENOMEM;
+
+	epfile = epfiles;
+	for (i = 1; i <= count; ++i, ++epfile) {
+		epfile->ffs = ffs;
+		mutex_init(&epfile->mutex);
+		if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+			sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
+		else
+			sprintf(epfile->name, "ep%u", i);
+		epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name,
+						 epfile,
+						 &ffs_epfile_operations);
+		if (unlikely(!epfile->dentry)) {
+			ffs_epfiles_destroy(epfiles, i - 1);
+			return -ENOMEM;
+		}
+	}
+
+	ffs->epfiles = epfiles;
+	return 0;
+}
+
+static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
+{
+	struct ffs_epfile *epfile = epfiles;
+
+	ENTER();
+
+	for (; count; --count, ++epfile) {
+		BUG_ON(mutex_is_locked(&epfile->mutex));
+		if (epfile->dentry) {
+			d_delete(epfile->dentry);
+			dput(epfile->dentry);
+			epfile->dentry = NULL;
+		}
+	}
+
+	kfree(epfiles);
+}
+
+static void ffs_func_eps_disable(struct ffs_function *func)
+{
+	struct ffs_ep *ep;
+	struct ffs_epfile *epfile;
+	unsigned short count;
+	unsigned long flags;
+
+	spin_lock_irqsave(&func->ffs->eps_lock, flags);
+	count = func->ffs->eps_count;
+	epfile = func->ffs->epfiles;
+	ep = func->eps;
+	while (count--) {
+		/* pending requests get nuked */
+		if (likely(ep->ep))
+			usb_ep_disable(ep->ep);
+		++ep;
+
+		if (epfile) {
+			epfile->ep = NULL;
+			__ffs_epfile_read_buffer_free(epfile);
+			++epfile;
+		}
+	}
+	spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
+}
+
+static int ffs_func_eps_enable(struct ffs_function *func)
+{
+	struct ffs_data *ffs;
+	struct ffs_ep *ep;
+	struct ffs_epfile *epfile;
+	unsigned short count;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&func->ffs->eps_lock, flags);
+	ffs = func->ffs;
+	ep = func->eps;
+	epfile = ffs->epfiles;
+	count = ffs->eps_count;
+	while(count--) {
+		ep->ep->driver_data = ep;
+
+		ret = config_ep_by_speed(func->gadget, &func->function, ep->ep);
+		if (ret) {
+			pr_err("%s: config_ep_by_speed(%s) returned %d\n",
+					__func__, ep->ep->name, ret);
+			break;
+		}
+
+		ret = usb_ep_enable(ep->ep);
+		if (likely(!ret)) {
+			epfile->ep = ep;
+			epfile->in = usb_endpoint_dir_in(ep->ep->desc);
+			epfile->isoc = usb_endpoint_xfer_isoc(ep->ep->desc);
+		} else {
+			break;
+		}
+
+		++ep;
+		++epfile;
+	}
+
+	wake_up_interruptible(&ffs->wait);
+	spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
+
+	return ret;
+}
+
+
+/* Parsing and building descriptors and strings *****************************/
+
+/*
+ * This validates if data pointed by data is a valid USB descriptor as
+ * well as record how many interfaces, endpoints and strings are
+ * required by given configuration.  Returns address after the
+ * descriptor or NULL if data is invalid.
+ */
+
+enum ffs_entity_type {
+	FFS_DESCRIPTOR, FFS_INTERFACE, FFS_STRING, FFS_ENDPOINT
+};
+
+enum ffs_os_desc_type {
+	FFS_OS_DESC, FFS_OS_DESC_EXT_COMPAT, FFS_OS_DESC_EXT_PROP
+};
+
+typedef int (*ffs_entity_callback)(enum ffs_entity_type entity,
+				   u8 *valuep,
+				   struct usb_descriptor_header *desc,
+				   void *priv);
+
+typedef int (*ffs_os_desc_callback)(enum ffs_os_desc_type entity,
+				    struct usb_os_desc_header *h, void *data,
+				    unsigned len, void *priv);
+
+static int __must_check ffs_do_single_desc(char *data, unsigned len,
+					   ffs_entity_callback entity,
+					   void *priv, int *current_class)
+{
+	struct usb_descriptor_header *_ds = (void *)data;
+	u8 length;
+	int ret;
+
+	ENTER();
+
+	/* At least two bytes are required: length and type */
+	if (len < 2) {
+		pr_vdebug("descriptor too short\n");
+		return -EINVAL;
+	}
+
+	/* If we have at least as many bytes as the descriptor takes? */
+	length = _ds->bLength;
+	if (len < length) {
+		pr_vdebug("descriptor longer then available data\n");
+		return -EINVAL;
+	}
+
+#define __entity_check_INTERFACE(val)  1
+#define __entity_check_STRING(val)     (val)
+#define __entity_check_ENDPOINT(val)   ((val) & USB_ENDPOINT_NUMBER_MASK)
+#define __entity(type, val) do {					\
+		pr_vdebug("entity " #type "(%02x)\n", (val));		\
+		if (unlikely(!__entity_check_ ##type(val))) {		\
+			pr_vdebug("invalid entity's value\n");		\
+			return -EINVAL;					\
+		}							\
+		ret = entity(FFS_ ##type, &val, _ds, priv);		\
+		if (unlikely(ret < 0)) {				\
+			pr_debug("entity " #type "(%02x); ret = %d\n",	\
+				 (val), ret);				\
+			return ret;					\
+		}							\
+	} while (0)
+
+	/* Parse descriptor depending on type. */
+	switch (_ds->bDescriptorType) {
+	case USB_DT_DEVICE:
+	case USB_DT_CONFIG:
+	case USB_DT_STRING:
+	case USB_DT_DEVICE_QUALIFIER:
+		/* function can't have any of those */
+		pr_vdebug("descriptor reserved for gadget: %d\n",
+		      _ds->bDescriptorType);
+		return -EINVAL;
+
+	case USB_DT_INTERFACE: {
+		struct usb_interface_descriptor *ds = (void *)_ds;
+		pr_vdebug("interface descriptor\n");
+		if (length != sizeof *ds)
+			goto inv_length;
+
+		__entity(INTERFACE, ds->bInterfaceNumber);
+		if (ds->iInterface)
+			__entity(STRING, ds->iInterface);
+		*current_class = ds->bInterfaceClass;
+	}
+		break;
+
+	case USB_DT_ENDPOINT: {
+		struct usb_endpoint_descriptor *ds = (void *)_ds;
+		pr_vdebug("endpoint descriptor\n");
+		if (length != USB_DT_ENDPOINT_SIZE &&
+		    length != USB_DT_ENDPOINT_AUDIO_SIZE)
+			goto inv_length;
+		__entity(ENDPOINT, ds->bEndpointAddress);
+	}
+		break;
+
+	case USB_TYPE_CLASS | 0x01:
+                if (*current_class == USB_INTERFACE_CLASS_HID) {
+			pr_vdebug("hid descriptor\n");
+			if (length != sizeof(struct hid_descriptor))
+				goto inv_length;
+			break;
+		} else if (*current_class == USB_INTERFACE_CLASS_CCID) {
+			pr_vdebug("ccid descriptor\n");
+			if (length != sizeof(struct ccid_descriptor))
+				goto inv_length;
+			break;
+		} else {
+			pr_vdebug("unknown descriptor: %d for class %d\n",
+			      _ds->bDescriptorType, *current_class);
+			return -EINVAL;
+		}
+
+	case USB_DT_OTG:
+		if (length != sizeof(struct usb_otg_descriptor))
+			goto inv_length;
+		break;
+
+	case USB_DT_INTERFACE_ASSOCIATION: {
+		struct usb_interface_assoc_descriptor *ds = (void *)_ds;
+		pr_vdebug("interface association descriptor\n");
+		if (length != sizeof *ds)
+			goto inv_length;
+		if (ds->iFunction)
+			__entity(STRING, ds->iFunction);
+	}
+		break;
+
+	case USB_DT_SS_ENDPOINT_COMP:
+		pr_vdebug("EP SS companion descriptor\n");
+		if (length != sizeof(struct usb_ss_ep_comp_descriptor))
+			goto inv_length;
+		break;
+
+	case USB_DT_OTHER_SPEED_CONFIG:
+	case USB_DT_INTERFACE_POWER:
+	case USB_DT_DEBUG:
+	case USB_DT_SECURITY:
+	case USB_DT_CS_RADIO_CONTROL:
+		/* TODO */
+		pr_vdebug("unimplemented descriptor: %d\n", _ds->bDescriptorType);
+		return -EINVAL;
+
+	default:
+		/* We should never be here */
+		pr_vdebug("unknown descriptor: %d\n", _ds->bDescriptorType);
+		return -EINVAL;
+
+inv_length:
+		pr_vdebug("invalid length: %d (descriptor %d)\n",
+			  _ds->bLength, _ds->bDescriptorType);
+		return -EINVAL;
+	}
+
+#undef __entity
+#undef __entity_check_DESCRIPTOR
+#undef __entity_check_INTERFACE
+#undef __entity_check_STRING
+#undef __entity_check_ENDPOINT
+
+	return length;
+}
+
+static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len,
+				     ffs_entity_callback entity, void *priv)
+{
+	const unsigned _len = len;
+	unsigned long num = 0;
+	int current_class = -1;
+
+	ENTER();
+
+	for (;;) {
+		int ret;
+
+		if (num == count)
+			data = NULL;
+
+		/* Record "descriptor" entity */
+		ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv);
+		if (unlikely(ret < 0)) {
+			pr_debug("entity DESCRIPTOR(%02lx); ret = %d\n",
+				 num, ret);
+			return ret;
+		}
+
+		if (!data)
+			return _len - len;
+
+		ret = ffs_do_single_desc(data, len, entity, priv,
+			&current_class);
+		if (unlikely(ret < 0)) {
+			pr_debug("%s returns %d\n", __func__, ret);
+			return ret;
+		}
+
+		len -= ret;
+		data += ret;
+		++num;
+	}
+}
+
+static int __ffs_data_do_entity(enum ffs_entity_type type,
+				u8 *valuep, struct usb_descriptor_header *desc,
+				void *priv)
+{
+	struct ffs_desc_helper *helper = priv;
+	struct usb_endpoint_descriptor *d;
+
+	ENTER();
+
+	switch (type) {
+	case FFS_DESCRIPTOR:
+		break;
+
+	case FFS_INTERFACE:
+		/*
+		 * Interfaces are indexed from zero so if we
+		 * encountered interface "n" then there are at least
+		 * "n+1" interfaces.
+		 */
+		if (*valuep >= helper->interfaces_count)
+			helper->interfaces_count = *valuep + 1;
+		break;
+
+	case FFS_STRING:
+		/*
+		 * Strings are indexed from 1 (0 is reserved
+		 * for languages list)
+		 */
+		if (*valuep > helper->ffs->strings_count)
+			helper->ffs->strings_count = *valuep;
+		break;
+
+	case FFS_ENDPOINT:
+		d = (void *)desc;
+		helper->eps_count++;
+		if (helper->eps_count >= FFS_MAX_EPS_COUNT)
+			return -EINVAL;
+		/* Check if descriptors for any speed were already parsed */
+		if (!helper->ffs->eps_count && !helper->ffs->interfaces_count)
+			helper->ffs->eps_addrmap[helper->eps_count] =
+				d->bEndpointAddress;
+		else if (helper->ffs->eps_addrmap[helper->eps_count] !=
+				d->bEndpointAddress)
+			return -EINVAL;
+		break;
+	}
+
+	return 0;
+}
+
+static int __ffs_do_os_desc_header(enum ffs_os_desc_type *next_type,
+				   struct usb_os_desc_header *desc)
+{
+	u16 bcd_version = le16_to_cpu(desc->bcdVersion);
+	u16 w_index = le16_to_cpu(desc->wIndex);
+
+	if (bcd_version != 1) {
+		pr_vdebug("unsupported os descriptors version: %d",
+			  bcd_version);
+		return -EINVAL;
+	}
+	switch (w_index) {
+	case 0x4:
+		*next_type = FFS_OS_DESC_EXT_COMPAT;
+		break;
+	case 0x5:
+		*next_type = FFS_OS_DESC_EXT_PROP;
+		break;
+	default:
+		pr_vdebug("unsupported os descriptor type: %d", w_index);
+		return -EINVAL;
+	}
+
+	return sizeof(*desc);
+}
+
+/*
+ * Process all extended compatibility/extended property descriptors
+ * of a feature descriptor
+ */
+static int __must_check ffs_do_single_os_desc(char *data, unsigned len,
+					      enum ffs_os_desc_type type,
+					      u16 feature_count,
+					      ffs_os_desc_callback entity,
+					      void *priv,
+					      struct usb_os_desc_header *h)
+{
+	int ret;
+	const unsigned _len = len;
+
+	ENTER();
+
+	/* loop over all ext compat/ext prop descriptors */
+	while (feature_count--) {
+		ret = entity(type, h, data, len, priv);
+		if (unlikely(ret < 0)) {
+			pr_debug("bad OS descriptor, type: %d\n", type);
+			return ret;
+		}
+		data += ret;
+		len -= ret;
+	}
+	return _len - len;
+}
+
+/* Process a number of complete Feature Descriptors (Ext Compat or Ext Prop) */
+static int __must_check ffs_do_os_descs(unsigned count,
+					char *data, unsigned len,
+					ffs_os_desc_callback entity, void *priv)
+{
+	const unsigned _len = len;
+	unsigned long num = 0;
+
+	ENTER();
+
+	for (num = 0; num < count; ++num) {
+		int ret;
+		enum ffs_os_desc_type type;
+		u16 feature_count;
+		struct usb_os_desc_header *desc = (void *)data;
+
+		if (len < sizeof(*desc))
+			return -EINVAL;
+
+		/*
+		 * Record "descriptor" entity.
+		 * Process dwLength, bcdVersion, wIndex, get b/wCount.
+		 * Move the data pointer to the beginning of extended
+		 * compatibilities proper or extended properties proper
+		 * portions of the data
+		 */
+		if (le32_to_cpu(desc->dwLength) > len)
+			return -EINVAL;
+
+		ret = __ffs_do_os_desc_header(&type, desc);
+		if (unlikely(ret < 0)) {
+			pr_debug("entity OS_DESCRIPTOR(%02lx); ret = %d\n",
+				 num, ret);
+			return ret;
+		}
+		/*
+		 * 16-bit hex "?? 00" Little Endian looks like 8-bit hex "??"
+		 */
+		feature_count = le16_to_cpu(desc->wCount);
+		if (type == FFS_OS_DESC_EXT_COMPAT &&
+		    (feature_count > 255 || desc->Reserved))
+				return -EINVAL;
+		len -= ret;
+		data += ret;
+
+		/*
+		 * Process all function/property descriptors
+		 * of this Feature Descriptor
+		 */
+		ret = ffs_do_single_os_desc(data, len, type,
+					    feature_count, entity, priv, desc);
+		if (unlikely(ret < 0)) {
+			pr_debug("%s returns %d\n", __func__, ret);
+			return ret;
+		}
+
+		len -= ret;
+		data += ret;
+	}
+	return _len - len;
+}
+
+/**
+ * Validate contents of the buffer from userspace related to OS descriptors.
+ */
+static int __ffs_data_do_os_desc(enum ffs_os_desc_type type,
+				 struct usb_os_desc_header *h, void *data,
+				 unsigned len, void *priv)
+{
+	struct ffs_data *ffs = priv;
+	u8 length;
+
+	ENTER();
+
+	switch (type) {
+	case FFS_OS_DESC_EXT_COMPAT: {
+		struct usb_ext_compat_desc *d = data;
+		int i;
+
+		if (len < sizeof(*d) ||
+		    d->bFirstInterfaceNumber >= ffs->interfaces_count)
+			return -EINVAL;
+		if (d->Reserved1 != 1) {
+			/*
+			 * According to the spec, Reserved1 must be set to 1
+			 * but older kernels incorrectly rejected non-zero
+			 * values.  We fix it here to avoid returning EINVAL
+			 * in response to values we used to accept.
+			 */
+			pr_debug("usb_ext_compat_desc::Reserved1 forced to 1\n");
+			d->Reserved1 = 1;
+		}
+		for (i = 0; i < ARRAY_SIZE(d->Reserved2); ++i)
+			if (d->Reserved2[i])
+				return -EINVAL;
+
+		length = sizeof(struct usb_ext_compat_desc);
+	}
+		break;
+	case FFS_OS_DESC_EXT_PROP: {
+		struct usb_ext_prop_desc *d = data;
+		u32 type, pdl;
+		u16 pnl;
+
+		if (len < sizeof(*d) || h->interface >= ffs->interfaces_count)
+			return -EINVAL;
+		length = le32_to_cpu(d->dwSize);
+		if (len < length)
+			return -EINVAL;
+		type = le32_to_cpu(d->dwPropertyDataType);
+		if (type < USB_EXT_PROP_UNICODE ||
+		    type > USB_EXT_PROP_UNICODE_MULTI) {
+			pr_vdebug("unsupported os descriptor property type: %d",
+				  type);
+			return -EINVAL;
+		}
+		pnl = le16_to_cpu(d->wPropertyNameLength);
+		if (length < 14 + pnl) {
+			pr_vdebug("invalid os descriptor length: %d pnl:%d (descriptor %d)\n",
+				  length, pnl, type);
+			return -EINVAL;
+		}
+		pdl = le32_to_cpu(*(__le32 *)((u8 *)data + 10 + pnl));
+		if (length != 14 + pnl + pdl) {
+			pr_vdebug("invalid os descriptor length: %d pnl:%d pdl:%d (descriptor %d)\n",
+				  length, pnl, pdl, type);
+			return -EINVAL;
+		}
+		++ffs->ms_os_descs_ext_prop_count;
+		/* property name reported to the host as "WCHAR"s */
+		ffs->ms_os_descs_ext_prop_name_len += pnl * 2;
+		ffs->ms_os_descs_ext_prop_data_len += pdl;
+	}
+		break;
+	default:
+		pr_vdebug("unknown descriptor: %d\n", type);
+		return -EINVAL;
+	}
+	return length;
+}
+
+static int __ffs_data_got_descs(struct ffs_data *ffs,
+				char *const _data, size_t len)
+{
+	char *data = _data, *raw_descs;
+	unsigned os_descs_count = 0, counts[3], flags;
+	int ret = -EINVAL, i;
+	struct ffs_desc_helper helper;
+
+	ENTER();
+
+	if (get_unaligned_le32(data + 4) != len)
+		goto error;
+
+	switch (get_unaligned_le32(data)) {
+	case FUNCTIONFS_DESCRIPTORS_MAGIC:
+		flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC;
+		data += 8;
+		len  -= 8;
+		break;
+	case FUNCTIONFS_DESCRIPTORS_MAGIC_V2:
+		flags = get_unaligned_le32(data + 8);
+		ffs->user_flags = flags;
+		if (flags & ~(FUNCTIONFS_HAS_FS_DESC |
+			      FUNCTIONFS_HAS_HS_DESC |
+			      FUNCTIONFS_HAS_SS_DESC |
+			      FUNCTIONFS_HAS_MS_OS_DESC |
+			      FUNCTIONFS_VIRTUAL_ADDR |
+			      FUNCTIONFS_EVENTFD |
+			      FUNCTIONFS_ALL_CTRL_RECIP |
+			      FUNCTIONFS_CONFIG0_SETUP)) {
+			ret = -ENOSYS;
+			goto error;
+		}
+		data += 12;
+		len  -= 12;
+		break;
+	default:
+		goto error;
+	}
+
+	if (flags & FUNCTIONFS_EVENTFD) {
+		if (len < 4)
+			goto error;
+		ffs->ffs_eventfd =
+			eventfd_ctx_fdget((int)get_unaligned_le32(data));
+		if (IS_ERR(ffs->ffs_eventfd)) {
+			ret = PTR_ERR(ffs->ffs_eventfd);
+			ffs->ffs_eventfd = NULL;
+			goto error;
+		}
+		data += 4;
+		len  -= 4;
+	}
+
+	/* Read fs_count, hs_count and ss_count (if present) */
+	for (i = 0; i < 3; ++i) {
+		if (!(flags & (1 << i))) {
+			counts[i] = 0;
+		} else if (len < 4) {
+			goto error;
+		} else {
+			counts[i] = get_unaligned_le32(data);
+			data += 4;
+			len  -= 4;
+		}
+	}
+	if (flags & (1 << i)) {
+		if (len < 4) {
+			goto error;
+		}
+		os_descs_count = get_unaligned_le32(data);
+		data += 4;
+		len -= 4;
+	};
+
+	/* Read descriptors */
+	raw_descs = data;
+	helper.ffs = ffs;
+	for (i = 0; i < 3; ++i) {
+		if (!counts[i])
+			continue;
+		helper.interfaces_count = 0;
+		helper.eps_count = 0;
+		ret = ffs_do_descs(counts[i], data, len,
+				   __ffs_data_do_entity, &helper);
+		if (ret < 0)
+			goto error;
+		if (!ffs->eps_count && !ffs->interfaces_count) {
+			ffs->eps_count = helper.eps_count;
+			ffs->interfaces_count = helper.interfaces_count;
+		} else {
+			if (ffs->eps_count != helper.eps_count) {
+				ret = -EINVAL;
+				goto error;
+			}
+			if (ffs->interfaces_count != helper.interfaces_count) {
+				ret = -EINVAL;
+				goto error;
+			}
+		}
+		data += ret;
+		len  -= ret;
+	}
+	if (os_descs_count) {
+		ret = ffs_do_os_descs(os_descs_count, data, len,
+				      __ffs_data_do_os_desc, ffs);
+		if (ret < 0)
+			goto error;
+		data += ret;
+		len -= ret;
+	}
+
+	if (raw_descs == data || len) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	ffs->raw_descs_data	= _data;
+	ffs->raw_descs		= raw_descs;
+	ffs->raw_descs_length	= data - raw_descs;
+	ffs->fs_descs_count	= counts[0];
+	ffs->hs_descs_count	= counts[1];
+	ffs->ss_descs_count	= counts[2];
+	ffs->ms_os_descs_count	= os_descs_count;
+
+	return 0;
+
+error:
+	kfree(_data);
+	return ret;
+}
+
+static int __ffs_data_got_strings(struct ffs_data *ffs,
+				  char *const _data, size_t len)
+{
+	u32 str_count, needed_count, lang_count;
+	struct usb_gadget_strings **stringtabs, *t;
+	const char *data = _data;
+	struct usb_string *s;
+
+	ENTER();
+
+	if (unlikely(len < 16 ||
+		     get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC ||
+		     get_unaligned_le32(data + 4) != len))
+		goto error;
+	str_count  = get_unaligned_le32(data + 8);
+	lang_count = get_unaligned_le32(data + 12);
+
+	/* if one is zero the other must be zero */
+	if (unlikely(!str_count != !lang_count))
+		goto error;
+
+	/* Do we have at least as many strings as descriptors need? */
+	needed_count = ffs->strings_count;
+	if (unlikely(str_count < needed_count))
+		goto error;
+
+	/*
+	 * If we don't need any strings just return and free all
+	 * memory.
+	 */
+	if (!needed_count) {
+		kfree(_data);
+		return 0;
+	}
+
+	/* Allocate everything in one chunk so there's less maintenance. */
+	{
+		unsigned i = 0;
+		vla_group(d);
+		vla_item(d, struct usb_gadget_strings *, stringtabs,
+			lang_count + 1);
+		vla_item(d, struct usb_gadget_strings, stringtab, lang_count);
+		vla_item(d, struct usb_string, strings,
+			lang_count*(needed_count+1));
+
+		char *vlabuf = kmalloc(vla_group_size(d), GFP_KERNEL);
+
+		if (unlikely(!vlabuf)) {
+			kfree(_data);
+			return -ENOMEM;
+		}
+
+		/* Initialize the VLA pointers */
+		stringtabs = vla_ptr(vlabuf, d, stringtabs);
+		t = vla_ptr(vlabuf, d, stringtab);
+		i = lang_count;
+		do {
+			*stringtabs++ = t++;
+		} while (--i);
+		*stringtabs = NULL;
+
+		/* stringtabs = vlabuf = d_stringtabs for later kfree */
+		stringtabs = vla_ptr(vlabuf, d, stringtabs);
+		t = vla_ptr(vlabuf, d, stringtab);
+		s = vla_ptr(vlabuf, d, strings);
+	}
+
+	/* For each language */
+	data += 16;
+	len -= 16;
+
+	do { /* lang_count > 0 so we can use do-while */
+		unsigned needed = needed_count;
+		u32 str_per_lang = str_count;
+
+		if (unlikely(len < 3))
+			goto error_free;
+		t->language = get_unaligned_le16(data);
+		t->strings  = s;
+		++t;
+
+		data += 2;
+		len -= 2;
+
+		/* For each string */
+		do { /* str_count > 0 so we can use do-while */
+			size_t length = strnlen(data, len);
+
+			if (unlikely(length == len))
+				goto error_free;
+
+			/*
+			 * User may provide more strings then we need,
+			 * if that's the case we simply ignore the
+			 * rest
+			 */
+			if (likely(needed)) {
+				/*
+				 * s->id will be set while adding
+				 * function to configuration so for
+				 * now just leave garbage here.
+				 */
+				s->s = data;
+				--needed;
+				++s;
+			}
+
+			data += length + 1;
+			len -= length + 1;
+		} while (--str_per_lang);
+
+		s->id = 0;   /* terminator */
+		s->s = NULL;
+		++s;
+
+	} while (--lang_count);
+
+	/* Some garbage left? */
+	if (unlikely(len))
+		goto error_free;
+
+	/* Done! */
+	ffs->stringtabs = stringtabs;
+	ffs->raw_strings = _data;
+
+	return 0;
+
+error_free:
+	kfree(stringtabs);
+error:
+	kfree(_data);
+	return -EINVAL;
+}
+
+
+/* Events handling and management *******************************************/
+
+static void __ffs_event_add(struct ffs_data *ffs,
+			    enum usb_functionfs_event_type type)
+{
+	enum usb_functionfs_event_type rem_type1, rem_type2 = type;
+	int neg = 0;
+
+	/*
+	 * Abort any unhandled setup
+	 *
+	 * We do not need to worry about some cmpxchg() changing value
+	 * of ffs->setup_state without holding the lock because when
+	 * state is FFS_SETUP_PENDING cmpxchg() in several places in
+	 * the source does nothing.
+	 */
+	if (ffs->setup_state == FFS_SETUP_PENDING)
+		ffs->setup_state = FFS_SETUP_CANCELLED;
+
+	/*
+	 * Logic of this function guarantees that there are at most four pending
+	 * evens on ffs->ev.types queue.  This is important because the queue
+	 * has space for four elements only and __ffs_ep0_read_events function
+	 * depends on that limit as well.  If more event types are added, those
+	 * limits have to be revisited or guaranteed to still hold.
+	 */
+	switch (type) {
+	case FUNCTIONFS_RESUME:
+		rem_type2 = FUNCTIONFS_SUSPEND;
+		/* FALL THROUGH */
+	case FUNCTIONFS_SUSPEND:
+	case FUNCTIONFS_SETUP:
+		rem_type1 = type;
+		/* Discard all similar events */
+		break;
+
+	case FUNCTIONFS_BIND:
+	case FUNCTIONFS_UNBIND:
+	case FUNCTIONFS_DISABLE:
+	case FUNCTIONFS_ENABLE:
+		/* Discard everything other then power management. */
+		rem_type1 = FUNCTIONFS_SUSPEND;
+		rem_type2 = FUNCTIONFS_RESUME;
+		neg = 1;
+		break;
+
+	default:
+		WARN(1, "%d: unknown event, this should not happen\n", type);
+		return;
+	}
+
+	{
+		u8 *ev  = ffs->ev.types, *out = ev;
+		unsigned n = ffs->ev.count;
+		for (; n; --n, ++ev)
+			if ((*ev == rem_type1 || *ev == rem_type2) == neg)
+				*out++ = *ev;
+			else
+				pr_vdebug("purging event %d\n", *ev);
+		ffs->ev.count = out - ffs->ev.types;
+	}
+
+	pr_vdebug("adding event %d\n", type);
+	ffs->ev.types[ffs->ev.count++] = type;
+	wake_up_locked(&ffs->ev.waitq);
+	if (ffs->ffs_eventfd)
+		eventfd_signal(ffs->ffs_eventfd, 1);
+}
+
+static void ffs_event_add(struct ffs_data *ffs,
+			  enum usb_functionfs_event_type type)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&ffs->ev.waitq.lock, flags);
+	__ffs_event_add(ffs, type);
+	spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags);
+}
+
+/* Bind/unbind USB function hooks *******************************************/
+
+static int ffs_ep_addr2idx(struct ffs_data *ffs, u8 endpoint_address)
+{
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(ffs->eps_addrmap); ++i)
+		if (ffs->eps_addrmap[i] == endpoint_address)
+			return i;
+	return -ENOENT;
+}
+
+static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
+				    struct usb_descriptor_header *desc,
+				    void *priv)
+{
+	struct usb_endpoint_descriptor *ds = (void *)desc;
+	struct ffs_function *func = priv;
+	struct ffs_ep *ffs_ep;
+	unsigned ep_desc_id;
+	int idx;
+	static const char *speed_names[] = { "full", "high", "super" };
+
+	if (type != FFS_DESCRIPTOR)
+		return 0;
+
+	/*
+	 * If ss_descriptors is not NULL, we are reading super speed
+	 * descriptors; if hs_descriptors is not NULL, we are reading high
+	 * speed descriptors; otherwise, we are reading full speed
+	 * descriptors.
+	 */
+	if (func->function.ss_descriptors) {
+		ep_desc_id = 2;
+		func->function.ss_descriptors[(long)valuep] = desc;
+	} else if (func->function.hs_descriptors) {
+		ep_desc_id = 1;
+		func->function.hs_descriptors[(long)valuep] = desc;
+	} else {
+		ep_desc_id = 0;
+		func->function.fs_descriptors[(long)valuep]    = desc;
+	}
+
+	if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT)
+		return 0;
+
+	idx = ffs_ep_addr2idx(func->ffs, ds->bEndpointAddress) - 1;
+	if (idx < 0)
+		return idx;
+
+	ffs_ep = func->eps + idx;
+
+	if (unlikely(ffs_ep->descs[ep_desc_id])) {
+		pr_err("two %sspeed descriptors for EP %d\n",
+			  speed_names[ep_desc_id],
+			  ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+		return -EINVAL;
+	}
+	ffs_ep->descs[ep_desc_id] = ds;
+
+	ffs_dump_mem(": Original  ep desc", ds, ds->bLength);
+	if (ffs_ep->ep) {
+		ds->bEndpointAddress = ffs_ep->descs[0]->bEndpointAddress;
+		if (!ds->wMaxPacketSize)
+			ds->wMaxPacketSize = ffs_ep->descs[0]->wMaxPacketSize;
+	} else {
+		struct usb_request *req;
+		struct usb_ep *ep;
+		u8 bEndpointAddress;
+		u16 wMaxPacketSize;
+
+		/*
+		 * We back up bEndpointAddress because autoconfig overwrites
+		 * it with physical endpoint address.
+		 */
+		bEndpointAddress = ds->bEndpointAddress;
+		/*
+		 * We back up wMaxPacketSize because autoconfig treats
+		 * endpoint descriptors as if they were full speed.
+		 */
+		wMaxPacketSize = ds->wMaxPacketSize;
+		pr_vdebug("autoconfig\n");
+		ep = usb_ep_autoconfig(func->gadget, ds);
+		if (unlikely(!ep))
+			return -ENOTSUPP;
+		ep->driver_data = func->eps + idx;
+
+		req = usb_ep_alloc_request(ep, GFP_KERNEL);
+		if (unlikely(!req))
+			return -ENOMEM;
+
+		ffs_ep->ep  = ep;
+		ffs_ep->req = req;
+		func->eps_revmap[ds->bEndpointAddress &
+				 USB_ENDPOINT_NUMBER_MASK] = idx + 1;
+		/*
+		 * If we use virtual address mapping, we restore
+		 * original bEndpointAddress value.
+		 */
+		if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+			ds->bEndpointAddress = bEndpointAddress;
+		/*
+		 * Restore wMaxPacketSize which was potentially
+		 * overwritten by autoconfig.
+		 */
+		ds->wMaxPacketSize = wMaxPacketSize;
+	}
+	ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength);
+
+	return 0;
+}
+
+static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep,
+				   struct usb_descriptor_header *desc,
+				   void *priv)
+{
+	struct ffs_function *func = priv;
+	unsigned idx;
+	u8 newValue;
+
+	switch (type) {
+	default:
+	case FFS_DESCRIPTOR:
+		/* Handled in previous pass by __ffs_func_bind_do_descs() */
+		return 0;
+
+	case FFS_INTERFACE:
+		idx = *valuep;
+		if (func->interfaces_nums[idx] < 0) {
+			int id = usb_interface_id(func->conf, &func->function);
+			if (unlikely(id < 0))
+				return id;
+			func->interfaces_nums[idx] = id;
+		}
+		newValue = func->interfaces_nums[idx];
+		break;
+
+	case FFS_STRING:
+		/* String' IDs are allocated when fsf_data is bound to cdev */
+		newValue = func->ffs->stringtabs[0]->strings[*valuep - 1].id;
+		break;
+
+	case FFS_ENDPOINT:
+		/*
+		 * USB_DT_ENDPOINT are handled in
+		 * __ffs_func_bind_do_descs().
+		 */
+		if (desc->bDescriptorType == USB_DT_ENDPOINT)
+			return 0;
+
+		idx = (*valuep & USB_ENDPOINT_NUMBER_MASK) - 1;
+		if (unlikely(!func->eps[idx].ep))
+			return -EINVAL;
+
+		{
+			struct usb_endpoint_descriptor **descs;
+			descs = func->eps[idx].descs;
+			newValue = descs[descs[0] ? 0 : 1]->bEndpointAddress;
+		}
+		break;
+	}
+
+	pr_vdebug("%02x -> %02x\n", *valuep, newValue);
+	*valuep = newValue;
+	return 0;
+}
+
+static int __ffs_func_bind_do_os_desc(enum ffs_os_desc_type type,
+				      struct usb_os_desc_header *h, void *data,
+				      unsigned len, void *priv)
+{
+	struct ffs_function *func = priv;
+	u8 length = 0;
+
+	switch (type) {
+	case FFS_OS_DESC_EXT_COMPAT: {
+		struct usb_ext_compat_desc *desc = data;
+		struct usb_os_desc_table *t;
+
+		t = &func->function.os_desc_table[desc->bFirstInterfaceNumber];
+		t->if_id = func->interfaces_nums[desc->bFirstInterfaceNumber];
+		memcpy(t->os_desc->ext_compat_id, &desc->CompatibleID,
+		       ARRAY_SIZE(desc->CompatibleID) +
+		       ARRAY_SIZE(desc->SubCompatibleID));
+		length = sizeof(*desc);
+	}
+		break;
+	case FFS_OS_DESC_EXT_PROP: {
+		struct usb_ext_prop_desc *desc = data;
+		struct usb_os_desc_table *t;
+		struct usb_os_desc_ext_prop *ext_prop;
+		char *ext_prop_name;
+		char *ext_prop_data;
+
+		t = &func->function.os_desc_table[h->interface];
+		t->if_id = func->interfaces_nums[h->interface];
+
+		ext_prop = func->ffs->ms_os_descs_ext_prop_avail;
+		func->ffs->ms_os_descs_ext_prop_avail += sizeof(*ext_prop);
+
+		ext_prop->type = le32_to_cpu(desc->dwPropertyDataType);
+		ext_prop->name_len = le16_to_cpu(desc->wPropertyNameLength);
+		ext_prop->data_len = le32_to_cpu(*(__le32 *)
+			usb_ext_prop_data_len_ptr(data, ext_prop->name_len));
+		length = ext_prop->name_len + ext_prop->data_len + 14;
+
+		ext_prop_name = func->ffs->ms_os_descs_ext_prop_name_avail;
+		func->ffs->ms_os_descs_ext_prop_name_avail +=
+			ext_prop->name_len;
+
+		ext_prop_data = func->ffs->ms_os_descs_ext_prop_data_avail;
+		func->ffs->ms_os_descs_ext_prop_data_avail +=
+			ext_prop->data_len;
+		memcpy(ext_prop_data,
+		       usb_ext_prop_data_ptr(data, ext_prop->name_len),
+		       ext_prop->data_len);
+		/* unicode data reported to the host as "WCHAR"s */
+		switch (ext_prop->type) {
+		case USB_EXT_PROP_UNICODE:
+		case USB_EXT_PROP_UNICODE_ENV:
+		case USB_EXT_PROP_UNICODE_LINK:
+		case USB_EXT_PROP_UNICODE_MULTI:
+			ext_prop->data_len *= 2;
+			break;
+		}
+		ext_prop->data = ext_prop_data;
+
+		memcpy(ext_prop_name, usb_ext_prop_name_ptr(data),
+		       ext_prop->name_len);
+		/* property name reported to the host as "WCHAR"s */
+		ext_prop->name_len *= 2;
+		ext_prop->name = ext_prop_name;
+
+		t->os_desc->ext_prop_len +=
+			ext_prop->name_len + ext_prop->data_len + 14;
+		++t->os_desc->ext_prop_count;
+		list_add_tail(&ext_prop->entry, &t->os_desc->ext_prop);
+	}
+		break;
+	default:
+		pr_vdebug("unknown descriptor: %d\n", type);
+	}
+
+	return length;
+}
+
+static inline struct f_fs_opts *ffs_do_functionfs_bind(struct usb_function *f,
+						struct usb_configuration *c)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+	struct f_fs_opts *ffs_opts =
+		container_of(f->fi, struct f_fs_opts, func_inst);
+	struct ffs_data *ffs_data;
+	int ret;
+
+	ENTER();
+
+	/*
+	 * Legacy gadget triggers binding in functionfs_ready_callback,
+	 * which already uses locking; taking the same lock here would
+	 * cause a deadlock.
+	 *
+	 * Configfs-enabled gadgets however do need ffs_dev_lock.
+	 */
+	if (!ffs_opts->no_configfs)
+		ffs_dev_lock();
+	ret = ffs_opts->dev->desc_ready ? 0 : -ENODEV;
+	ffs_data = ffs_opts->dev->ffs_data;
+	if (!ffs_opts->no_configfs)
+		ffs_dev_unlock();
+	if (ret)
+		return ERR_PTR(ret);
+
+	func->ffs = ffs_data;
+	func->conf = c;
+	func->gadget = c->cdev->gadget;
+
+	/*
+	 * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+	 * configurations are bound in sequence with list_for_each_entry,
+	 * in each configuration its functions are bound in sequence
+	 * with list_for_each_entry, so we assume no race condition
+	 * with regard to ffs_opts->bound access
+	 */
+	if (!ffs_opts->refcnt) {
+		ret = functionfs_bind(func->ffs, c->cdev);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+	ffs_opts->refcnt++;
+	func->function.strings = func->ffs->stringtabs;
+
+	return ffs_opts;
+}
+
+static int _ffs_func_bind(struct usb_configuration *c,
+			  struct usb_function *f)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+	struct ffs_data *ffs = func->ffs;
+
+	const int full = !!func->ffs->fs_descs_count;
+	const int high = !!func->ffs->hs_descs_count;
+	const int super = !!func->ffs->ss_descs_count;
+
+	int fs_len, hs_len, ss_len, ret, i;
+	struct ffs_ep *eps_ptr;
+
+	/* Make it a single chunk, less management later on */
+	vla_group(d);
+	vla_item_with_sz(d, struct ffs_ep, eps, ffs->eps_count);
+	vla_item_with_sz(d, struct usb_descriptor_header *, fs_descs,
+		full ? ffs->fs_descs_count + 1 : 0);
+	vla_item_with_sz(d, struct usb_descriptor_header *, hs_descs,
+		high ? ffs->hs_descs_count + 1 : 0);
+	vla_item_with_sz(d, struct usb_descriptor_header *, ss_descs,
+		super ? ffs->ss_descs_count + 1 : 0);
+	vla_item_with_sz(d, short, inums, ffs->interfaces_count);
+	vla_item_with_sz(d, struct usb_os_desc_table, os_desc_table,
+			 c->cdev->use_os_string ? ffs->interfaces_count : 0);
+	vla_item_with_sz(d, char[16], ext_compat,
+			 c->cdev->use_os_string ? ffs->interfaces_count : 0);
+	vla_item_with_sz(d, struct usb_os_desc, os_desc,
+			 c->cdev->use_os_string ? ffs->interfaces_count : 0);
+	vla_item_with_sz(d, struct usb_os_desc_ext_prop, ext_prop,
+			 ffs->ms_os_descs_ext_prop_count);
+	vla_item_with_sz(d, char, ext_prop_name,
+			 ffs->ms_os_descs_ext_prop_name_len);
+	vla_item_with_sz(d, char, ext_prop_data,
+			 ffs->ms_os_descs_ext_prop_data_len);
+	vla_item_with_sz(d, char, raw_descs, ffs->raw_descs_length);
+	char *vlabuf;
+
+	ENTER();
+
+	/* Has descriptors only for speeds gadget does not support */
+	if (unlikely(!(full | high | super)))
+		return -ENOTSUPP;
+
+	/* Allocate a single chunk, less management later on */
+	vlabuf = kzalloc(vla_group_size(d), GFP_KERNEL);
+	if (unlikely(!vlabuf))
+		return -ENOMEM;
+
+	ffs->ms_os_descs_ext_prop_avail = vla_ptr(vlabuf, d, ext_prop);
+	ffs->ms_os_descs_ext_prop_name_avail =
+		vla_ptr(vlabuf, d, ext_prop_name);
+	ffs->ms_os_descs_ext_prop_data_avail =
+		vla_ptr(vlabuf, d, ext_prop_data);
+
+	/* Copy descriptors  */
+	memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs,
+	       ffs->raw_descs_length);
+
+	memset(vla_ptr(vlabuf, d, inums), 0xff, d_inums__sz);
+	eps_ptr = vla_ptr(vlabuf, d, eps);
+	for (i = 0; i < ffs->eps_count; i++)
+		eps_ptr[i].num = -1;
+
+	/* Save pointers
+	 * d_eps == vlabuf, func->eps used to kfree vlabuf later
+	*/
+	func->eps             = vla_ptr(vlabuf, d, eps);
+	func->interfaces_nums = vla_ptr(vlabuf, d, inums);
+
+	/*
+	 * Go through all the endpoint descriptors and allocate
+	 * endpoints first, so that later we can rewrite the endpoint
+	 * numbers without worrying that it may be described later on.
+	 */
+	if (likely(full)) {
+		func->function.fs_descriptors = vla_ptr(vlabuf, d, fs_descs);
+		fs_len = ffs_do_descs(ffs->fs_descs_count,
+				      vla_ptr(vlabuf, d, raw_descs),
+				      d_raw_descs__sz,
+				      __ffs_func_bind_do_descs, func);
+		if (unlikely(fs_len < 0)) {
+			ret = fs_len;
+			goto error;
+		}
+	} else {
+		fs_len = 0;
+	}
+
+	if (likely(high)) {
+		func->function.hs_descriptors = vla_ptr(vlabuf, d, hs_descs);
+		hs_len = ffs_do_descs(ffs->hs_descs_count,
+				      vla_ptr(vlabuf, d, raw_descs) + fs_len,
+				      d_raw_descs__sz - fs_len,
+				      __ffs_func_bind_do_descs, func);
+		if (unlikely(hs_len < 0)) {
+			ret = hs_len;
+			goto error;
+		}
+	} else {
+		hs_len = 0;
+	}
+
+	if (likely(super)) {
+		func->function.ss_descriptors = func->function.ssp_descriptors =
+			vla_ptr(vlabuf, d, ss_descs);
+		ss_len = ffs_do_descs(ffs->ss_descs_count,
+				vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len,
+				d_raw_descs__sz - fs_len - hs_len,
+				__ffs_func_bind_do_descs, func);
+		if (unlikely(ss_len < 0)) {
+			ret = ss_len;
+			goto error;
+		}
+	} else {
+		ss_len = 0;
+	}
+
+	/*
+	 * Now handle interface numbers allocation and interface and
+	 * endpoint numbers rewriting.  We can do that in one go
+	 * now.
+	 */
+	ret = ffs_do_descs(ffs->fs_descs_count +
+			   (high ? ffs->hs_descs_count : 0) +
+			   (super ? ffs->ss_descs_count : 0),
+			   vla_ptr(vlabuf, d, raw_descs), d_raw_descs__sz,
+			   __ffs_func_bind_do_nums, func);
+	if (unlikely(ret < 0))
+		goto error;
+
+	func->function.os_desc_table = vla_ptr(vlabuf, d, os_desc_table);
+	if (c->cdev->use_os_string) {
+		for (i = 0; i < ffs->interfaces_count; ++i) {
+			struct usb_os_desc *desc;
+
+			desc = func->function.os_desc_table[i].os_desc =
+				vla_ptr(vlabuf, d, os_desc) +
+				i * sizeof(struct usb_os_desc);
+			desc->ext_compat_id =
+				vla_ptr(vlabuf, d, ext_compat) + i * 16;
+			INIT_LIST_HEAD(&desc->ext_prop);
+		}
+		ret = ffs_do_os_descs(ffs->ms_os_descs_count,
+				      vla_ptr(vlabuf, d, raw_descs) +
+				      fs_len + hs_len + ss_len,
+				      d_raw_descs__sz - fs_len - hs_len -
+				      ss_len,
+				      __ffs_func_bind_do_os_desc, func);
+		if (unlikely(ret < 0))
+			goto error;
+	}
+	func->function.os_desc_n =
+		c->cdev->use_os_string ? ffs->interfaces_count : 0;
+
+	/* And we're done */
+	ffs_event_add(ffs, FUNCTIONFS_BIND);
+	return 0;
+
+error:
+	/* XXX Do we need to release all claimed endpoints here? */
+	return ret;
+}
+
+static int ffs_func_bind(struct usb_configuration *c,
+			 struct usb_function *f)
+{
+	struct f_fs_opts *ffs_opts = ffs_do_functionfs_bind(f, c);
+	struct ffs_function *func = ffs_func_from_usb(f);
+	int ret;
+
+	if (IS_ERR(ffs_opts))
+		return PTR_ERR(ffs_opts);
+
+	ret = _ffs_func_bind(c, f);
+	if (ret && !--ffs_opts->refcnt)
+		functionfs_unbind(func->ffs);
+
+	return ret;
+}
+
+
+/* Other USB function hooks *************************************************/
+
+static void ffs_reset_work(struct work_struct *work)
+{
+	struct ffs_data *ffs = container_of(work,
+		struct ffs_data, reset_work);
+	ffs_data_reset(ffs);
+}
+
+static int ffs_func_set_alt(struct usb_function *f,
+			    unsigned interface, unsigned alt)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+	struct ffs_data *ffs = func->ffs;
+	int ret = 0, intf;
+
+	if (alt != (unsigned)-1) {
+		intf = ffs_func_revmap_intf(func, interface);
+		if (unlikely(intf < 0))
+			return intf;
+	}
+
+	if (ffs->func)
+		ffs_func_eps_disable(ffs->func);
+
+	if (ffs->state == FFS_DEACTIVATED) {
+		ffs->state = FFS_CLOSING;
+		INIT_WORK(&ffs->reset_work, ffs_reset_work);
+		schedule_work(&ffs->reset_work);
+		return -ENODEV;
+	}
+
+	if (ffs->state != FFS_ACTIVE)
+		return -ENODEV;
+
+	if (alt == (unsigned)-1) {
+		ffs->func = NULL;
+		ffs_event_add(ffs, FUNCTIONFS_DISABLE);
+		return 0;
+	}
+
+	ffs->func = func;
+	ret = ffs_func_eps_enable(func);
+	if (likely(ret >= 0))
+		ffs_event_add(ffs, FUNCTIONFS_ENABLE);
+	return ret;
+}
+
+static void ffs_func_disable(struct usb_function *f)
+{
+	ffs_func_set_alt(f, 0, (unsigned)-1);
+}
+
+static int ffs_func_setup(struct usb_function *f,
+			  const struct usb_ctrlrequest *creq)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+	struct ffs_data *ffs = func->ffs;
+	unsigned long flags;
+	int ret;
+
+	ENTER();
+
+	pr_vdebug("creq->bRequestType = %02x\n", creq->bRequestType);
+	pr_vdebug("creq->bRequest     = %02x\n", creq->bRequest);
+	pr_vdebug("creq->wValue       = %04x\n", le16_to_cpu(creq->wValue));
+	pr_vdebug("creq->wIndex       = %04x\n", le16_to_cpu(creq->wIndex));
+	pr_vdebug("creq->wLength      = %04x\n", le16_to_cpu(creq->wLength));
+
+	/*
+	 * Most requests directed to interface go through here
+	 * (notable exceptions are set/get interface) so we need to
+	 * handle them.  All other either handled by composite or
+	 * passed to usb_configuration->setup() (if one is set).  No
+	 * matter, we will handle requests directed to endpoint here
+	 * as well (as it's straightforward).  Other request recipient
+	 * types are only handled when the user flag FUNCTIONFS_ALL_CTRL_RECIP
+	 * is being used.
+	 */
+	if (ffs->state != FFS_ACTIVE)
+		return -ENODEV;
+
+	switch (creq->bRequestType & USB_RECIP_MASK) {
+	case USB_RECIP_INTERFACE:
+		ret = ffs_func_revmap_intf(func, le16_to_cpu(creq->wIndex));
+		if (unlikely(ret < 0))
+			return ret;
+		break;
+
+	case USB_RECIP_ENDPOINT:
+		ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex));
+		if (unlikely(ret < 0))
+			return ret;
+		if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+			ret = func->ffs->eps_addrmap[ret];
+		break;
+
+	default:
+		if (func->ffs->user_flags & FUNCTIONFS_ALL_CTRL_RECIP)
+			ret = le16_to_cpu(creq->wIndex);
+		else
+			return -EOPNOTSUPP;
+	}
+
+	spin_lock_irqsave(&ffs->ev.waitq.lock, flags);
+	ffs->ev.setup = *creq;
+	ffs->ev.setup.wIndex = cpu_to_le16(ret);
+	__ffs_event_add(ffs, FUNCTIONFS_SETUP);
+	spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags);
+
+	return ffs->ev.setup.wLength == 0 ? USB_GADGET_DELAYED_STATUS : 0;
+}
+
+static bool ffs_func_req_match(struct usb_function *f,
+			       const struct usb_ctrlrequest *creq,
+			       bool config0)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+
+	if (config0 && !(func->ffs->user_flags & FUNCTIONFS_CONFIG0_SETUP))
+		return false;
+
+	switch (creq->bRequestType & USB_RECIP_MASK) {
+	case USB_RECIP_INTERFACE:
+		return (ffs_func_revmap_intf(func,
+					     le16_to_cpu(creq->wIndex)) >= 0);
+	case USB_RECIP_ENDPOINT:
+		return (ffs_func_revmap_ep(func,
+					   le16_to_cpu(creq->wIndex)) >= 0);
+	default:
+		return (bool) (func->ffs->user_flags &
+			       FUNCTIONFS_ALL_CTRL_RECIP);
+	}
+}
+
+static void ffs_func_suspend(struct usb_function *f)
+{
+	ENTER();
+	ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_SUSPEND);
+}
+
+static void ffs_func_resume(struct usb_function *f)
+{
+	ENTER();
+	ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_RESUME);
+}
+
+
+/* Endpoint and interface numbers reverse mapping ***************************/
+
+static int ffs_func_revmap_ep(struct ffs_function *func, u8 num)
+{
+	num = func->eps_revmap[num & USB_ENDPOINT_NUMBER_MASK];
+	return num ? num : -EDOM;
+}
+
+static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf)
+{
+	short *nums = func->interfaces_nums;
+	unsigned count = func->ffs->interfaces_count;
+
+	for (; count; --count, ++nums) {
+		if (*nums >= 0 && *nums == intf)
+			return nums - func->interfaces_nums;
+	}
+
+	return -EDOM;
+}
+
+
+/* Devices management *******************************************************/
+
+static LIST_HEAD(ffs_devices);
+
+static struct ffs_dev *_ffs_do_find_dev(const char *name)
+{
+	struct ffs_dev *dev;
+
+	if (!name)
+		return NULL;
+
+	list_for_each_entry(dev, &ffs_devices, entry) {
+		if (strcmp(dev->name, name) == 0)
+			return dev;
+	}
+
+	return NULL;
+}
+
+/*
+ * ffs_lock must be taken by the caller of this function
+ */
+static struct ffs_dev *_ffs_get_single_dev(void)
+{
+	struct ffs_dev *dev;
+
+	if (list_is_singular(&ffs_devices)) {
+		dev = list_first_entry(&ffs_devices, struct ffs_dev, entry);
+		if (dev->single)
+			return dev;
+	}
+
+	return NULL;
+}
+
+/*
+ * ffs_lock must be taken by the caller of this function
+ */
+static struct ffs_dev *_ffs_find_dev(const char *name)
+{
+	struct ffs_dev *dev;
+
+	dev = _ffs_get_single_dev();
+	if (dev)
+		return dev;
+
+	return _ffs_do_find_dev(name);
+}
+
+/* Configfs support *********************************************************/
+
+static inline struct f_fs_opts *to_ffs_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_fs_opts,
+			    func_inst.group);
+}
+
+static void ffs_attr_release(struct config_item *item)
+{
+	struct f_fs_opts *opts = to_ffs_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations ffs_item_ops = {
+	.release	= ffs_attr_release,
+};
+
+static const struct config_item_type ffs_func_type = {
+	.ct_item_ops	= &ffs_item_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+
+/* Function registration interface ******************************************/
+
+static void ffs_free_inst(struct usb_function_instance *f)
+{
+	struct f_fs_opts *opts;
+
+	opts = to_f_fs_opts(f);
+	ffs_release_dev(opts->dev);
+	ffs_dev_lock();
+	_ffs_free_dev(opts->dev);
+	ffs_dev_unlock();
+	kfree(opts);
+}
+
+static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name)
+{
+	if (strlen(name) >= FIELD_SIZEOF(struct ffs_dev, name))
+		return -ENAMETOOLONG;
+	return ffs_name_dev(to_f_fs_opts(fi)->dev, name);
+}
+
+static struct usb_function_instance *ffs_alloc_inst(void)
+{
+	struct f_fs_opts *opts;
+	struct ffs_dev *dev;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	opts->func_inst.set_inst_name = ffs_set_inst_name;
+	opts->func_inst.free_func_inst = ffs_free_inst;
+	ffs_dev_lock();
+	dev = _ffs_alloc_dev();
+	ffs_dev_unlock();
+	if (IS_ERR(dev)) {
+		kfree(opts);
+		return ERR_CAST(dev);
+	}
+	opts->dev = dev;
+	dev->opts = opts;
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &ffs_func_type);
+	return &opts->func_inst;
+}
+
+static void ffs_free(struct usb_function *f)
+{
+	kfree(ffs_func_from_usb(f));
+}
+
+static void ffs_func_unbind(struct usb_configuration *c,
+			    struct usb_function *f)
+{
+	struct ffs_function *func = ffs_func_from_usb(f);
+	struct ffs_data *ffs = func->ffs;
+	struct f_fs_opts *opts =
+		container_of(f->fi, struct f_fs_opts, func_inst);
+	struct ffs_ep *ep = func->eps;
+	unsigned count = ffs->eps_count;
+	unsigned long flags;
+
+	ENTER();
+	if (ffs->func == func) {
+		ffs_func_eps_disable(func);
+		ffs->func = NULL;
+	}
+
+	/* Drain any pending AIO completions */
+	drain_workqueue(ffs->io_completion_wq);
+
+	ffs_event_add(ffs, FUNCTIONFS_UNBIND);
+	if (!--opts->refcnt)
+		functionfs_unbind(ffs);
+
+	/* cleanup after autoconfig */
+	spin_lock_irqsave(&func->ffs->eps_lock, flags);
+	while (count--) {
+		if (ep->ep && ep->req)
+			usb_ep_free_request(ep->ep, ep->req);
+		ep->req = NULL;
+		++ep;
+	}
+	spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
+	kfree(func->eps);
+	func->eps = NULL;
+	/*
+	 * eps, descriptors and interfaces_nums are allocated in the
+	 * same chunk so only one free is required.
+	 */
+	func->function.fs_descriptors = NULL;
+	func->function.hs_descriptors = NULL;
+	func->function.ss_descriptors = NULL;
+	func->function.ssp_descriptors = NULL;
+	func->interfaces_nums = NULL;
+
+}
+
+static struct usb_function *ffs_alloc(struct usb_function_instance *fi)
+{
+	struct ffs_function *func;
+
+	ENTER();
+
+	func = kzalloc(sizeof(*func), GFP_KERNEL);
+	if (unlikely(!func))
+		return ERR_PTR(-ENOMEM);
+
+	func->function.name    = "Function FS Gadget";
+
+	func->function.bind    = ffs_func_bind;
+	func->function.unbind  = ffs_func_unbind;
+	func->function.set_alt = ffs_func_set_alt;
+	func->function.disable = ffs_func_disable;
+	func->function.setup   = ffs_func_setup;
+	func->function.req_match = ffs_func_req_match;
+	func->function.suspend = ffs_func_suspend;
+	func->function.resume  = ffs_func_resume;
+	func->function.free_func = ffs_free;
+
+	return &func->function;
+}
+
+/*
+ * ffs_lock must be taken by the caller of this function
+ */
+static struct ffs_dev *_ffs_alloc_dev(void)
+{
+	struct ffs_dev *dev;
+	int ret;
+
+	if (_ffs_get_single_dev())
+			return ERR_PTR(-EBUSY);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return ERR_PTR(-ENOMEM);
+
+	if (list_empty(&ffs_devices)) {
+		ret = functionfs_init();
+		if (ret) {
+			kfree(dev);
+			return ERR_PTR(ret);
+		}
+	}
+
+	list_add(&dev->entry, &ffs_devices);
+
+	return dev;
+}
+
+int ffs_name_dev(struct ffs_dev *dev, const char *name)
+{
+	struct ffs_dev *existing;
+	int ret = 0;
+
+	ffs_dev_lock();
+
+	existing = _ffs_do_find_dev(name);
+	if (!existing)
+		strlcpy(dev->name, name, ARRAY_SIZE(dev->name));
+	else if (existing != dev)
+		ret = -EBUSY;
+
+	ffs_dev_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ffs_name_dev);
+
+int ffs_single_dev(struct ffs_dev *dev)
+{
+	int ret;
+
+	ret = 0;
+	ffs_dev_lock();
+
+	if (!list_is_singular(&ffs_devices))
+		ret = -EBUSY;
+	else
+		dev->single = true;
+
+	ffs_dev_unlock();
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ffs_single_dev);
+
+/*
+ * ffs_lock must be taken by the caller of this function
+ */
+static void _ffs_free_dev(struct ffs_dev *dev)
+{
+	list_del(&dev->entry);
+
+	kfree(dev);
+	if (list_empty(&ffs_devices))
+		functionfs_cleanup();
+}
+
+static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data)
+{
+	int ret = 0;
+	struct ffs_dev *ffs_dev;
+
+	ENTER();
+	ffs_dev_lock();
+
+	ffs_dev = _ffs_find_dev(dev_name);
+	if (!ffs_dev) {
+		ret = -ENOENT;
+	} else if (ffs_dev->mounted) {
+		ret = -EBUSY;
+	} else if (ffs_dev->ffs_acquire_dev_callback &&
+		   ffs_dev->ffs_acquire_dev_callback(ffs_dev)) {
+		ret = -ENOENT;
+	} else {
+		ffs_dev->mounted = true;
+		ffs_dev->ffs_data = ffs_data;
+		ffs_data->private_data = ffs_dev;
+	}
+
+	ffs_dev_unlock();
+	return ret;
+}
+
+static void ffs_release_dev(struct ffs_dev *ffs_dev)
+{
+	ENTER();
+	ffs_dev_lock();
+
+	if (ffs_dev && ffs_dev->mounted) {
+		ffs_dev->mounted = false;
+		if (ffs_dev->ffs_data) {
+			ffs_dev->ffs_data->private_data = NULL;
+			ffs_dev->ffs_data = NULL;
+		}
+
+		if (ffs_dev->ffs_release_dev_callback)
+			ffs_dev->ffs_release_dev_callback(ffs_dev);
+	}
+
+	ffs_dev_unlock();
+}
+
+static int ffs_ready(struct ffs_data *ffs)
+{
+	struct ffs_dev *ffs_obj;
+	int ret = 0;
+
+	ENTER();
+	ffs_dev_lock();
+
+	ffs_obj = ffs->private_data;
+	if (!ffs_obj) {
+		ret = -EINVAL;
+		goto done;
+	}
+	if (WARN_ON(ffs_obj->desc_ready)) {
+		ret = -EBUSY;
+		goto done;
+	}
+
+	ffs_obj->desc_ready = true;
+
+	if (ffs_obj->ffs_ready_callback) {
+		ret = ffs_obj->ffs_ready_callback(ffs);
+		if (ret)
+			goto done;
+	}
+
+	set_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags);
+done:
+	ffs_dev_unlock();
+	return ret;
+}
+
+static void ffs_closed(struct ffs_data *ffs)
+{
+	struct ffs_dev *ffs_obj;
+	struct f_fs_opts *opts;
+	struct config_item *ci;
+
+	ENTER();
+	ffs_dev_lock();
+
+	ffs_obj = ffs->private_data;
+	if (!ffs_obj)
+		goto done;
+
+	ffs_obj->desc_ready = false;
+
+	if (test_and_clear_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags) &&
+	    ffs_obj->ffs_closed_callback)
+		ffs_obj->ffs_closed_callback(ffs);
+
+	if (ffs_obj->opts)
+		opts = ffs_obj->opts;
+	else
+		goto done;
+
+	if (opts->no_configfs || !opts->func_inst.group.cg_item.ci_parent
+	    || !kref_read(&opts->func_inst.group.cg_item.ci_kref))
+		goto done;
+
+	ci = opts->func_inst.group.cg_item.ci_parent->ci_parent;
+	ffs_dev_unlock();
+
+	if (test_bit(FFS_FL_BOUND, &ffs->flags))
+		unregister_gadget_item(ci);
+	return;
+done:
+	ffs_dev_unlock();
+}
+
+/* Misc helper functions ****************************************************/
+
+static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock)
+{
+	return nonblock
+		? likely(mutex_trylock(mutex)) ? 0 : -EAGAIN
+		: mutex_lock_interruptible(mutex);
+}
+
+static char *ffs_prepare_buffer(const char __user *buf, size_t len)
+{
+	char *data;
+
+	if (unlikely(!len))
+		return NULL;
+
+	data = kmalloc(len, GFP_KERNEL);
+	if (unlikely(!data))
+		return ERR_PTR(-ENOMEM);
+
+	if (unlikely(copy_from_user(data, buf, len))) {
+		kfree(data);
+		return ERR_PTR(-EFAULT);
+	}
+
+	pr_vdebug("Buffer from user space:\n");
+	ffs_dump_mem("", data, len);
+
+	return data;
+}
+
+DECLARE_USB_FUNCTION_INIT(ffs, ffs_alloc_inst, ffs_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal Nazarewicz");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_hid.c b/marvell/linux/drivers/usb/gadget/function/f_hid.c
new file mode 100644
index 0000000..571560d
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_hid.c
@@ -0,0 +1,1363 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_hid.c -- USB HID function driver
+ *
+ * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/hid.h>
+#include <linux/idr.h>
+#include <linux/cdev.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/usb/g_hid.h>
+
+#include "u_f.h"
+#include "u_hid.h"
+
+#define HIDG_MINORS	4
+
+static int major, minors;
+static struct class *hidg_class;
+static DEFINE_IDA(hidg_ida);
+static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */
+
+/*-------------------------------------------------------------------------*/
+/*                            HID gadget struct                            */
+
+struct f_hidg_req_list {
+	struct usb_request	*req;
+	unsigned int		pos;
+	struct list_head 	list;
+};
+
+struct f_hidg {
+	/* configuration */
+	unsigned char			bInterfaceSubClass;
+	unsigned char			bInterfaceProtocol;
+	unsigned char			protocol;
+	unsigned char			idle;
+	unsigned short			report_desc_length;
+	char				*report_desc;
+	unsigned short			report_length;
+	/*
+	 * use_out_ep - if true, the OUT Endpoint (interrupt out method)
+	 *              will be used to receive reports from the host
+	 *              using functions with the "intout" suffix.
+	 *              Otherwise, the OUT Endpoint will not be configured
+	 *              and the SETUP/SET_REPORT method ("ssreport" suffix)
+	 *              will be used to receive reports.
+	 */
+	bool				use_out_ep;
+
+	/* recv report */
+	spinlock_t			read_spinlock;
+	wait_queue_head_t		read_queue;
+	/* recv report - interrupt out only (use_out_ep == 1) */
+	struct list_head		completed_out_req;
+	unsigned int			qlen;
+	/* recv report - setup set_report only (use_out_ep == 0) */
+	char				*set_report_buf;
+	unsigned int			set_report_length;
+
+	/* send report */
+	spinlock_t			write_spinlock;
+	bool				write_pending;
+	wait_queue_head_t		write_queue;
+	struct usb_request		*req;
+
+	struct device			dev;
+	struct cdev			cdev;
+	struct usb_function		func;
+
+	struct usb_ep			*in_ep;
+	struct usb_ep			*out_ep;
+};
+
+static inline struct f_hidg *func_to_hidg(struct usb_function *f)
+{
+	return container_of(f, struct f_hidg, func);
+}
+
+static void hidg_release(struct device *dev)
+{
+	struct f_hidg *hidg = container_of(dev, struct f_hidg, dev);
+
+	kfree(hidg->report_desc);
+	kfree(hidg->set_report_buf);
+	kfree(hidg);
+}
+
+/*-------------------------------------------------------------------------*/
+/*                           Static descriptors                            */
+
+static struct usb_interface_descriptor hidg_interface_desc = {
+	.bLength		= sizeof hidg_interface_desc,
+	.bDescriptorType	= USB_DT_INTERFACE,
+	/* .bInterfaceNumber	= DYNAMIC */
+	.bAlternateSetting	= 0,
+	/* .bNumEndpoints	= DYNAMIC (depends on use_out_ep) */
+	.bInterfaceClass	= USB_CLASS_HID,
+	/* .bInterfaceSubClass	= DYNAMIC */
+	/* .bInterfaceProtocol	= DYNAMIC */
+	/* .iInterface		= DYNAMIC */
+};
+
+static struct hid_descriptor hidg_desc = {
+	.bLength			= sizeof hidg_desc,
+	.bDescriptorType		= HID_DT_HID,
+	.bcdHID				= cpu_to_le16(0x0101),
+	.bCountryCode			= 0x00,
+	.bNumDescriptors		= 0x1,
+	/*.desc[0].bDescriptorType	= DYNAMIC */
+	/*.desc[0].wDescriptorLenght	= DYNAMIC */
+};
+
+/* Super-Speed Support */
+
+static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 4, /* FIXME: Add this field in the
+				      * HID gadget configuration?
+				      * (struct hidg_func_descriptor)
+				      */
+};
+
+static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = {
+	.bLength                = sizeof(hidg_ss_in_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+	/* .wBytesPerInterval   = DYNAMIC */
+};
+
+static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 4, /* FIXME: Add this field in the
+				      * HID gadget configuration?
+				      * (struct hidg_func_descriptor)
+				      */
+};
+
+static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = {
+	.bLength                = sizeof(hidg_ss_out_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+	/* .wBytesPerInterval   = DYNAMIC */
+};
+
+static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
+	(struct usb_descriptor_header *)&hidg_ss_in_comp_desc,
+	(struct usb_descriptor_header *)&hidg_ss_out_ep_desc,
+	(struct usb_descriptor_header *)&hidg_ss_out_comp_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
+	(struct usb_descriptor_header *)&hidg_ss_in_comp_desc,
+	NULL,
+};
+
+/* High-Speed Support */
+
+static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 4, /* FIXME: Add this field in the
+				      * HID gadget configuration?
+				      * (struct hidg_func_descriptor)
+				      */
+};
+
+static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 4, /* FIXME: Add this field in the
+				      * HID gadget configuration?
+				      * (struct hidg_func_descriptor)
+				      */
+};
+
+static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
+	(struct usb_descriptor_header *)&hidg_hs_out_ep_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
+	NULL,
+};
+
+/* Full-Speed Support */
+
+static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 10, /* FIXME: Add this field in the
+				       * HID gadget configuration?
+				       * (struct hidg_func_descriptor)
+				       */
+};
+
+static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	/*.wMaxPacketSize	= DYNAMIC */
+	.bInterval		= 10, /* FIXME: Add this field in the
+				       * HID gadget configuration?
+				       * (struct hidg_func_descriptor)
+				       */
+};
+
+static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
+	(struct usb_descriptor_header *)&hidg_fs_out_ep_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = {
+	(struct usb_descriptor_header *)&hidg_interface_desc,
+	(struct usb_descriptor_header *)&hidg_desc,
+	(struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+/*                                 Strings                                 */
+
+#define CT_FUNC_HID_IDX	0
+
+static struct usb_string ct_func_string_defs[] = {
+	[CT_FUNC_HID_IDX].s	= "HID Interface",
+	{},			/* end of list */
+};
+
+static struct usb_gadget_strings ct_func_string_table = {
+	.language	= 0x0409,	/* en-US */
+	.strings	= ct_func_string_defs,
+};
+
+static struct usb_gadget_strings *ct_func_strings[] = {
+	&ct_func_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+/*                              Char Device                                */
+
+static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
+				  size_t count, loff_t *ptr)
+{
+	struct f_hidg *hidg = file->private_data;
+	struct f_hidg_req_list *list;
+	struct usb_request *req;
+	unsigned long flags;
+	int ret;
+
+	if (!count)
+		return 0;
+
+	if (!access_ok(buffer, count))
+		return -EFAULT;
+
+	spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req))
+
+	/* wait for at least one buffer to complete */
+	while (!READ_COND_INTOUT) {
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT))
+			return -ERESTARTSYS;
+
+		spin_lock_irqsave(&hidg->read_spinlock, flags);
+	}
+
+	/* pick the first one */
+	list = list_first_entry(&hidg->completed_out_req,
+				struct f_hidg_req_list, list);
+
+	/*
+	 * Remove this from list to protect it from beign free()
+	 * while host disables our function
+	 */
+	list_del(&list->list);
+
+	req = list->req;
+	count = min_t(unsigned int, count, req->actual - list->pos);
+	spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+	/* copy to user outside spinlock */
+	count -= copy_to_user(buffer, req->buf + list->pos, count);
+	list->pos += count;
+
+	/*
+	 * if this request is completely handled and transfered to
+	 * userspace, remove its entry from the list and requeue it
+	 * again. Otherwise, we will revisit it again upon the next
+	 * call, taking into account its current read position.
+	 */
+	if (list->pos == req->actual) {
+		kfree(list);
+
+		req->length = hidg->report_length;
+		ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL);
+		if (ret < 0) {
+			free_ep_req(hidg->out_ep, req);
+			return ret;
+		}
+	} else {
+		spin_lock_irqsave(&hidg->read_spinlock, flags);
+		list_add(&list->list, &hidg->completed_out_req);
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+		wake_up(&hidg->read_queue);
+	}
+
+	return count;
+}
+
+#define READ_COND_SSREPORT (hidg->set_report_buf != NULL)
+
+static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer,
+				    size_t count, loff_t *ptr)
+{
+	struct f_hidg *hidg = file->private_data;
+	char *tmp_buf = NULL;
+	unsigned long flags;
+
+	if (!count)
+		return 0;
+
+	spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+	while (!READ_COND_SSREPORT) {
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT))
+			return -ERESTARTSYS;
+
+		spin_lock_irqsave(&hidg->read_spinlock, flags);
+	}
+
+	count = min_t(unsigned int, count, hidg->set_report_length);
+	tmp_buf = hidg->set_report_buf;
+	hidg->set_report_buf = NULL;
+
+	spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+	if (tmp_buf != NULL) {
+		count -= copy_to_user(buffer, tmp_buf, count);
+		kfree(tmp_buf);
+	} else {
+		count = -ENOMEM;
+	}
+
+	wake_up(&hidg->read_queue);
+
+	return count;
+}
+
+static ssize_t f_hidg_read(struct file *file, char __user *buffer,
+			   size_t count, loff_t *ptr)
+{
+	struct f_hidg *hidg = file->private_data;
+
+	if (hidg->use_out_ep)
+		return f_hidg_intout_read(file, buffer, count, ptr);
+	else
+		return f_hidg_ssreport_read(file, buffer, count, ptr);
+}
+
+static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_hidg *hidg = (struct f_hidg *)ep->driver_data;
+	unsigned long flags;
+
+	if (req->status != 0) {
+		ERROR(hidg->func.config->cdev,
+			"End Point Request ERROR: %d\n", req->status);
+	}
+
+	spin_lock_irqsave(&hidg->write_spinlock, flags);
+	hidg->write_pending = 0;
+	spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+	wake_up(&hidg->write_queue);
+}
+
+static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
+			    size_t count, loff_t *offp)
+{
+	struct f_hidg *hidg  = file->private_data;
+	struct usb_request *req;
+	unsigned long flags;
+	ssize_t status = -ENOMEM;
+
+	if (!access_ok(buffer, count))
+		return -EFAULT;
+
+	spin_lock_irqsave(&hidg->write_spinlock, flags);
+
+	if (!hidg->req) {
+		spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+		return -ESHUTDOWN;
+	}
+
+#define WRITE_COND (!hidg->write_pending)
+try_again:
+	/* write queue */
+	while (!WRITE_COND) {
+		spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		if (wait_event_interruptible_exclusive(
+				hidg->write_queue, WRITE_COND))
+			return -ERESTARTSYS;
+
+		spin_lock_irqsave(&hidg->write_spinlock, flags);
+	}
+
+	hidg->write_pending = 1;
+	req = hidg->req;
+	count  = min_t(unsigned, count, hidg->report_length);
+
+	spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+
+	if (!req) {
+		ERROR(hidg->func.config->cdev, "hidg->req is NULL\n");
+		status = -ESHUTDOWN;
+		goto release_write_pending;
+	}
+
+	status = copy_from_user(req->buf, buffer, count);
+	if (status != 0) {
+		ERROR(hidg->func.config->cdev,
+			"copy_from_user error\n");
+		status = -EINVAL;
+		goto release_write_pending;
+	}
+
+	spin_lock_irqsave(&hidg->write_spinlock, flags);
+
+	/* when our function has been disabled by host */
+	if (!hidg->req) {
+		free_ep_req(hidg->in_ep, req);
+		/*
+		 * TODO
+		 * Should we fail with error here?
+		 */
+		goto try_again;
+	}
+
+	req->status   = 0;
+	req->zero     = 0;
+	req->length   = count;
+	req->complete = f_hidg_req_complete;
+	req->context  = hidg;
+
+	spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+
+	if (!hidg->in_ep->enabled) {
+		ERROR(hidg->func.config->cdev, "in_ep is disabled\n");
+		status = -ESHUTDOWN;
+		goto release_write_pending;
+	}
+
+	status = usb_ep_queue(hidg->in_ep, req, GFP_ATOMIC);
+	if (status < 0)
+		goto release_write_pending;
+	else
+		status = count;
+
+	return status;
+release_write_pending:
+	spin_lock_irqsave(&hidg->write_spinlock, flags);
+	hidg->write_pending = 0;
+	spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+
+	wake_up(&hidg->write_queue);
+
+	return status;
+}
+
+static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
+{
+	struct f_hidg	*hidg  = file->private_data;
+	__poll_t	ret = 0;
+
+	poll_wait(file, &hidg->read_queue, wait);
+	poll_wait(file, &hidg->write_queue, wait);
+
+	if (WRITE_COND)
+		ret |= EPOLLOUT | EPOLLWRNORM;
+
+	if (hidg->use_out_ep) {
+		if (READ_COND_INTOUT)
+			ret |= EPOLLIN | EPOLLRDNORM;
+	} else {
+		if (READ_COND_SSREPORT)
+			ret |= EPOLLIN | EPOLLRDNORM;
+	}
+
+	return ret;
+}
+
+#undef WRITE_COND
+#undef READ_COND_SSREPORT
+#undef READ_COND_INTOUT
+
+static int f_hidg_release(struct inode *inode, struct file *fd)
+{
+	fd->private_data = NULL;
+	return 0;
+}
+
+static int f_hidg_open(struct inode *inode, struct file *fd)
+{
+	struct f_hidg *hidg =
+		container_of(inode->i_cdev, struct f_hidg, cdev);
+
+	fd->private_data = hidg;
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+/*                                usb_function                             */
+
+static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep,
+						    unsigned length)
+{
+	return alloc_ep_req(ep, length);
+}
+
+static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_hidg *hidg = (struct f_hidg *) req->context;
+	struct usb_composite_dev *cdev = hidg->func.config->cdev;
+	struct f_hidg_req_list *req_list;
+	unsigned long flags;
+
+	switch (req->status) {
+	case 0:
+		req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC);
+		if (!req_list) {
+			ERROR(cdev, "Unable to allocate mem for req_list\n");
+			goto free_req;
+		}
+
+		req_list->req = req;
+
+		spin_lock_irqsave(&hidg->read_spinlock, flags);
+		list_add_tail(&req_list->list, &hidg->completed_out_req);
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+		wake_up(&hidg->read_queue);
+		break;
+	default:
+		ERROR(cdev, "Set report failed %d\n", req->status);
+		/* FALLTHROUGH */
+	case -ECONNABORTED:		/* hardware forced ep reset */
+	case -ECONNRESET:		/* request dequeued */
+	case -ESHUTDOWN:		/* disconnect from host */
+free_req:
+		free_ep_req(ep, req);
+		return;
+	}
+}
+
+static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_hidg *hidg = (struct f_hidg *)req->context;
+	struct usb_composite_dev *cdev = hidg->func.config->cdev;
+	char *new_buf = NULL;
+	unsigned long flags;
+
+	if (req->status != 0 || req->buf == NULL || req->actual == 0) {
+		ERROR(cdev,
+		      "%s FAILED: status=%d, buf=%p, actual=%d\n",
+		      __func__, req->status, req->buf, req->actual);
+		return;
+	}
+
+	spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+	new_buf = krealloc(hidg->set_report_buf, req->actual, GFP_ATOMIC);
+	if (new_buf == NULL) {
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+		return;
+	}
+	hidg->set_report_buf = new_buf;
+
+	hidg->set_report_length = req->actual;
+	memcpy(hidg->set_report_buf, req->buf, req->actual);
+
+	spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+	wake_up(&hidg->read_queue);
+}
+
+static int hidg_setup(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct f_hidg			*hidg = func_to_hidg(f);
+	struct usb_composite_dev	*cdev = f->config->cdev;
+	struct usb_request		*req  = cdev->req;
+	int status = 0;
+	__u16 value, length;
+
+	value	= __le16_to_cpu(ctrl->wValue);
+	length	= __le16_to_cpu(ctrl->wLength);
+
+	VDBG(cdev,
+	     "%s crtl_request : bRequestType:0x%x bRequest:0x%x Value:0x%x\n",
+	     __func__, ctrl->bRequestType, ctrl->bRequest, value);
+
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_GET_REPORT):
+		VDBG(cdev, "get_report\n");
+
+		/* send an empty report */
+		length = min_t(unsigned, length, hidg->report_length);
+		memset(req->buf, 0x0, length);
+
+		goto respond;
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_GET_PROTOCOL):
+		VDBG(cdev, "get_protocol\n");
+		length = min_t(unsigned int, length, 1);
+		((u8 *) req->buf)[0] = hidg->protocol;
+		goto respond;
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_GET_IDLE):
+		VDBG(cdev, "get_idle\n");
+		length = min_t(unsigned int, length, 1);
+		((u8 *) req->buf)[0] = hidg->idle;
+		goto respond;
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_SET_REPORT):
+		VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
+		if (hidg->use_out_ep)
+			goto stall;
+		req->complete = hidg_ssreport_complete;
+		req->context  = hidg;
+		goto respond;
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_SET_PROTOCOL):
+		VDBG(cdev, "set_protocol\n");
+		if (value > HID_REPORT_PROTOCOL)
+			goto stall;
+		length = 0;
+		/*
+		 * We assume that programs implementing the Boot protocol
+		 * are also compatible with the Report Protocol
+		 */
+		if (hidg->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {
+			hidg->protocol = value;
+			goto respond;
+		}
+		goto stall;
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+		  | HID_REQ_SET_IDLE):
+		VDBG(cdev, "set_idle\n");
+		length = 0;
+		hidg->idle = value >> 8;
+		goto respond;
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8
+		  | USB_REQ_GET_DESCRIPTOR):
+		switch (value >> 8) {
+		case HID_DT_HID:
+		{
+			struct hid_descriptor hidg_desc_copy = hidg_desc;
+
+			VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n");
+			hidg_desc_copy.desc[0].bDescriptorType = HID_DT_REPORT;
+			hidg_desc_copy.desc[0].wDescriptorLength =
+				cpu_to_le16(hidg->report_desc_length);
+
+			length = min_t(unsigned short, length,
+						   hidg_desc_copy.bLength);
+			memcpy(req->buf, &hidg_desc_copy, length);
+			goto respond;
+			break;
+		}
+		case HID_DT_REPORT:
+			VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n");
+			length = min_t(unsigned short, length,
+						   hidg->report_desc_length);
+			memcpy(req->buf, hidg->report_desc, length);
+			goto respond;
+			break;
+
+		default:
+			VDBG(cdev, "Unknown descriptor request 0x%x\n",
+				 value >> 8);
+			goto stall;
+			break;
+		}
+		break;
+
+	default:
+		VDBG(cdev, "Unknown request 0x%x\n",
+			 ctrl->bRequest);
+		goto stall;
+		break;
+	}
+
+stall:
+	return -EOPNOTSUPP;
+
+respond:
+	req->zero = 0;
+	req->length = length;
+	status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+	if (status < 0)
+		ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value);
+	return status;
+}
+
+static void hidg_disable(struct usb_function *f)
+{
+	struct f_hidg *hidg = func_to_hidg(f);
+	struct f_hidg_req_list *list, *next;
+	unsigned long flags;
+
+	usb_ep_disable(hidg->in_ep);
+
+	if (hidg->out_ep) {
+		usb_ep_disable(hidg->out_ep);
+
+		spin_lock_irqsave(&hidg->read_spinlock, flags);
+		list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
+			free_ep_req(hidg->out_ep, list->req);
+			list_del(&list->list);
+			kfree(list);
+		}
+		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+	}
+
+	spin_lock_irqsave(&hidg->write_spinlock, flags);
+	if (!hidg->write_pending) {
+		free_ep_req(hidg->in_ep, hidg->req);
+		hidg->write_pending = 1;
+	}
+
+	hidg->req = NULL;
+	spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+}
+
+static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev		*cdev = f->config->cdev;
+	struct f_hidg				*hidg = func_to_hidg(f);
+	struct usb_request			*req_in = NULL;
+	unsigned long				flags;
+	int i, status = 0;
+
+	VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt);
+
+	if (hidg->in_ep != NULL) {
+		/* restart endpoint */
+		usb_ep_disable(hidg->in_ep);
+
+		status = config_ep_by_speed(f->config->cdev->gadget, f,
+					    hidg->in_ep);
+		if (status) {
+			ERROR(cdev, "config_ep_by_speed FAILED!\n");
+			goto fail;
+		}
+		status = usb_ep_enable(hidg->in_ep);
+		if (status < 0) {
+			ERROR(cdev, "Enable IN endpoint FAILED!\n");
+			goto fail;
+		}
+		hidg->in_ep->driver_data = hidg;
+
+		req_in = hidg_alloc_ep_req(hidg->in_ep, hidg->report_length);
+		if (!req_in) {
+			status = -ENOMEM;
+			goto disable_ep_in;
+		}
+	}
+
+	if (hidg->use_out_ep && hidg->out_ep != NULL) {
+		/* restart endpoint */
+		usb_ep_disable(hidg->out_ep);
+
+		status = config_ep_by_speed(f->config->cdev->gadget, f,
+					    hidg->out_ep);
+		if (status) {
+			ERROR(cdev, "config_ep_by_speed FAILED!\n");
+			goto free_req_in;
+		}
+		status = usb_ep_enable(hidg->out_ep);
+		if (status < 0) {
+			ERROR(cdev, "Enable OUT endpoint FAILED!\n");
+			goto free_req_in;
+		}
+		hidg->out_ep->driver_data = hidg;
+
+		/*
+		 * allocate a bunch of read buffers and queue them all at once.
+		 */
+		for (i = 0; i < hidg->qlen && status == 0; i++) {
+			struct usb_request *req =
+					hidg_alloc_ep_req(hidg->out_ep,
+							  hidg->report_length);
+			if (req) {
+				req->complete = hidg_intout_complete;
+				req->context  = hidg;
+				status = usb_ep_queue(hidg->out_ep, req,
+						      GFP_ATOMIC);
+				if (status) {
+					ERROR(cdev, "%s queue req --> %d\n",
+						hidg->out_ep->name, status);
+					free_ep_req(hidg->out_ep, req);
+				}
+			} else {
+				status = -ENOMEM;
+				goto disable_out_ep;
+			}
+		}
+	}
+
+	if (hidg->in_ep != NULL) {
+		spin_lock_irqsave(&hidg->write_spinlock, flags);
+		hidg->req = req_in;
+		hidg->write_pending = 0;
+		spin_unlock_irqrestore(&hidg->write_spinlock, flags);
+
+		wake_up(&hidg->write_queue);
+	}
+	return 0;
+disable_out_ep:
+	if (hidg->out_ep)
+		usb_ep_disable(hidg->out_ep);
+free_req_in:
+	if (req_in)
+		free_ep_req(hidg->in_ep, req_in);
+
+disable_ep_in:
+	if (hidg->in_ep)
+		usb_ep_disable(hidg->in_ep);
+
+fail:
+	return status;
+}
+
+static const struct file_operations f_hidg_fops = {
+	.owner		= THIS_MODULE,
+	.open		= f_hidg_open,
+	.release	= f_hidg_release,
+	.write		= f_hidg_write,
+	.read		= f_hidg_read,
+	.poll		= f_hidg_poll,
+	.llseek		= noop_llseek,
+};
+
+static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_ep		*ep;
+	struct f_hidg		*hidg = func_to_hidg(f);
+	struct usb_string	*us;
+	int			status;
+
+	/* maybe allocate device-global string IDs, and patch descriptors */
+	us = usb_gstrings_attach(c->cdev, ct_func_strings,
+				 ARRAY_SIZE(ct_func_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	hidg_interface_desc.iInterface = us[CT_FUNC_HID_IDX].id;
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	hidg_interface_desc.bInterfaceNumber = status;
+
+	/* allocate instance-specific endpoints */
+	status = -ENODEV;
+	ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc);
+	if (!ep)
+		goto fail;
+	hidg->in_ep = ep;
+
+	hidg->out_ep = NULL;
+	if (hidg->use_out_ep) {
+		ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
+		if (!ep)
+			goto fail;
+		hidg->out_ep = ep;
+	}
+
+	/* used only if use_out_ep == 1 */
+	hidg->set_report_buf = NULL;
+
+	/* set descriptor dynamic values */
+	hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass;
+	hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol;
+	hidg_interface_desc.bNumEndpoints = hidg->use_out_ep ? 2 : 1;
+	hidg->protocol = HID_REPORT_PROTOCOL;
+	hidg->idle = 1;
+	hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	hidg_ss_in_comp_desc.wBytesPerInterval =
+				cpu_to_le16(hidg->report_length);
+	hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	hidg_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	hidg_ss_out_comp_desc.wBytesPerInterval =
+				cpu_to_le16(hidg->report_length);
+	hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
+	/*
+	 * We can use hidg_desc struct here but we should not relay
+	 * that its content won't change after returning from this function.
+	 */
+	hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT;
+	hidg_desc.desc[0].wDescriptorLength =
+		cpu_to_le16(hidg->report_desc_length);
+
+	hidg_hs_in_ep_desc.bEndpointAddress =
+		hidg_fs_in_ep_desc.bEndpointAddress;
+	hidg_hs_out_ep_desc.bEndpointAddress =
+		hidg_fs_out_ep_desc.bEndpointAddress;
+
+	hidg_ss_in_ep_desc.bEndpointAddress =
+		hidg_fs_in_ep_desc.bEndpointAddress;
+	hidg_ss_out_ep_desc.bEndpointAddress =
+		hidg_fs_out_ep_desc.bEndpointAddress;
+
+	if (hidg->use_out_ep)
+		status = usb_assign_descriptors(f,
+			hidg_fs_descriptors_intout,
+			hidg_hs_descriptors_intout,
+			hidg_ss_descriptors_intout,
+			hidg_ss_descriptors_intout);
+	else
+		status = usb_assign_descriptors(f,
+			hidg_fs_descriptors_ssreport,
+			hidg_hs_descriptors_ssreport,
+			hidg_ss_descriptors_ssreport,
+			hidg_ss_descriptors_ssreport);
+
+	if (status)
+		goto fail;
+
+	spin_lock_init(&hidg->write_spinlock);
+	hidg->write_pending = 1;
+	hidg->req = NULL;
+	spin_lock_init(&hidg->read_spinlock);
+	init_waitqueue_head(&hidg->write_queue);
+	init_waitqueue_head(&hidg->read_queue);
+	INIT_LIST_HEAD(&hidg->completed_out_req);
+
+	/* create char device */
+	cdev_init(&hidg->cdev, &f_hidg_fops);
+	status = cdev_device_add(&hidg->cdev, &hidg->dev);
+	if (status)
+		goto fail_free_descs;
+
+	return 0;
+fail_free_descs:
+	usb_free_all_descriptors(f);
+fail:
+	ERROR(f->config->cdev, "hidg_bind FAILED\n");
+	if (hidg->req != NULL)
+		free_ep_req(hidg->in_ep, hidg->req);
+
+	return status;
+}
+
+static inline int hidg_get_minor(void)
+{
+	int ret;
+
+	ret = ida_simple_get(&hidg_ida, 0, 0, GFP_KERNEL);
+	if (ret >= HIDG_MINORS) {
+		ida_simple_remove(&hidg_ida, ret);
+		ret = -ENODEV;
+	}
+
+	return ret;
+}
+
+static inline struct f_hid_opts *to_f_hid_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_hid_opts,
+			    func_inst.group);
+}
+
+static void hid_attr_release(struct config_item *item)
+{
+	struct f_hid_opts *opts = to_f_hid_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations hidg_item_ops = {
+	.release	= hid_attr_release,
+};
+
+#define F_HID_OPT(name, prec, limit)					\
+static ssize_t f_hid_opts_##name##_show(struct config_item *item, char *page)\
+{									\
+	struct f_hid_opts *opts = to_f_hid_opts(item);			\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%d\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_hid_opts_##name##_store(struct config_item *item,	\
+					 const char *page, size_t len)	\
+{									\
+	struct f_hid_opts *opts = to_f_hid_opts(item);			\
+	int ret;							\
+	u##prec num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou##prec(page, 0, &num);				\
+	if (ret)							\
+		goto end;						\
+									\
+	if (num > limit) {						\
+		ret = -EINVAL;						\
+		goto end;						\
+	}								\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_hid_opts_, name)
+
+F_HID_OPT(subclass, 8, 255);
+F_HID_OPT(protocol, 8, 255);
+F_HID_OPT(no_out_endpoint, 8, 1);
+F_HID_OPT(report_length, 16, 65535);
+
+static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page)
+{
+	struct f_hid_opts *opts = to_f_hid_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = opts->report_desc_length;
+	memcpy(page, opts->report_desc, opts->report_desc_length);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_hid_opts_report_desc_store(struct config_item *item,
+					    const char *page, size_t len)
+{
+	struct f_hid_opts *opts = to_f_hid_opts(item);
+	int ret = -EBUSY;
+	char *d;
+
+	mutex_lock(&opts->lock);
+
+	if (opts->refcnt)
+		goto end;
+	if (len > PAGE_SIZE) {
+		ret = -ENOSPC;
+		goto end;
+	}
+	d = kmemdup(page, len, GFP_KERNEL);
+	if (!d) {
+		ret = -ENOMEM;
+		goto end;
+	}
+	kfree(opts->report_desc);
+	opts->report_desc = d;
+	opts->report_desc_length = len;
+	opts->report_desc_alloc = true;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_hid_opts_, report_desc);
+
+static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page)
+{
+	struct f_hid_opts *opts = to_f_hid_opts(item);
+
+	return sprintf(page, "%d:%d\n", major, opts->minor);
+}
+
+CONFIGFS_ATTR_RO(f_hid_opts_, dev);
+
+static struct configfs_attribute *hid_attrs[] = {
+	&f_hid_opts_attr_subclass,
+	&f_hid_opts_attr_protocol,
+	&f_hid_opts_attr_no_out_endpoint,
+	&f_hid_opts_attr_report_length,
+	&f_hid_opts_attr_report_desc,
+	&f_hid_opts_attr_dev,
+	NULL,
+};
+
+static const struct config_item_type hid_func_type = {
+	.ct_item_ops	= &hidg_item_ops,
+	.ct_attrs	= hid_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static inline void hidg_put_minor(int minor)
+{
+	ida_simple_remove(&hidg_ida, minor);
+}
+
+static void hidg_free_inst(struct usb_function_instance *f)
+{
+	struct f_hid_opts *opts;
+
+	opts = container_of(f, struct f_hid_opts, func_inst);
+
+	mutex_lock(&hidg_ida_lock);
+
+	hidg_put_minor(opts->minor);
+	if (ida_is_empty(&hidg_ida))
+		ghid_cleanup();
+
+	mutex_unlock(&hidg_ida_lock);
+
+	if (opts->report_desc_alloc)
+		kfree(opts->report_desc);
+
+	kfree(opts);
+}
+
+static struct usb_function_instance *hidg_alloc_inst(void)
+{
+	struct f_hid_opts *opts;
+	struct usb_function_instance *ret;
+	int status = 0;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = hidg_free_inst;
+	ret = &opts->func_inst;
+
+	mutex_lock(&hidg_ida_lock);
+
+	if (ida_is_empty(&hidg_ida)) {
+		status = ghid_setup(NULL, HIDG_MINORS);
+		if (status)  {
+			ret = ERR_PTR(status);
+			kfree(opts);
+			goto unlock;
+		}
+	}
+
+	opts->minor = hidg_get_minor();
+	if (opts->minor < 0) {
+		ret = ERR_PTR(opts->minor);
+		kfree(opts);
+		if (ida_is_empty(&hidg_ida))
+			ghid_cleanup();
+		goto unlock;
+	}
+	config_group_init_type_name(&opts->func_inst.group, "", &hid_func_type);
+
+unlock:
+	mutex_unlock(&hidg_ida_lock);
+	return ret;
+}
+
+static void hidg_free(struct usb_function *f)
+{
+	struct f_hidg *hidg;
+	struct f_hid_opts *opts;
+
+	hidg = func_to_hidg(f);
+	opts = container_of(f->fi, struct f_hid_opts, func_inst);
+	put_device(&hidg->dev);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void hidg_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_hidg *hidg = func_to_hidg(f);
+
+	cdev_device_del(&hidg->cdev, &hidg->dev);
+
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
+{
+	struct f_hidg *hidg;
+	struct f_hid_opts *opts;
+	int ret;
+
+	/* allocate and initialize one new instance */
+	hidg = kzalloc(sizeof(*hidg), GFP_KERNEL);
+	if (!hidg)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_hid_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+
+	device_initialize(&hidg->dev);
+	hidg->dev.release = hidg_release;
+	hidg->dev.class = hidg_class;
+	hidg->dev.devt = MKDEV(major, opts->minor);
+	ret = dev_set_name(&hidg->dev, "hidg%d", opts->minor);
+	if (ret) {
+		--opts->refcnt;
+		mutex_unlock(&opts->lock);
+		return ERR_PTR(ret);
+	}
+
+	hidg->bInterfaceSubClass = opts->subclass;
+	hidg->bInterfaceProtocol = opts->protocol;
+	hidg->report_length = opts->report_length;
+	hidg->report_desc_length = opts->report_desc_length;
+	if (opts->report_desc) {
+		hidg->report_desc = kmemdup(opts->report_desc,
+					    opts->report_desc_length,
+					    GFP_KERNEL);
+		if (!hidg->report_desc) {
+			put_device(&hidg->dev);
+			--opts->refcnt;
+			mutex_unlock(&opts->lock);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+	hidg->use_out_ep = !opts->no_out_endpoint;
+
+	mutex_unlock(&opts->lock);
+
+	hidg->func.name    = "hid";
+	hidg->func.bind    = hidg_bind;
+	hidg->func.unbind  = hidg_unbind;
+	hidg->func.set_alt = hidg_set_alt;
+	hidg->func.disable = hidg_disable;
+	hidg->func.setup   = hidg_setup;
+	hidg->func.free_func = hidg_free;
+
+	/* this could me made configurable at some point */
+	hidg->qlen	   = 4;
+
+	return &hidg->func;
+}
+
+DECLARE_USB_FUNCTION_INIT(hid, hidg_alloc_inst, hidg_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Fabien Chouteau");
+
+int ghid_setup(struct usb_gadget *g, int count)
+{
+	int status;
+	dev_t dev;
+
+	hidg_class = class_create(THIS_MODULE, "hidg");
+	if (IS_ERR(hidg_class)) {
+		status = PTR_ERR(hidg_class);
+		hidg_class = NULL;
+		return status;
+	}
+
+	status = alloc_chrdev_region(&dev, 0, count, "hidg");
+	if (status) {
+		class_destroy(hidg_class);
+		hidg_class = NULL;
+		return status;
+	}
+
+	major = MAJOR(dev);
+	minors = count;
+
+	return 0;
+}
+
+void ghid_cleanup(void)
+{
+	if (major) {
+		unregister_chrdev_region(MKDEV(major, 0), minors);
+		major = minors = 0;
+	}
+
+	class_destroy(hidg_class);
+	hidg_class = NULL;
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_loopback.c b/marvell/linux/drivers/usb/gadget/function/f_loopback.c
new file mode 100644
index 0000000..90215a8
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_loopback.c
@@ -0,0 +1,598 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_loopback.c - USB peripheral loopback configuration driver
+ *
+ * Copyright (C) 2003-2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/usb/composite.h>
+
+#include "g_zero.h"
+#include "u_f.h"
+
+/*
+ * LOOPBACK FUNCTION ... a testing vehicle for USB peripherals,
+ *
+ * This takes messages of various sizes written OUT to a device, and loops
+ * them back so they can be read IN from it.  It has been used by certain
+ * test applications.  It supports limited testing of data queueing logic.
+ */
+struct f_loopback {
+	struct usb_function	function;
+
+	struct usb_ep		*in_ep;
+	struct usb_ep		*out_ep;
+
+	unsigned                qlen;
+	unsigned                buflen;
+};
+
+static inline struct f_loopback *func_to_loop(struct usb_function *f)
+{
+	return container_of(f, struct f_loopback, function);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_interface_descriptor loopback_intf = {
+	.bLength =		sizeof(loopback_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_loop_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_loop_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_loopback_descs[] = {
+	(struct usb_descriptor_header *) &loopback_intf,
+	(struct usb_descriptor_header *) &fs_loop_sink_desc,
+	(struct usb_descriptor_header *) &fs_loop_source_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_loop_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_loop_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *hs_loopback_descs[] = {
+	(struct usb_descriptor_header *) &loopback_intf,
+	(struct usb_descriptor_header *) &hs_loop_source_desc,
+	(struct usb_descriptor_header *) &hs_loop_sink_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_loop_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_loop_source_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	0,
+};
+
+static struct usb_endpoint_descriptor ss_loop_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_loop_sink_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	0,
+};
+
+static struct usb_descriptor_header *ss_loopback_descs[] = {
+	(struct usb_descriptor_header *) &loopback_intf,
+	(struct usb_descriptor_header *) &ss_loop_source_desc,
+	(struct usb_descriptor_header *) &ss_loop_source_comp_desc,
+	(struct usb_descriptor_header *) &ss_loop_sink_desc,
+	(struct usb_descriptor_header *) &ss_loop_sink_comp_desc,
+	NULL,
+};
+
+/* function-specific strings: */
+
+static struct usb_string strings_loopback[] = {
+	[0].s = "loop input to output",
+	{  }			/* end of list */
+};
+
+static struct usb_gadget_strings stringtab_loop = {
+	.language	= 0x0409,	/* en-us */
+	.strings	= strings_loopback,
+};
+
+static struct usb_gadget_strings *loopback_strings[] = {
+	&stringtab_loop,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_loopback	*loop = func_to_loop(f);
+	int			id;
+	int ret;
+
+	/* allocate interface ID(s) */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	loopback_intf.bInterfaceNumber = id;
+
+	id = usb_string_id(cdev);
+	if (id < 0)
+		return id;
+	strings_loopback[0].id = id;
+	loopback_intf.iInterface = id;
+
+	/* allocate endpoints */
+
+	loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
+	if (!loop->in_ep) {
+autoconf_fail:
+		ERROR(cdev, "%s: can't autoconfigure on %s\n",
+			f->name, cdev->gadget->name);
+		return -ENODEV;
+	}
+
+	loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
+	if (!loop->out_ep)
+		goto autoconf_fail;
+
+	/* support high speed hardware */
+	hs_loop_source_desc.bEndpointAddress =
+		fs_loop_source_desc.bEndpointAddress;
+	hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
+
+	/* support super speed hardware */
+	ss_loop_source_desc.bEndpointAddress =
+		fs_loop_source_desc.bEndpointAddress;
+	ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs,
+			ss_loopback_descs, ss_loopback_descs);
+	if (ret)
+		return ret;
+
+	DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+	    (gadget_is_superspeed(c->cdev->gadget) ? "super" :
+	     (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
+			f->name, loop->in_ep->name, loop->out_ep->name);
+	return 0;
+}
+
+static void lb_free_func(struct usb_function *f)
+{
+	struct f_lb_opts *opts;
+
+	opts = container_of(f->fi, struct f_lb_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	usb_free_all_descriptors(f);
+	kfree(func_to_loop(f));
+}
+
+static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_loopback	*loop = ep->driver_data;
+	struct usb_composite_dev *cdev = loop->function.config->cdev;
+	int			status = req->status;
+
+	switch (status) {
+	case 0:				/* normal completion? */
+		if (ep == loop->out_ep) {
+			/*
+			 * We received some data from the host so let's
+			 * queue it so host can read the from our in ep
+			 */
+			struct usb_request *in_req = req->context;
+
+			in_req->zero = (req->actual < req->length);
+			in_req->length = req->actual;
+			ep = loop->in_ep;
+			req = in_req;
+		} else {
+			/*
+			 * We have just looped back a bunch of data
+			 * to host. Now let's wait for some more data.
+			 */
+			req = req->context;
+			ep = loop->out_ep;
+		}
+
+		/* queue the buffer back to host or for next bunch of data */
+		status = usb_ep_queue(ep, req, GFP_ATOMIC);
+		if (status == 0) {
+			return;
+		} else {
+			ERROR(cdev, "Unable to loop back buffer to %s: %d\n",
+			      ep->name, status);
+			goto free_req;
+		}
+
+		/* "should never get here" */
+	default:
+		ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name,
+				status, req->actual, req->length);
+		/* FALLTHROUGH */
+
+	/* NOTE:  since this driver doesn't maintain an explicit record
+	 * of requests it submitted (just maintains qlen count), we
+	 * rely on the hardware driver to clean up on disconnect or
+	 * endpoint disable.
+	 */
+	case -ECONNABORTED:		/* hardware forced ep reset */
+	case -ECONNRESET:		/* request dequeued */
+	case -ESHUTDOWN:		/* disconnect from host */
+free_req:
+		usb_ep_free_request(ep == loop->in_ep ?
+				    loop->out_ep : loop->in_ep,
+				    req->context);
+		free_ep_req(ep, req);
+		return;
+	}
+}
+
+static void disable_loopback(struct f_loopback *loop)
+{
+	struct usb_composite_dev	*cdev;
+
+	cdev = loop->function.config->cdev;
+	disable_endpoints(cdev, loop->in_ep, loop->out_ep, NULL, NULL);
+	VDBG(cdev, "%s disabled\n", loop->function.name);
+}
+
+static inline struct usb_request *lb_alloc_ep_req(struct usb_ep *ep, int len)
+{
+	return alloc_ep_req(ep, len);
+}
+
+static int alloc_requests(struct usb_composite_dev *cdev,
+			  struct f_loopback *loop)
+{
+	struct usb_request *in_req, *out_req;
+	int i;
+	int result = 0;
+
+	/*
+	 * allocate a bunch of read buffers and queue them all at once.
+	 * we buffer at most 'qlen' transfers; We allocate buffers only
+	 * for out transfer and reuse them in IN transfers to implement
+	 * our loopback functionality
+	 */
+	for (i = 0; i < loop->qlen && result == 0; i++) {
+		result = -ENOMEM;
+
+		in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC);
+		if (!in_req)
+			goto fail;
+
+		out_req = lb_alloc_ep_req(loop->out_ep, loop->buflen);
+		if (!out_req)
+			goto fail_in;
+
+		in_req->complete = loopback_complete;
+		out_req->complete = loopback_complete;
+
+		in_req->buf = out_req->buf;
+		/* length will be set in complete routine */
+		in_req->context = out_req;
+		out_req->context = in_req;
+
+		result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC);
+		if (result) {
+			ERROR(cdev, "%s queue req --> %d\n",
+					loop->out_ep->name, result);
+			goto fail_out;
+		}
+	}
+
+	return 0;
+
+fail_out:
+	free_ep_req(loop->out_ep, out_req);
+fail_in:
+	usb_ep_free_request(loop->in_ep, in_req);
+fail:
+	return result;
+}
+
+static int enable_endpoint(struct usb_composite_dev *cdev,
+			   struct f_loopback *loop, struct usb_ep *ep)
+{
+	int					result;
+
+	result = config_ep_by_speed(cdev->gadget, &(loop->function), ep);
+	if (result)
+		goto out;
+
+	result = usb_ep_enable(ep);
+	if (result < 0)
+		goto out;
+	ep->driver_data = loop;
+	result = 0;
+
+out:
+	return result;
+}
+
+static int
+enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop)
+{
+	int					result = 0;
+
+	result = enable_endpoint(cdev, loop, loop->in_ep);
+	if (result)
+		goto out;
+
+	result = enable_endpoint(cdev, loop, loop->out_ep);
+	if (result)
+		goto disable_in;
+
+	result = alloc_requests(cdev, loop);
+	if (result)
+		goto disable_out;
+
+	DBG(cdev, "%s enabled\n", loop->function.name);
+	return 0;
+
+disable_out:
+	usb_ep_disable(loop->out_ep);
+disable_in:
+	usb_ep_disable(loop->in_ep);
+out:
+	return result;
+}
+
+static int loopback_set_alt(struct usb_function *f,
+		unsigned intf, unsigned alt)
+{
+	struct f_loopback	*loop = func_to_loop(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt is zero */
+	disable_loopback(loop);
+	return enable_loopback(cdev, loop);
+}
+
+static void loopback_disable(struct usb_function *f)
+{
+	struct f_loopback	*loop = func_to_loop(f);
+
+	disable_loopback(loop);
+}
+
+static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
+{
+	struct f_loopback	*loop;
+	struct f_lb_opts	*lb_opts;
+
+	loop = kzalloc(sizeof *loop, GFP_KERNEL);
+	if (!loop)
+		return ERR_PTR(-ENOMEM);
+
+	lb_opts = container_of(fi, struct f_lb_opts, func_inst);
+
+	mutex_lock(&lb_opts->lock);
+	lb_opts->refcnt++;
+	mutex_unlock(&lb_opts->lock);
+
+	loop->buflen = lb_opts->bulk_buflen;
+	loop->qlen = lb_opts->qlen;
+	if (!loop->qlen)
+		loop->qlen = 32;
+
+	loop->function.name = "loopback";
+	loop->function.bind = loopback_bind;
+	loop->function.set_alt = loopback_set_alt;
+	loop->function.disable = loopback_disable;
+	loop->function.strings = loopback_strings;
+
+	loop->function.free_func = lb_free_func;
+
+	return &loop->function;
+}
+
+static inline struct f_lb_opts *to_f_lb_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_lb_opts,
+			    func_inst.group);
+}
+
+static void lb_attr_release(struct config_item *item)
+{
+	struct f_lb_opts *lb_opts = to_f_lb_opts(item);
+
+	usb_put_function_instance(&lb_opts->func_inst);
+}
+
+static struct configfs_item_operations lb_item_ops = {
+	.release		= lb_attr_release,
+};
+
+static ssize_t f_lb_opts_qlen_show(struct config_item *item, char *page)
+{
+	struct f_lb_opts *opts = to_f_lb_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%d\n", opts->qlen);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_lb_opts_qlen_store(struct config_item *item,
+				    const char *page, size_t len)
+{
+	struct f_lb_opts *opts = to_f_lb_opts(item);
+	int ret;
+	u32 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->qlen = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_lb_opts_, qlen);
+
+static ssize_t f_lb_opts_bulk_buflen_show(struct config_item *item, char *page)
+{
+	struct f_lb_opts *opts = to_f_lb_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%d\n", opts->bulk_buflen);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_lb_opts_bulk_buflen_store(struct config_item *item,
+				    const char *page, size_t len)
+{
+	struct f_lb_opts *opts = to_f_lb_opts(item);
+	int ret;
+	u32 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->bulk_buflen = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_lb_opts_, bulk_buflen);
+
+static struct configfs_attribute *lb_attrs[] = {
+	&f_lb_opts_attr_qlen,
+	&f_lb_opts_attr_bulk_buflen,
+	NULL,
+};
+
+static const struct config_item_type lb_func_type = {
+	.ct_item_ops    = &lb_item_ops,
+	.ct_attrs	= lb_attrs,
+	.ct_owner       = THIS_MODULE,
+};
+
+static void lb_free_instance(struct usb_function_instance *fi)
+{
+	struct f_lb_opts *lb_opts;
+
+	lb_opts = container_of(fi, struct f_lb_opts, func_inst);
+	kfree(lb_opts);
+}
+
+static struct usb_function_instance *loopback_alloc_instance(void)
+{
+	struct f_lb_opts *lb_opts;
+
+	lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL);
+	if (!lb_opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&lb_opts->lock);
+	lb_opts->func_inst.free_func_inst = lb_free_instance;
+	lb_opts->bulk_buflen = GZERO_BULK_BUFLEN;
+	lb_opts->qlen = GZERO_QLEN;
+
+	config_group_init_type_name(&lb_opts->func_inst.group, "",
+				    &lb_func_type);
+
+	return  &lb_opts->func_inst;
+}
+DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc);
+
+int __init lb_modinit(void)
+{
+	return usb_function_register(&Loopbackusb_func);
+}
+
+void __exit lb_modexit(void)
+{
+	usb_function_unregister(&Loopbackusb_func);
+}
+
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_mass_storage.c b/marvell/linux/drivers/usb/gadget/function/f_mass_storage.c
new file mode 100644
index 0000000..fdd1289
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_mass_storage.c
@@ -0,0 +1,3560 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * f_mass_storage.c -- Mass Storage USB Composite Function
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyright (C) 2009 Samsung Electronics
+ *                    Author: Michal Nazarewicz <mina86@mina86.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * The Mass Storage Function acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive.  In
+ * addition to providing an example of a genuinely useful composite
+ * function for a USB device, it also illustrates a technique of
+ * double-buffering for increased throughput.
+ *
+ * For more information about MSF and in particular its module
+ * parameters and sysfs interface read the
+ * <Documentation/usb/mass-storage.rst> file.
+ */
+
+/*
+ * MSF is configured by specifying a fsg_config structure.  It has the
+ * following fields:
+ *
+ *	nluns		Number of LUNs function have (anywhere from 1
+ *				to FSG_MAX_LUNS).
+ *	luns		An array of LUN configuration values.  This
+ *				should be filled for each LUN that
+ *				function will include (ie. for "nluns"
+ *				LUNs).  Each element of the array has
+ *				the following fields:
+ *	->filename	The path to the backing file for the LUN.
+ *				Required if LUN is not marked as
+ *				removable.
+ *	->ro		Flag specifying access to the LUN shall be
+ *				read-only.  This is implied if CD-ROM
+ *				emulation is enabled as well as when
+ *				it was impossible to open "filename"
+ *				in R/W mode.
+ *	->removable	Flag specifying that LUN shall be indicated as
+ *				being removable.
+ *	->cdrom		Flag specifying that LUN shall be reported as
+ *				being a CD-ROM.
+ *	->nofua		Flag specifying that FUA flag in SCSI WRITE(10,12)
+ *				commands for this LUN shall be ignored.
+ *
+ *	vendor_name
+ *	product_name
+ *	release		Information used as a reply to INQUIRY
+ *				request.  To use default set to NULL,
+ *				NULL, 0xffff respectively.  The first
+ *				field should be 8 and the second 16
+ *				characters or less.
+ *
+ *	can_stall	Set to permit function to halt bulk endpoints.
+ *				Disabled on some USB devices known not
+ *				to work correctly.  You should set it
+ *				to true.
+ *
+ * If "removable" is not set for a LUN then a backing file must be
+ * specified.  If it is set, then NULL filename means the LUN's medium
+ * is not loaded (an empty string as "filename" in the fsg_config
+ * structure causes error).  The CD-ROM emulation includes a single
+ * data track and no audio tracks; hence there need be only one
+ * backing file per LUN.
+ *
+ * This function is heavily based on "File-backed Storage Gadget" by
+ * Alan Stern which in turn is heavily based on "Gadget Zero" by David
+ * Brownell.  The driver's SCSI command interface was based on the
+ * "Information technology - Small Computer System Interface - 2"
+ * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93,
+ * available at <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>.
+ * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which
+ * was based on the "Universal Serial Bus Mass Storage Class UFI
+ * Command Specification" document, Revision 1.0, December 14, 1998,
+ * available at
+ * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>.
+ */
+
+/*
+ *				Driver Design
+ *
+ * The MSF is fairly straightforward.  There is a main kernel
+ * thread that handles most of the work.  Interrupt routines field
+ * callbacks from the controller driver: bulk- and interrupt-request
+ * completion notifications, endpoint-0 events, and disconnect events.
+ * Completion events are passed to the main thread by wakeup calls.  Many
+ * ep0 requests are handled at interrupt time, but SetInterface,
+ * SetConfiguration, and device reset requests are forwarded to the
+ * thread in the form of "exceptions" using SIGUSR1 signals (since they
+ * should interrupt any ongoing file I/O operations).
+ *
+ * The thread's main routine implements the standard command/data/status
+ * parts of a SCSI interaction.  It and its subroutines are full of tests
+ * for pending signals/exceptions -- all this polling is necessary since
+ * the kernel has no setjmp/longjmp equivalents.  (Maybe this is an
+ * indication that the driver really wants to be running in userspace.)
+ * An important point is that so long as the thread is alive it keeps an
+ * open reference to the backing file.  This will prevent unmounting
+ * the backing file's underlying filesystem and could cause problems
+ * during system shutdown, for example.  To prevent such problems, the
+ * thread catches INT, TERM, and KILL signals and converts them into
+ * an EXIT exception.
+ *
+ * In normal operation the main thread is started during the gadget's
+ * fsg_bind() callback and stopped during fsg_unbind().  But it can
+ * also exit when it receives a signal, and there's no point leaving
+ * the gadget running when the thread is dead.  As of this moment, MSF
+ * provides no way to deregister the gadget when thread dies -- maybe
+ * a callback functions is needed.
+ *
+ * To provide maximum throughput, the driver uses a circular pipeline of
+ * buffer heads (struct fsg_buffhd).  In principle the pipeline can be
+ * arbitrarily long; in practice the benefits don't justify having more
+ * than 2 stages (i.e., double buffering).  But it helps to think of the
+ * pipeline as being a long one.  Each buffer head contains a bulk-in and
+ * a bulk-out request pointer (since the buffer can be used for both
+ * output and input -- directions always are given from the host's
+ * point of view) as well as a pointer to the buffer and various state
+ * variables.
+ *
+ * Use of the pipeline follows a simple protocol.  There is a variable
+ * (fsg->next_buffhd_to_fill) that points to the next buffer head to use.
+ * At any time that buffer head may still be in use from an earlier
+ * request, so each buffer head has a state variable indicating whether
+ * it is EMPTY, FULL, or BUSY.  Typical use involves waiting for the
+ * buffer head to be EMPTY, filling the buffer either by file I/O or by
+ * USB I/O (during which the buffer head is BUSY), and marking the buffer
+ * head FULL when the I/O is complete.  Then the buffer will be emptied
+ * (again possibly by USB I/O, during which it is marked BUSY) and
+ * finally marked EMPTY again (possibly by a completion routine).
+ *
+ * A module parameter tells the driver to avoid stalling the bulk
+ * endpoints wherever the transport specification allows.  This is
+ * necessary for some UDCs like the SuperH, which cannot reliably clear a
+ * halt on a bulk endpoint.  However, under certain circumstances the
+ * Bulk-only specification requires a stall.  In such cases the driver
+ * will halt the endpoint and set a flag indicating that it should clear
+ * the halt in software during the next device reset.  Hopefully this
+ * will permit everything to work correctly.  Furthermore, although the
+ * specification allows the bulk-out endpoint to halt when the host sends
+ * too much data, implementing this would cause an unavoidable race.
+ * The driver will always use the "no-stall" approach for OUT transfers.
+ *
+ * One subtle point concerns sending status-stage responses for ep0
+ * requests.  Some of these requests, such as device reset, can involve
+ * interrupting an ongoing file I/O operation, which might take an
+ * arbitrarily long time.  During that delay the host might give up on
+ * the original ep0 request and issue a new one.  When that happens the
+ * driver should not notify the host about completion of the original
+ * request, as the host will no longer be waiting for it.  So the driver
+ * assigns to each ep0 request a unique tag, and it keeps track of the
+ * tag value of the request associated with a long-running exception
+ * (device-reset, interface-change, or configuration-change).  When the
+ * exception handler is finished, the status-stage response is submitted
+ * only if the current ep0 request tag is equal to the exception request
+ * tag.  Thus only the most recently received ep0 request will get a
+ * status-stage response.
+ *
+ * Warning: This driver source file is too long.  It ought to be split up
+ * into a header file plus about 3 separate .c files, to handle the details
+ * of the Gadget, USB Mass Storage, and SCSI protocols.
+ */
+
+
+/* #define VERBOSE_DEBUG */
+/* #define DUMP_MSGS */
+
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/dcache.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kthread.h>
+#include <linux/sched/signal.h>
+#include <linux/limits.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/freezer.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+
+#include <linux/nospec.h>
+
+//#include "configfs.h"
+
+
+/*------------------------------------------------------------------------*/
+
+#define FSG_DRIVER_DESC		"Mass Storage Function"
+#define FSG_DRIVER_VERSION	"2009/09/11"
+
+static const char fsg_string_interface[] = "Mass Storage";
+
+#include "storage_common.h"
+#include "f_mass_storage.h"
+
+#include "storage_common.c"
+
+/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */
+static struct usb_string		fsg_strings[] = {
+	{FSG_STRING_INTERFACE,		fsg_string_interface},
+	{}
+};
+
+static struct usb_gadget_strings	fsg_stringtab = {
+	.language	= 0x0409,		/* en-us */
+	.strings	= fsg_strings,
+};
+
+static struct usb_gadget_strings *fsg_strings_array[] = {
+	&fsg_stringtab,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+struct fsg_dev;
+struct fsg_common;
+
+/* Data shared by all the FSG instances. */
+struct fsg_common {
+	struct usb_gadget	*gadget;
+	struct usb_composite_dev *cdev;
+	struct fsg_dev		*fsg;
+	wait_queue_head_t	io_wait;
+	wait_queue_head_t	fsg_wait;
+
+	/* filesem protects: backing files in use */
+	struct rw_semaphore	filesem;
+
+	/* lock protects: state and thread_task */
+	spinlock_t		lock;
+
+	struct usb_ep		*ep0;		/* Copy of gadget->ep0 */
+	struct usb_request	*ep0req;	/* Copy of cdev->req */
+	unsigned int		ep0_req_tag;
+
+	struct fsg_buffhd	*next_buffhd_to_fill;
+	struct fsg_buffhd	*next_buffhd_to_drain;
+	struct fsg_buffhd	*buffhds;
+	unsigned int		fsg_num_buffers;
+
+	int			cmnd_size;
+	u8			cmnd[MAX_COMMAND_SIZE];
+
+	unsigned int		lun;
+	struct fsg_lun		*luns[FSG_MAX_LUNS];
+	struct fsg_lun		*curlun;
+
+	unsigned int		bulk_out_maxpacket;
+	enum fsg_state		state;		/* For exception handling */
+	unsigned int		exception_req_tag;
+	void			*exception_arg;
+
+	enum data_direction	data_dir;
+	u32			data_size;
+	u32			data_size_from_cmnd;
+	u32			tag;
+	u32			residue;
+	u32			usb_amount_left;
+
+	unsigned int		can_stall:1;
+	unsigned int		free_storage_on_release:1;
+	unsigned int		phase_error:1;
+	unsigned int		short_packet_received:1;
+	unsigned int		bad_lun_okay:1;
+	unsigned int		running:1;
+	unsigned int		sysfs:1;
+
+	struct completion	thread_notifier;
+	struct task_struct	*thread_task;
+
+	/* Gadget's private data. */
+	void			*private_data;
+
+	char inquiry_string[INQUIRY_STRING_LEN];
+};
+
+struct fsg_dev {
+	struct usb_function	function;
+	struct usb_gadget	*gadget;	/* Copy of cdev->gadget */
+	struct fsg_common	*common;
+
+	u16			interface_number;
+
+	unsigned int		bulk_in_enabled:1;
+	unsigned int		bulk_out_enabled:1;
+
+	unsigned long		atomic_bitflags;
+#define IGNORE_BULK_OUT		0
+
+	struct usb_ep		*bulk_in;
+	struct usb_ep		*bulk_out;
+};
+
+static inline int __fsg_is_set(struct fsg_common *common,
+			       const char *func, unsigned line)
+{
+	if (common->fsg)
+		return 1;
+	ERROR(common, "common->fsg is NULL in %s at %u\n", func, line);
+	WARN_ON(1);
+	return 0;
+}
+
+#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__))
+
+static inline struct fsg_dev *fsg_from_func(struct usb_function *f)
+{
+	return container_of(f, struct fsg_dev, function);
+}
+
+typedef void (*fsg_routine_t)(struct fsg_dev *);
+
+static int exception_in_progress(struct fsg_common *common)
+{
+	return common->state > FSG_STATE_NORMAL;
+}
+
+/* Make bulk-out requests be divisible by the maxpacket size */
+static void set_bulk_out_req_length(struct fsg_common *common,
+				    struct fsg_buffhd *bh, unsigned int length)
+{
+	unsigned int	rem;
+
+	bh->bulk_out_intended_length = length;
+	rem = length % common->bulk_out_maxpacket;
+	if (rem > 0)
+		length += common->bulk_out_maxpacket - rem;
+	bh->outreq->length = length;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
+{
+	const char	*name;
+
+	if (ep == fsg->bulk_in)
+		name = "bulk-in";
+	else if (ep == fsg->bulk_out)
+		name = "bulk-out";
+	else
+		name = ep->name;
+	DBG(fsg, "%s set halt\n", name);
+	return usb_ep_set_halt(ep);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* These routines may be called in process context or in_irq */
+
+static void __raise_exception(struct fsg_common *common, enum fsg_state new_state,
+			      void *arg)
+{
+	unsigned long		flags;
+
+	/*
+	 * Do nothing if a higher-priority exception is already in progress.
+	 * If a lower-or-equal priority exception is in progress, preempt it
+	 * and notify the main thread by sending it a signal.
+	 */
+	spin_lock_irqsave(&common->lock, flags);
+	if (common->state <= new_state) {
+		common->exception_req_tag = common->ep0_req_tag;
+		common->state = new_state;
+		common->exception_arg = arg;
+		if (common->thread_task)
+			send_sig_info(SIGUSR1, SEND_SIG_PRIV,
+				      common->thread_task);
+	}
+	spin_unlock_irqrestore(&common->lock, flags);
+}
+
+static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
+{
+	__raise_exception(common, new_state, NULL);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int ep0_queue(struct fsg_common *common)
+{
+	int	rc;
+
+	rc = usb_ep_queue(common->ep0, common->ep0req, GFP_ATOMIC);
+	common->ep0->driver_data = common;
+	if (rc != 0 && rc != -ESHUTDOWN) {
+		/* We can't do much more than wait for a reset */
+		WARNING(common, "error in submission: %s --> %d\n",
+			common->ep0->name, rc);
+	}
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Completion handlers. These always run in_irq. */
+
+static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_common	*common = ep->driver_data;
+	struct fsg_buffhd	*bh = req->context;
+
+	if (req->status || req->actual != req->length)
+		DBG(common, "%s --> %d, %u/%u\n", __func__,
+		    req->status, req->actual, req->length);
+	if (req->status == -ECONNRESET)		/* Request was cancelled */
+		usb_ep_fifo_flush(ep);
+
+	/* Synchronize with the smp_load_acquire() in sleep_thread() */
+	smp_store_release(&bh->state, BUF_STATE_EMPTY);
+	wake_up(&common->io_wait);
+}
+
+static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_common	*common = ep->driver_data;
+	struct fsg_buffhd	*bh = req->context;
+
+	dump_msg(common, "bulk-out", req->buf, req->actual);
+	if (req->status || req->actual != bh->bulk_out_intended_length)
+		DBG(common, "%s --> %d, %u/%u\n", __func__,
+		    req->status, req->actual, bh->bulk_out_intended_length);
+	if (req->status == -ECONNRESET)		/* Request was cancelled */
+		usb_ep_fifo_flush(ep);
+
+	/* Synchronize with the smp_load_acquire() in sleep_thread() */
+	smp_store_release(&bh->state, BUF_STATE_FULL);
+	wake_up(&common->io_wait);
+}
+
+static int _fsg_common_get_max_lun(struct fsg_common *common)
+{
+	int i = ARRAY_SIZE(common->luns) - 1;
+
+	while (i >= 0 && !common->luns[i])
+		--i;
+
+	return i;
+}
+
+static int fsg_setup(struct usb_function *f,
+		     const struct usb_ctrlrequest *ctrl)
+{
+	struct fsg_dev		*fsg = fsg_from_func(f);
+	struct usb_request	*req = fsg->common->ep0req;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	if (!fsg_is_set(fsg->common))
+		return -EOPNOTSUPP;
+
+	++fsg->common->ep0_req_tag;	/* Record arrival of a new request */
+	req->context = NULL;
+	req->length = 0;
+	dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl));
+
+	switch (ctrl->bRequest) {
+
+	case US_BULK_RESET_REQUEST:
+		if (ctrl->bRequestType !=
+		    (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != fsg->interface_number || w_value != 0 ||
+				w_length != 0)
+			return -EDOM;
+
+		/*
+		 * Raise an exception to stop the current operation
+		 * and reinitialize our state.
+		 */
+		DBG(fsg, "bulk reset request\n");
+		raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET);
+		return USB_GADGET_DELAYED_STATUS;
+
+	case US_BULK_GET_MAX_LUN:
+		if (ctrl->bRequestType !=
+		    (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != fsg->interface_number || w_value != 0 ||
+				w_length != 1)
+			return -EDOM;
+		VDBG(fsg, "get max LUN\n");
+		*(u8 *)req->buf = _fsg_common_get_max_lun(fsg->common);
+
+		/* Respond with data/status */
+		req->length = min((u16)1, w_length);
+		return ep0_queue(fsg->common);
+	}
+
+	VDBG(fsg,
+	     "unknown class-specific control req %02x.%02x v%04x i%04x l%u\n",
+	     ctrl->bRequestType, ctrl->bRequest,
+	     le16_to_cpu(ctrl->wValue), w_index, w_length);
+	return -EOPNOTSUPP;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* All the following routines run in process context */
+
+/* Use this for bulk or interrupt transfers, not ep0 */
+static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+			   struct usb_request *req)
+{
+	int	rc;
+
+	if (ep == fsg->bulk_in)
+		dump_msg(fsg, "bulk-in", req->buf, req->length);
+
+	rc = usb_ep_queue(ep, req, GFP_KERNEL);
+	if (rc) {
+
+		/* We can't do much more than wait for a reset */
+		req->status = rc;
+
+		/*
+		 * Note: currently the net2280 driver fails zero-length
+		 * submissions if DMA is enabled.
+		 */
+		if (rc != -ESHUTDOWN &&
+				!(rc == -EOPNOTSUPP && req->length == 0))
+			WARNING(fsg, "error in submission: %s --> %d\n",
+					ep->name, rc);
+	}
+	return rc;
+}
+
+static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	int rc;
+
+	if (!fsg_is_set(common))
+		return false;
+	bh->state = BUF_STATE_SENDING;
+	rc = start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq);
+	if (rc) {
+		bh->state = BUF_STATE_EMPTY;
+		if (rc == -ESHUTDOWN) {
+			common->running = 0;
+			return false;
+		}
+	}
+	return true;
+}
+
+static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	int rc;
+
+	if (!fsg_is_set(common))
+		return false;
+	bh->state = BUF_STATE_RECEIVING;
+	rc = start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq);
+	if (rc) {
+		bh->state = BUF_STATE_FULL;
+		if (rc == -ESHUTDOWN) {
+			common->running = 0;
+			return false;
+		}
+	}
+	return true;
+}
+
+static int sleep_thread(struct fsg_common *common, bool can_freeze,
+		struct fsg_buffhd *bh)
+{
+	int	rc;
+
+	/* Wait until a signal arrives or bh is no longer busy */
+	if (can_freeze)
+		/*
+		 * synchronize with the smp_store_release(&bh->state) in
+		 * bulk_in_complete() or bulk_out_complete()
+		 */
+		rc = wait_event_freezable(common->io_wait,
+				bh && smp_load_acquire(&bh->state) >=
+					BUF_STATE_EMPTY);
+	else
+		rc = wait_event_interruptible(common->io_wait,
+				bh && smp_load_acquire(&bh->state) >=
+					BUF_STATE_EMPTY);
+	return rc ? -EINTR : 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_read(struct fsg_common *common)
+{
+	struct fsg_lun		*curlun = common->curlun;
+	u32			lba;
+	struct fsg_buffhd	*bh;
+	int			rc;
+	u32			amount_left;
+	loff_t			file_offset, file_offset_tmp;
+	unsigned int		amount;
+	ssize_t			nread;
+
+	/*
+	 * Get the starting Logical Block Address and check that it's
+	 * not too big.
+	 */
+	if (common->cmnd[0] == READ_6)
+		lba = get_unaligned_be24(&common->cmnd[1]);
+	else {
+		lba = get_unaligned_be32(&common->cmnd[2]);
+
+		/*
+		 * We allow DPO (Disable Page Out = don't save data in the
+		 * cache) and FUA (Force Unit Access = don't read from the
+		 * cache), but we don't implement them.
+		 */
+		if ((common->cmnd[1] & ~0x18) != 0) {
+			curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+	file_offset = ((loff_t) lba) << curlun->blkbits;
+
+	/* Carry out the file reads */
+	amount_left = common->data_size_from_cmnd;
+	if (unlikely(amount_left == 0))
+		return -EIO;		/* No default reply */
+
+	for (;;) {
+		/*
+		 * Figure out how much we need to read:
+		 * Try to read the remaining amount.
+		 * But don't read more than the buffer size.
+		 * And don't try to read past the end of the file.
+		 */
+		amount = min(amount_left, FSG_BUFLEN);
+		amount = min((loff_t)amount,
+			     curlun->file_length - file_offset);
+
+		/* Wait for the next buffer to become available */
+		bh = common->next_buffhd_to_fill;
+		rc = sleep_thread(common, false, bh);
+		if (rc)
+			return rc;
+
+		/*
+		 * If we were asked to read past the end of file,
+		 * end with an empty buffer.
+		 */
+		if (amount == 0) {
+			curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+			curlun->sense_data_info =
+					file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			bh->inreq->length = 0;
+			bh->state = BUF_STATE_FULL;
+			break;
+		}
+
+		/* Perform the read */
+		file_offset_tmp = file_offset;
+		nread = kernel_read(curlun->filp, bh->buf, amount,
+				&file_offset_tmp);
+		VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+		      (unsigned long long)file_offset, (int)nread);
+		if (signal_pending(current))
+			return -EINTR;
+
+		if (nread < 0) {
+			LDBG(curlun, "error in file read: %d\n", (int)nread);
+			nread = 0;
+		} else if (nread < amount) {
+			LDBG(curlun, "partial file read: %d/%u\n",
+			     (int)nread, amount);
+			nread = round_down(nread, curlun->blksize);
+		}
+		file_offset  += nread;
+		amount_left  -= nread;
+		common->residue -= nread;
+
+		/*
+		 * Except at the end of the transfer, nread will be
+		 * equal to the buffer size, which is divisible by the
+		 * bulk-in maxpacket size.
+		 */
+		bh->inreq->length = nread;
+		bh->state = BUF_STATE_FULL;
+
+		/* If an error occurred, report it and its position */
+		if (nread < amount) {
+			curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+			curlun->sense_data_info =
+					file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+		if (amount_left == 0)
+			break;		/* No more left to read */
+
+		/* Send this buffer and go read some more */
+		bh->inreq->zero = 0;
+		if (!start_in_transfer(common, bh))
+			/* Don't know what to do if common->fsg is NULL */
+			return -EIO;
+		common->next_buffhd_to_fill = bh->next;
+	}
+
+	return -EIO;		/* No default reply */
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_write(struct fsg_common *common)
+{
+	struct fsg_lun		*curlun = common->curlun;
+	u32			lba;
+	struct fsg_buffhd	*bh;
+	int			get_some_more;
+	u32			amount_left_to_req, amount_left_to_write;
+	loff_t			usb_offset, file_offset, file_offset_tmp;
+	unsigned int		amount;
+	ssize_t			nwritten;
+	int			rc;
+
+	if (curlun->ro) {
+		curlun->sense_data = SS_WRITE_PROTECTED;
+		return -EINVAL;
+	}
+	spin_lock(&curlun->filp->f_lock);
+	curlun->filp->f_flags &= ~O_SYNC;	/* Default is not to wait */
+	spin_unlock(&curlun->filp->f_lock);
+
+	/*
+	 * Get the starting Logical Block Address and check that it's
+	 * not too big
+	 */
+	if (common->cmnd[0] == WRITE_6)
+		lba = get_unaligned_be24(&common->cmnd[1]);
+	else {
+		lba = get_unaligned_be32(&common->cmnd[2]);
+
+		/*
+		 * We allow DPO (Disable Page Out = don't save data in the
+		 * cache) and FUA (Force Unit Access = write directly to the
+		 * medium).  We don't implement DPO; we implement FUA by
+		 * performing synchronous output.
+		 */
+		if (common->cmnd[1] & ~0x18) {
+			curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+		if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */
+			spin_lock(&curlun->filp->f_lock);
+			curlun->filp->f_flags |= O_SYNC;
+			spin_unlock(&curlun->filp->f_lock);
+		}
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	/* Carry out the file writes */
+	get_some_more = 1;
+	file_offset = usb_offset = ((loff_t) lba) << curlun->blkbits;
+	amount_left_to_req = common->data_size_from_cmnd;
+	amount_left_to_write = common->data_size_from_cmnd;
+
+	while (amount_left_to_write > 0) {
+
+		/* Queue a request for more data from the host */
+		bh = common->next_buffhd_to_fill;
+		if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+			/*
+			 * Figure out how much we want to get:
+			 * Try to get the remaining amount,
+			 * but not more than the buffer size.
+			 */
+			amount = min(amount_left_to_req, FSG_BUFLEN);
+
+			/* Beyond the end of the backing file? */
+			if (usb_offset >= curlun->file_length) {
+				get_some_more = 0;
+				curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+				curlun->sense_data_info =
+					usb_offset >> curlun->blkbits;
+				curlun->info_valid = 1;
+				continue;
+			}
+
+			/* Get the next buffer */
+			usb_offset += amount;
+			common->usb_amount_left -= amount;
+			amount_left_to_req -= amount;
+			if (amount_left_to_req == 0)
+				get_some_more = 0;
+
+			/*
+			 * Except at the end of the transfer, amount will be
+			 * equal to the buffer size, which is divisible by
+			 * the bulk-out maxpacket size.
+			 */
+			set_bulk_out_req_length(common, bh, amount);
+			if (!start_out_transfer(common, bh))
+				/* Dunno what to do if common->fsg is NULL */
+				return -EIO;
+			common->next_buffhd_to_fill = bh->next;
+			continue;
+		}
+
+		/* Write the received data to the backing file */
+		bh = common->next_buffhd_to_drain;
+		if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+			break;			/* We stopped early */
+
+		/* Wait for the data to be received */
+		rc = sleep_thread(common, false, bh);
+		if (rc)
+			return rc;
+
+		common->next_buffhd_to_drain = bh->next;
+		bh->state = BUF_STATE_EMPTY;
+
+		/* Did something go wrong with the transfer? */
+		if (bh->outreq->status != 0) {
+			curlun->sense_data = SS_COMMUNICATION_FAILURE;
+			curlun->sense_data_info =
+					file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+		amount = bh->outreq->actual;
+		if (curlun->file_length - file_offset < amount) {
+			LERROR(curlun, "write %u @ %llu beyond end %llu\n",
+				       amount, (unsigned long long)file_offset,
+				       (unsigned long long)curlun->file_length);
+			amount = curlun->file_length - file_offset;
+		}
+
+		/*
+		 * Don't accept excess data.  The spec doesn't say
+		 * what to do in this case.  We'll ignore the error.
+		 */
+		amount = min(amount, bh->bulk_out_intended_length);
+
+		/* Don't write a partial block */
+		amount = round_down(amount, curlun->blksize);
+		if (amount == 0)
+			goto empty_write;
+
+		/* Perform the write */
+		file_offset_tmp = file_offset;
+		nwritten = kernel_write(curlun->filp, bh->buf, amount,
+				&file_offset_tmp);
+		VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+				(unsigned long long)file_offset, (int)nwritten);
+		if (signal_pending(current))
+			return -EINTR;		/* Interrupted! */
+
+		if (nwritten < 0) {
+			LDBG(curlun, "error in file write: %d\n",
+					(int) nwritten);
+			nwritten = 0;
+		} else if (nwritten < amount) {
+			LDBG(curlun, "partial file write: %d/%u\n",
+					(int) nwritten, amount);
+			nwritten = round_down(nwritten, curlun->blksize);
+		}
+		file_offset += nwritten;
+		amount_left_to_write -= nwritten;
+		common->residue -= nwritten;
+
+		/* If an error occurred, report it and its position */
+		if (nwritten < amount) {
+			curlun->sense_data = SS_WRITE_ERROR;
+			curlun->sense_data_info =
+					file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+ empty_write:
+		/* Did the host decide to stop early? */
+		if (bh->outreq->actual < bh->bulk_out_intended_length) {
+			common->short_packet_received = 1;
+			break;
+		}
+	}
+
+	return -EIO;		/* No default reply */
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_synchronize_cache(struct fsg_common *common)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		rc;
+
+	/* We ignore the requested LBA and write out all file's
+	 * dirty data buffers. */
+	rc = fsg_lun_fsync_sub(curlun);
+	if (rc)
+		curlun->sense_data = SS_WRITE_ERROR;
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void invalidate_sub(struct fsg_lun *curlun)
+{
+	struct file	*filp = curlun->filp;
+	struct inode	*inode = file_inode(filp);
+	unsigned long __maybe_unused	rc;
+
+	rc = invalidate_mapping_pages(inode->i_mapping, 0, -1);
+	VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc);
+}
+
+static int do_verify(struct fsg_common *common)
+{
+	struct fsg_lun		*curlun = common->curlun;
+	u32			lba;
+	u32			verification_length;
+	struct fsg_buffhd	*bh = common->next_buffhd_to_fill;
+	loff_t			file_offset, file_offset_tmp;
+	u32			amount_left;
+	unsigned int		amount;
+	ssize_t			nread;
+
+	/*
+	 * Get the starting Logical Block Address and check that it's
+	 * not too big.
+	 */
+	lba = get_unaligned_be32(&common->cmnd[2]);
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	/*
+	 * We allow DPO (Disable Page Out = don't save data in the
+	 * cache) but we don't implement it.
+	 */
+	if (common->cmnd[1] & ~0x10) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	verification_length = get_unaligned_be16(&common->cmnd[7]);
+	if (unlikely(verification_length == 0))
+		return -EIO;		/* No default reply */
+
+	/* Prepare to carry out the file verify */
+	amount_left = verification_length << curlun->blkbits;
+	file_offset = ((loff_t) lba) << curlun->blkbits;
+
+	/* Write out all the dirty buffers before invalidating them */
+	fsg_lun_fsync_sub(curlun);
+	if (signal_pending(current))
+		return -EINTR;
+
+	invalidate_sub(curlun);
+	if (signal_pending(current))
+		return -EINTR;
+
+	/* Just try to read the requested blocks */
+	while (amount_left > 0) {
+		/*
+		 * Figure out how much we need to read:
+		 * Try to read the remaining amount, but not more than
+		 * the buffer size.
+		 * And don't try to read past the end of the file.
+		 */
+		amount = min(amount_left, FSG_BUFLEN);
+		amount = min((loff_t)amount,
+			     curlun->file_length - file_offset);
+		if (amount == 0) {
+			curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+			curlun->sense_data_info =
+				file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+		/* Perform the read */
+		file_offset_tmp = file_offset;
+		nread = kernel_read(curlun->filp, bh->buf, amount,
+				&file_offset_tmp);
+		VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+				(unsigned long long) file_offset,
+				(int) nread);
+		if (signal_pending(current))
+			return -EINTR;
+
+		if (nread < 0) {
+			LDBG(curlun, "error in file verify: %d\n", (int)nread);
+			nread = 0;
+		} else if (nread < amount) {
+			LDBG(curlun, "partial file verify: %d/%u\n",
+			     (int)nread, amount);
+			nread = round_down(nread, curlun->blksize);
+		}
+		if (nread == 0) {
+			curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+			curlun->sense_data_info =
+				file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+		file_offset += nread;
+		amount_left -= nread;
+	}
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun *curlun = common->curlun;
+	u8	*buf = (u8 *) bh->buf;
+
+	if (!curlun) {		/* Unsupported LUNs are okay */
+		common->bad_lun_okay = 1;
+		memset(buf, 0, 36);
+		buf[0] = TYPE_NO_LUN;	/* Unsupported, no device-type */
+		buf[4] = 31;		/* Additional length */
+		return 36;
+	}
+
+	buf[0] = curlun->cdrom ? TYPE_ROM : TYPE_DISK;
+	buf[1] = curlun->removable ? 0x80 : 0;
+	buf[2] = 2;		/* ANSI SCSI level 2 */
+	buf[3] = 2;		/* SCSI-2 INQUIRY data format */
+	buf[4] = 31;		/* Additional length */
+	buf[5] = 0;		/* No special options */
+	buf[6] = 0;
+	buf[7] = 0;
+	if (curlun->inquiry_string[0])
+		memcpy(buf + 8, curlun->inquiry_string,
+		       sizeof(curlun->inquiry_string));
+	else
+		memcpy(buf + 8, common->inquiry_string,
+		       sizeof(common->inquiry_string));
+	return 36;
+}
+
+static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	u8		*buf = (u8 *) bh->buf;
+	u32		sd, sdinfo;
+	int		valid;
+
+	/*
+	 * From the SCSI-2 spec., section 7.9 (Unit attention condition):
+	 *
+	 * If a REQUEST SENSE command is received from an initiator
+	 * with a pending unit attention condition (before the target
+	 * generates the contingent allegiance condition), then the
+	 * target shall either:
+	 *   a) report any pending sense data and preserve the unit
+	 *	attention condition on the logical unit, or,
+	 *   b) report the unit attention condition, may discard any
+	 *	pending sense data, and clear the unit attention
+	 *	condition on the logical unit for that initiator.
+	 *
+	 * FSG normally uses option a); enable this code to use option b).
+	 */
+#if 0
+	if (curlun && curlun->unit_attention_data != SS_NO_SENSE) {
+		curlun->sense_data = curlun->unit_attention_data;
+		curlun->unit_attention_data = SS_NO_SENSE;
+	}
+#endif
+
+	if (!curlun) {		/* Unsupported LUNs are okay */
+		common->bad_lun_okay = 1;
+		sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+		sdinfo = 0;
+		valid = 0;
+	} else {
+		sd = curlun->sense_data;
+		sdinfo = curlun->sense_data_info;
+		valid = curlun->info_valid << 7;
+		curlun->sense_data = SS_NO_SENSE;
+		curlun->sense_data_info = 0;
+		curlun->info_valid = 0;
+	}
+
+	memset(buf, 0, 18);
+	buf[0] = valid | 0x70;			/* Valid, current error */
+	buf[2] = SK(sd);
+	put_unaligned_be32(sdinfo, &buf[3]);	/* Sense information */
+	buf[7] = 18 - 8;			/* Additional sense length */
+	buf[12] = ASC(sd);
+	buf[13] = ASCQ(sd);
+	return 18;
+}
+
+static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	u32		lba = get_unaligned_be32(&common->cmnd[2]);
+	int		pmi = common->cmnd[8];
+	u8		*buf = (u8 *)bh->buf;
+
+	/* Check the PMI and LBA fields */
+	if (pmi > 1 || (pmi == 0 && lba != 0)) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	put_unaligned_be32(curlun->num_sectors - 1, &buf[0]);
+						/* Max logical block */
+	put_unaligned_be32(curlun->blksize, &buf[4]);/* Block length */
+	return 8;
+}
+
+static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		msf = common->cmnd[1] & 0x02;
+	u32		lba = get_unaligned_be32(&common->cmnd[2]);
+	u8		*buf = (u8 *)bh->buf;
+
+	if (common->cmnd[1] & ~0x02) {		/* Mask away MSF */
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	memset(buf, 0, 8);
+	buf[0] = 0x01;		/* 2048 bytes of user data, rest is EC */
+	store_cdrom_address(&buf[4], msf, lba);
+	return 8;
+}
+
+static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		msf = common->cmnd[1] & 0x02;
+	int		start_track = common->cmnd[6];
+	u8		*buf = (u8 *)bh->buf;
+
+	if ((common->cmnd[1] & ~0x02) != 0 ||	/* Mask away MSF */
+			start_track > 1) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	memset(buf, 0, 20);
+	buf[1] = (20-2);		/* TOC data length */
+	buf[2] = 1;			/* First track number */
+	buf[3] = 1;			/* Last track number */
+	buf[5] = 0x16;			/* Data track, copying allowed */
+	buf[6] = 0x01;			/* Only track is number 1 */
+	store_cdrom_address(&buf[8], msf, 0);
+
+	buf[13] = 0x16;			/* Lead-out track is data */
+	buf[14] = 0xAA;			/* Lead-out track number */
+	store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+	return 20;
+}
+
+static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		mscmnd = common->cmnd[0];
+	u8		*buf = (u8 *) bh->buf;
+	u8		*buf0 = buf;
+	int		pc, page_code;
+	int		changeable_values, all_pages;
+	int		valid_page = 0;
+	int		len, limit;
+
+	if ((common->cmnd[1] & ~0x08) != 0) {	/* Mask away DBD */
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+	pc = common->cmnd[2] >> 6;
+	page_code = common->cmnd[2] & 0x3f;
+	if (pc == 3) {
+		curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED;
+		return -EINVAL;
+	}
+	changeable_values = (pc == 1);
+	all_pages = (page_code == 0x3f);
+
+	/*
+	 * Write the mode parameter header.  Fixed values are: default
+	 * medium type, no cache control (DPOFUA), and no block descriptors.
+	 * The only variable value is the WriteProtect bit.  We will fill in
+	 * the mode data length later.
+	 */
+	memset(buf, 0, 8);
+	if (mscmnd == MODE_SENSE) {
+		buf[2] = (curlun->ro ? 0x80 : 0x00);		/* WP, DPOFUA */
+		buf += 4;
+		limit = 255;
+	} else {			/* MODE_SENSE_10 */
+		buf[3] = (curlun->ro ? 0x80 : 0x00);		/* WP, DPOFUA */
+		buf += 8;
+		limit = 65535;		/* Should really be FSG_BUFLEN */
+	}
+
+	/* No block descriptors */
+
+	/*
+	 * The mode pages, in numerical order.  The only page we support
+	 * is the Caching page.
+	 */
+	if (page_code == 0x08 || all_pages) {
+		valid_page = 1;
+		buf[0] = 0x08;		/* Page code */
+		buf[1] = 10;		/* Page length */
+		memset(buf+2, 0, 10);	/* None of the fields are changeable */
+
+		if (!changeable_values) {
+			buf[2] = 0x04;	/* Write cache enable, */
+					/* Read cache not disabled */
+					/* No cache retention priorities */
+			put_unaligned_be16(0xffff, &buf[4]);
+					/* Don't disable prefetch */
+					/* Minimum prefetch = 0 */
+			put_unaligned_be16(0xffff, &buf[8]);
+					/* Maximum prefetch */
+			put_unaligned_be16(0xffff, &buf[10]);
+					/* Maximum prefetch ceiling */
+		}
+		buf += 12;
+	}
+
+	/*
+	 * Check that a valid page was requested and the mode data length
+	 * isn't too long.
+	 */
+	len = buf - buf0;
+	if (!valid_page || len > limit) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	/*  Store the mode data length */
+	if (mscmnd == MODE_SENSE)
+		buf0[0] = len - 1;
+	else
+		put_unaligned_be16(len - 2, buf0);
+	return len;
+}
+
+static int do_start_stop(struct fsg_common *common)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		loej, start;
+
+	if (!curlun) {
+		return -EINVAL;
+	} else if (!curlun->removable) {
+		curlun->sense_data = SS_INVALID_COMMAND;
+		return -EINVAL;
+	} else if ((common->cmnd[1] & ~0x01) != 0 || /* Mask away Immed */
+		   (common->cmnd[4] & ~0x03) != 0) { /* Mask LoEj, Start */
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	loej  = common->cmnd[4] & 0x02;
+	start = common->cmnd[4] & 0x01;
+
+	/*
+	 * Our emulation doesn't support mounting; the medium is
+	 * available for use as soon as it is loaded.
+	 */
+	if (start) {
+		if (!fsg_lun_is_open(curlun)) {
+			curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	/* Are we allowed to unload the media? */
+	if (curlun->prevent_medium_removal) {
+		LDBG(curlun, "unload attempt prevented\n");
+		curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED;
+		return -EINVAL;
+	}
+
+	if (!loej)
+		return 0;
+
+	up_read(&common->filesem);
+	down_write(&common->filesem);
+	fsg_lun_close(curlun);
+	up_write(&common->filesem);
+	down_read(&common->filesem);
+
+	return 0;
+}
+
+static int do_prevent_allow(struct fsg_common *common)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	int		prevent;
+
+	if (!common->curlun) {
+		return -EINVAL;
+	} else if (!common->curlun->removable) {
+		common->curlun->sense_data = SS_INVALID_COMMAND;
+		return -EINVAL;
+	}
+
+	prevent = common->cmnd[4] & 0x01;
+	if ((common->cmnd[4] & ~0x01) != 0) {	/* Mask away Prevent */
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	if (curlun->prevent_medium_removal && !prevent)
+		fsg_lun_fsync_sub(curlun);
+	curlun->prevent_medium_removal = prevent;
+	return 0;
+}
+
+static int do_read_format_capacities(struct fsg_common *common,
+			struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+	u8		*buf = (u8 *) bh->buf;
+
+	buf[0] = buf[1] = buf[2] = 0;
+	buf[3] = 8;	/* Only the Current/Maximum Capacity Descriptor */
+	buf += 4;
+
+	put_unaligned_be32(curlun->num_sectors, &buf[0]);
+						/* Number of blocks */
+	put_unaligned_be32(curlun->blksize, &buf[4]);/* Block length */
+	buf[4] = 0x02;				/* Current capacity */
+	return 12;
+}
+
+static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = common->curlun;
+
+	/* We don't support MODE SELECT */
+	if (curlun)
+		curlun->sense_data = SS_INVALID_COMMAND;
+	return -EINVAL;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int halt_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+	int	rc;
+
+	rc = fsg_set_halt(fsg, fsg->bulk_in);
+	if (rc == -EAGAIN)
+		VDBG(fsg, "delayed bulk-in endpoint halt\n");
+	while (rc != 0) {
+		if (rc != -EAGAIN) {
+			WARNING(fsg, "usb_ep_set_halt -> %d\n", rc);
+			rc = 0;
+			break;
+		}
+
+		/* Wait for a short time and then try again */
+		if (msleep_interruptible(100) != 0)
+			return -EINTR;
+		rc = usb_ep_set_halt(fsg->bulk_in);
+	}
+	return rc;
+}
+
+static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+	int	rc;
+
+	DBG(fsg, "bulk-in set wedge\n");
+	rc = usb_ep_set_wedge(fsg->bulk_in);
+	if (rc == -EAGAIN)
+		VDBG(fsg, "delayed bulk-in endpoint wedge\n");
+	while (rc != 0) {
+		if (rc != -EAGAIN) {
+			WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc);
+			rc = 0;
+			break;
+		}
+
+		/* Wait for a short time and then try again */
+		if (msleep_interruptible(100) != 0)
+			return -EINTR;
+		rc = usb_ep_set_wedge(fsg->bulk_in);
+	}
+	return rc;
+}
+
+static int throw_away_data(struct fsg_common *common)
+{
+	struct fsg_buffhd	*bh, *bh2;
+	u32			amount;
+	int			rc;
+
+	for (bh = common->next_buffhd_to_drain;
+	     bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
+	     bh = common->next_buffhd_to_drain) {
+
+		/* Try to submit another request if we need one */
+		bh2 = common->next_buffhd_to_fill;
+		if (bh2->state == BUF_STATE_EMPTY &&
+				common->usb_amount_left > 0) {
+			amount = min(common->usb_amount_left, FSG_BUFLEN);
+
+			/*
+			 * Except at the end of the transfer, amount will be
+			 * equal to the buffer size, which is divisible by
+			 * the bulk-out maxpacket size.
+			 */
+			set_bulk_out_req_length(common, bh2, amount);
+			if (!start_out_transfer(common, bh2))
+				/* Dunno what to do if common->fsg is NULL */
+				return -EIO;
+			common->next_buffhd_to_fill = bh2->next;
+			common->usb_amount_left -= amount;
+			continue;
+		}
+
+		/* Wait for the data to be received */
+		rc = sleep_thread(common, false, bh);
+		if (rc)
+			return rc;
+
+		/* Throw away the data in a filled buffer */
+		bh->state = BUF_STATE_EMPTY;
+		common->next_buffhd_to_drain = bh->next;
+
+		/* A short packet or an error ends everything */
+		if (bh->outreq->actual < bh->bulk_out_intended_length ||
+				bh->outreq->status != 0) {
+			raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+			return -EINTR;
+		}
+	}
+	return 0;
+}
+
+static int finish_reply(struct fsg_common *common)
+{
+	struct fsg_buffhd	*bh = common->next_buffhd_to_fill;
+	int			rc = 0;
+
+	switch (common->data_dir) {
+	case DATA_DIR_NONE:
+		break;			/* Nothing to send */
+
+	/*
+	 * If we don't know whether the host wants to read or write,
+	 * this must be CB or CBI with an unknown command.  We mustn't
+	 * try to send or receive any data.  So stall both bulk pipes
+	 * if we can and wait for a reset.
+	 */
+	case DATA_DIR_UNKNOWN:
+		if (!common->can_stall) {
+			/* Nothing */
+		} else if (fsg_is_set(common)) {
+			fsg_set_halt(common->fsg, common->fsg->bulk_out);
+			rc = halt_bulk_in_endpoint(common->fsg);
+		} else {
+			/* Don't know what to do if common->fsg is NULL */
+			rc = -EIO;
+		}
+		break;
+
+	/* All but the last buffer of data must have already been sent */
+	case DATA_DIR_TO_HOST:
+		if (common->data_size == 0) {
+			/* Nothing to send */
+
+		/* Don't know what to do if common->fsg is NULL */
+		} else if (!fsg_is_set(common)) {
+			rc = -EIO;
+
+		/* If there's no residue, simply send the last buffer */
+		} else if (common->residue == 0) {
+			bh->inreq->zero = 0;
+			if (!start_in_transfer(common, bh))
+				return -EIO;
+			common->next_buffhd_to_fill = bh->next;
+
+		/*
+		 * For Bulk-only, mark the end of the data with a short
+		 * packet.  If we are allowed to stall, halt the bulk-in
+		 * endpoint.  (Note: This violates the Bulk-Only Transport
+		 * specification, which requires us to pad the data if we
+		 * don't halt the endpoint.  Presumably nobody will mind.)
+		 */
+		} else {
+			bh->inreq->zero = 1;
+			if (!start_in_transfer(common, bh))
+				rc = -EIO;
+			common->next_buffhd_to_fill = bh->next;
+			if (common->can_stall)
+				rc = halt_bulk_in_endpoint(common->fsg);
+		}
+		break;
+
+	/*
+	 * We have processed all we want from the data the host has sent.
+	 * There may still be outstanding bulk-out requests.
+	 */
+	case DATA_DIR_FROM_HOST:
+		if (common->residue == 0) {
+			/* Nothing to receive */
+
+		/* Did the host stop sending unexpectedly early? */
+		} else if (common->short_packet_received) {
+			raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+			rc = -EINTR;
+
+		/*
+		 * We haven't processed all the incoming data.  Even though
+		 * we may be allowed to stall, doing so would cause a race.
+		 * The controller may already have ACK'ed all the remaining
+		 * bulk-out packets, in which case the host wouldn't see a
+		 * STALL.  Not realizing the endpoint was halted, it wouldn't
+		 * clear the halt -- leading to problems later on.
+		 */
+#if 0
+		} else if (common->can_stall) {
+			if (fsg_is_set(common))
+				fsg_set_halt(common->fsg,
+					     common->fsg->bulk_out);
+			raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+			rc = -EINTR;
+#endif
+
+		/*
+		 * We can't stall.  Read in the excess data and throw it
+		 * all away.
+		 */
+		} else {
+			rc = throw_away_data(common);
+		}
+		break;
+	}
+	return rc;
+}
+
+static void send_status(struct fsg_common *common)
+{
+	struct fsg_lun		*curlun = common->curlun;
+	struct fsg_buffhd	*bh;
+	struct bulk_cs_wrap	*csw;
+	int			rc;
+	u8			status = US_BULK_STAT_OK;
+	u32			sd, sdinfo = 0;
+
+	/* Wait for the next buffer to become available */
+	bh = common->next_buffhd_to_fill;
+	rc = sleep_thread(common, false, bh);
+	if (rc)
+		return;
+
+	if (curlun) {
+		sd = curlun->sense_data;
+		sdinfo = curlun->sense_data_info;
+	} else if (common->bad_lun_okay)
+		sd = SS_NO_SENSE;
+	else
+		sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+
+	if (common->phase_error) {
+		DBG(common, "sending phase-error status\n");
+		status = US_BULK_STAT_PHASE;
+		sd = SS_INVALID_COMMAND;
+	} else if (sd != SS_NO_SENSE) {
+		DBG(common, "sending command-failure status\n");
+		status = US_BULK_STAT_FAIL;
+		VDBG(common, "  sense data: SK x%02x, ASC x%02x, ASCQ x%02x;"
+				"  info x%x\n",
+				SK(sd), ASC(sd), ASCQ(sd), sdinfo);
+	}
+
+	/* Store and send the Bulk-only CSW */
+	csw = (void *)bh->buf;
+
+	csw->Signature = cpu_to_le32(US_BULK_CS_SIGN);
+	csw->Tag = common->tag;
+	csw->Residue = cpu_to_le32(common->residue);
+	csw->Status = status;
+
+	bh->inreq->length = US_BULK_CS_WRAP_LEN;
+	bh->inreq->zero = 0;
+	if (!start_in_transfer(common, bh))
+		/* Don't know what to do if common->fsg is NULL */
+		return;
+
+	common->next_buffhd_to_fill = bh->next;
+	return;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Check whether the command is properly formed and whether its data size
+ * and direction agree with the values we already have.
+ */
+static int check_command(struct fsg_common *common, int cmnd_size,
+			 enum data_direction data_dir, unsigned int mask,
+			 int needs_medium, const char *name)
+{
+	int			i;
+	unsigned int		lun = common->cmnd[1] >> 5;
+	static const char	dirletter[4] = {'u', 'o', 'i', 'n'};
+	char			hdlen[20];
+	struct fsg_lun		*curlun;
+
+	hdlen[0] = 0;
+	if (common->data_dir != DATA_DIR_UNKNOWN)
+		sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir],
+			common->data_size);
+	VDBG(common, "SCSI command: %s;  Dc=%d, D%c=%u;  Hc=%d%s\n",
+	     name, cmnd_size, dirletter[(int) data_dir],
+	     common->data_size_from_cmnd, common->cmnd_size, hdlen);
+
+	/*
+	 * We can't reply at all until we know the correct data direction
+	 * and size.
+	 */
+	if (common->data_size_from_cmnd == 0)
+		data_dir = DATA_DIR_NONE;
+	if (common->data_size < common->data_size_from_cmnd) {
+		/*
+		 * Host data size < Device data size is a phase error.
+		 * Carry out the command, but only transfer as much as
+		 * we are allowed.
+		 */
+		common->data_size_from_cmnd = common->data_size;
+		common->phase_error = 1;
+	}
+	common->residue = common->data_size;
+	common->usb_amount_left = common->data_size;
+
+	/* Conflicting data directions is a phase error */
+	if (common->data_dir != data_dir && common->data_size_from_cmnd > 0) {
+		common->phase_error = 1;
+		return -EINVAL;
+	}
+
+	/* Verify the length of the command itself */
+	if (cmnd_size != common->cmnd_size) {
+
+		/*
+		 * Special case workaround: There are plenty of buggy SCSI
+		 * implementations. Many have issues with cbw->Length
+		 * field passing a wrong command size. For those cases we
+		 * always try to work around the problem by using the length
+		 * sent by the host side provided it is at least as large
+		 * as the correct command length.
+		 * Examples of such cases would be MS-Windows, which issues
+		 * REQUEST SENSE with cbw->Length == 12 where it should
+		 * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and
+		 * REQUEST SENSE with cbw->Length == 10 where it should
+		 * be 6 as well.
+		 */
+		if (cmnd_size <= common->cmnd_size) {
+			DBG(common, "%s is buggy! Expected length %d "
+			    "but we got %d\n", name,
+			    cmnd_size, common->cmnd_size);
+			cmnd_size = common->cmnd_size;
+		} else {
+			common->phase_error = 1;
+			return -EINVAL;
+		}
+	}
+
+	/* Check that the LUN values are consistent */
+	if (common->lun != lun)
+		DBG(common, "using LUN %u from CBW, not LUN %u from CDB\n",
+		    common->lun, lun);
+
+	/* Check the LUN */
+	curlun = common->curlun;
+	if (curlun) {
+		if (common->cmnd[0] != REQUEST_SENSE) {
+			curlun->sense_data = SS_NO_SENSE;
+			curlun->sense_data_info = 0;
+			curlun->info_valid = 0;
+		}
+	} else {
+		common->bad_lun_okay = 0;
+
+		/*
+		 * INQUIRY and REQUEST SENSE commands are explicitly allowed
+		 * to use unsupported LUNs; all others may not.
+		 */
+		if (common->cmnd[0] != INQUIRY &&
+		    common->cmnd[0] != REQUEST_SENSE) {
+			DBG(common, "unsupported LUN %u\n", common->lun);
+			return -EINVAL;
+		}
+	}
+
+	/*
+	 * If a unit attention condition exists, only INQUIRY and
+	 * REQUEST SENSE commands are allowed; anything else must fail.
+	 */
+	if (curlun && curlun->unit_attention_data != SS_NO_SENSE &&
+	    common->cmnd[0] != INQUIRY &&
+	    common->cmnd[0] != REQUEST_SENSE) {
+		curlun->sense_data = curlun->unit_attention_data;
+		curlun->unit_attention_data = SS_NO_SENSE;
+		return -EINVAL;
+	}
+
+	/* Check that only command bytes listed in the mask are non-zero */
+	common->cmnd[1] &= 0x1f;			/* Mask away the LUN */
+	for (i = 1; i < cmnd_size; ++i) {
+		if (common->cmnd[i] && !(mask & (1 << i))) {
+			if (curlun)
+				curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+	}
+
+	/* If the medium isn't mounted and the command needs to access
+	 * it, return an error. */
+	if (curlun && !fsg_lun_is_open(curlun) && needs_medium) {
+		curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* wrapper of check_command for data size in blocks handling */
+static int check_command_size_in_blocks(struct fsg_common *common,
+		int cmnd_size, enum data_direction data_dir,
+		unsigned int mask, int needs_medium, const char *name)
+{
+	if (common->curlun)
+		common->data_size_from_cmnd <<= common->curlun->blkbits;
+	return check_command(common, cmnd_size, data_dir,
+			mask, needs_medium, name);
+}
+
+static int do_scsi_command(struct fsg_common *common)
+{
+	struct fsg_buffhd	*bh;
+	int			rc;
+	int			reply = -EINVAL;
+	int			i;
+	static char		unknown[16];
+
+	dump_cdb(common);
+
+	/* Wait for the next buffer to become available for data or status */
+	bh = common->next_buffhd_to_fill;
+	common->next_buffhd_to_drain = bh;
+	rc = sleep_thread(common, false, bh);
+	if (rc)
+		return rc;
+
+	common->phase_error = 0;
+	common->short_packet_received = 0;
+
+	down_read(&common->filesem);	/* We're using the backing file */
+	switch (common->cmnd[0]) {
+
+	case INQUIRY:
+		common->data_size_from_cmnd = common->cmnd[4];
+		reply = check_command(common, 6, DATA_DIR_TO_HOST,
+				      (1<<4), 0,
+				      "INQUIRY");
+		if (reply == 0)
+			reply = do_inquiry(common, bh);
+		break;
+
+	case MODE_SELECT:
+		common->data_size_from_cmnd = common->cmnd[4];
+		reply = check_command(common, 6, DATA_DIR_FROM_HOST,
+				      (1<<1) | (1<<4), 0,
+				      "MODE SELECT(6)");
+		if (reply == 0)
+			reply = do_mode_select(common, bh);
+		break;
+
+	case MODE_SELECT_10:
+		common->data_size_from_cmnd =
+			get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command(common, 10, DATA_DIR_FROM_HOST,
+				      (1<<1) | (3<<7), 0,
+				      "MODE SELECT(10)");
+		if (reply == 0)
+			reply = do_mode_select(common, bh);
+		break;
+
+	case MODE_SENSE:
+		common->data_size_from_cmnd = common->cmnd[4];
+		reply = check_command(common, 6, DATA_DIR_TO_HOST,
+				      (1<<1) | (1<<2) | (1<<4), 0,
+				      "MODE SENSE(6)");
+		if (reply == 0)
+			reply = do_mode_sense(common, bh);
+		break;
+
+	case MODE_SENSE_10:
+		common->data_size_from_cmnd =
+			get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command(common, 10, DATA_DIR_TO_HOST,
+				      (1<<1) | (1<<2) | (3<<7), 0,
+				      "MODE SENSE(10)");
+		if (reply == 0)
+			reply = do_mode_sense(common, bh);
+		break;
+
+	case ALLOW_MEDIUM_REMOVAL:
+		common->data_size_from_cmnd = 0;
+		reply = check_command(common, 6, DATA_DIR_NONE,
+				      (1<<4), 0,
+				      "PREVENT-ALLOW MEDIUM REMOVAL");
+		if (reply == 0)
+			reply = do_prevent_allow(common);
+		break;
+
+	case READ_6:
+		i = common->cmnd[4];
+		common->data_size_from_cmnd = (i == 0) ? 256 : i;
+		reply = check_command_size_in_blocks(common, 6,
+				      DATA_DIR_TO_HOST,
+				      (7<<1) | (1<<4), 1,
+				      "READ(6)");
+		if (reply == 0)
+			reply = do_read(common);
+		break;
+
+	case READ_10:
+		common->data_size_from_cmnd =
+				get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command_size_in_blocks(common, 10,
+				      DATA_DIR_TO_HOST,
+				      (1<<1) | (0xf<<2) | (3<<7), 1,
+				      "READ(10)");
+		if (reply == 0)
+			reply = do_read(common);
+		break;
+
+	case READ_12:
+		common->data_size_from_cmnd =
+				get_unaligned_be32(&common->cmnd[6]);
+		reply = check_command_size_in_blocks(common, 12,
+				      DATA_DIR_TO_HOST,
+				      (1<<1) | (0xf<<2) | (0xf<<6), 1,
+				      "READ(12)");
+		if (reply == 0)
+			reply = do_read(common);
+		break;
+
+	case READ_CAPACITY:
+		common->data_size_from_cmnd = 8;
+		reply = check_command(common, 10, DATA_DIR_TO_HOST,
+				      (0xf<<2) | (1<<8), 1,
+				      "READ CAPACITY");
+		if (reply == 0)
+			reply = do_read_capacity(common, bh);
+		break;
+
+	case READ_HEADER:
+		if (!common->curlun || !common->curlun->cdrom)
+			goto unknown_cmnd;
+		common->data_size_from_cmnd =
+			get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command(common, 10, DATA_DIR_TO_HOST,
+				      (3<<7) | (0x1f<<1), 1,
+				      "READ HEADER");
+		if (reply == 0)
+			reply = do_read_header(common, bh);
+		break;
+
+	case READ_TOC:
+		if (!common->curlun || !common->curlun->cdrom)
+			goto unknown_cmnd;
+		common->data_size_from_cmnd =
+			get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command(common, 10, DATA_DIR_TO_HOST,
+				      (7<<6) | (1<<1), 1,
+				      "READ TOC");
+		if (reply == 0)
+			reply = do_read_toc(common, bh);
+		break;
+
+	case READ_FORMAT_CAPACITIES:
+		common->data_size_from_cmnd =
+			get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command(common, 10, DATA_DIR_TO_HOST,
+				      (3<<7), 1,
+				      "READ FORMAT CAPACITIES");
+		if (reply == 0)
+			reply = do_read_format_capacities(common, bh);
+		break;
+
+	case REQUEST_SENSE:
+		common->data_size_from_cmnd = common->cmnd[4];
+		reply = check_command(common, 6, DATA_DIR_TO_HOST,
+				      (1<<4), 0,
+				      "REQUEST SENSE");
+		if (reply == 0)
+			reply = do_request_sense(common, bh);
+		break;
+
+	case START_STOP:
+		common->data_size_from_cmnd = 0;
+		reply = check_command(common, 6, DATA_DIR_NONE,
+				      (1<<1) | (1<<4), 0,
+				      "START-STOP UNIT");
+		if (reply == 0)
+			reply = do_start_stop(common);
+		break;
+
+	case SYNCHRONIZE_CACHE:
+		common->data_size_from_cmnd = 0;
+		reply = check_command(common, 10, DATA_DIR_NONE,
+				      (0xf<<2) | (3<<7), 1,
+				      "SYNCHRONIZE CACHE");
+		if (reply == 0)
+			reply = do_synchronize_cache(common);
+		break;
+
+	case TEST_UNIT_READY:
+		common->data_size_from_cmnd = 0;
+		reply = check_command(common, 6, DATA_DIR_NONE,
+				0, 1,
+				"TEST UNIT READY");
+		break;
+
+	/*
+	 * Although optional, this command is used by MS-Windows.  We
+	 * support a minimal version: BytChk must be 0.
+	 */
+	case VERIFY:
+		common->data_size_from_cmnd = 0;
+		reply = check_command(common, 10, DATA_DIR_NONE,
+				      (1<<1) | (0xf<<2) | (3<<7), 1,
+				      "VERIFY");
+		if (reply == 0)
+			reply = do_verify(common);
+		break;
+
+	case WRITE_6:
+		i = common->cmnd[4];
+		common->data_size_from_cmnd = (i == 0) ? 256 : i;
+		reply = check_command_size_in_blocks(common, 6,
+				      DATA_DIR_FROM_HOST,
+				      (7<<1) | (1<<4), 1,
+				      "WRITE(6)");
+		if (reply == 0)
+			reply = do_write(common);
+		break;
+
+	case WRITE_10:
+		common->data_size_from_cmnd =
+				get_unaligned_be16(&common->cmnd[7]);
+		reply = check_command_size_in_blocks(common, 10,
+				      DATA_DIR_FROM_HOST,
+				      (1<<1) | (0xf<<2) | (3<<7), 1,
+				      "WRITE(10)");
+		if (reply == 0)
+			reply = do_write(common);
+		break;
+
+	case WRITE_12:
+		common->data_size_from_cmnd =
+				get_unaligned_be32(&common->cmnd[6]);
+		reply = check_command_size_in_blocks(common, 12,
+				      DATA_DIR_FROM_HOST,
+				      (1<<1) | (0xf<<2) | (0xf<<6), 1,
+				      "WRITE(12)");
+		if (reply == 0)
+			reply = do_write(common);
+		break;
+
+	/*
+	 * Some mandatory commands that we recognize but don't implement.
+	 * They don't mean much in this setting.  It's left as an exercise
+	 * for anyone interested to implement RESERVE and RELEASE in terms
+	 * of Posix locks.
+	 */
+	case FORMAT_UNIT:
+	case RELEASE:
+	case RESERVE:
+	case SEND_DIAGNOSTIC:
+		/* Fall through */
+
+	default:
+unknown_cmnd:
+		common->data_size_from_cmnd = 0;
+		sprintf(unknown, "Unknown x%02x", common->cmnd[0]);
+		reply = check_command(common, common->cmnd_size,
+				      DATA_DIR_UNKNOWN, ~0, 0, unknown);
+		if (reply == 0) {
+			common->curlun->sense_data = SS_INVALID_COMMAND;
+			reply = -EINVAL;
+		}
+		break;
+	}
+	up_read(&common->filesem);
+
+	if (reply == -EINTR || signal_pending(current))
+		return -EINTR;
+
+	/* Set up the single reply buffer for finish_reply() */
+	if (reply == -EINVAL)
+		reply = 0;		/* Error reply length */
+	if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) {
+		reply = min((u32)reply, common->data_size_from_cmnd);
+		bh->inreq->length = reply;
+		bh->state = BUF_STATE_FULL;
+		common->residue -= reply;
+	}				/* Otherwise it's already set */
+
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct usb_request	*req = bh->outreq;
+	struct bulk_cb_wrap	*cbw = req->buf;
+	struct fsg_common	*common = fsg->common;
+
+	/* Was this a real packet?  Should it be ignored? */
+	if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+		return -EINVAL;
+
+	/* Is the CBW valid? */
+	if (req->actual != US_BULK_CB_WRAP_LEN ||
+			cbw->Signature != cpu_to_le32(
+				US_BULK_CB_SIGN)) {
+		DBG(fsg, "invalid CBW: len %u sig 0x%x\n",
+				req->actual,
+				le32_to_cpu(cbw->Signature));
+
+		/*
+		 * The Bulk-only spec says we MUST stall the IN endpoint
+		 * (6.6.1), so it's unavoidable.  It also says we must
+		 * retain this state until the next reset, but there's
+		 * no way to tell the controller driver it should ignore
+		 * Clear-Feature(HALT) requests.
+		 *
+		 * We aren't required to halt the OUT endpoint; instead
+		 * we can simply accept and discard any data received
+		 * until the next reset.
+		 */
+		wedge_bulk_in_endpoint(fsg);
+		set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+		return -EINVAL;
+	}
+
+	/* Is the CBW meaningful? */
+	if (cbw->Lun >= ARRAY_SIZE(common->luns) ||
+	    cbw->Flags & ~US_BULK_FLAG_IN || cbw->Length <= 0 ||
+	    cbw->Length > MAX_COMMAND_SIZE) {
+		DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, "
+				"cmdlen %u\n",
+				cbw->Lun, cbw->Flags, cbw->Length);
+
+		/*
+		 * We can do anything we want here, so let's stall the
+		 * bulk pipes if we are allowed to.
+		 */
+		if (common->can_stall) {
+			fsg_set_halt(fsg, fsg->bulk_out);
+			halt_bulk_in_endpoint(fsg);
+		}
+		return -EINVAL;
+	}
+
+	/* Save the command for later */
+	common->cmnd_size = cbw->Length;
+	memcpy(common->cmnd, cbw->CDB, common->cmnd_size);
+	if (cbw->Flags & US_BULK_FLAG_IN)
+		common->data_dir = DATA_DIR_TO_HOST;
+	else
+		common->data_dir = DATA_DIR_FROM_HOST;
+	common->data_size = le32_to_cpu(cbw->DataTransferLength);
+	if (common->data_size == 0)
+		common->data_dir = DATA_DIR_NONE;
+	common->lun = cbw->Lun;
+	if (common->lun < ARRAY_SIZE(common->luns))
+		common->curlun = common->luns[common->lun];
+	else
+		common->curlun = NULL;
+	common->tag = cbw->Tag;
+	return 0;
+}
+
+static int get_next_command(struct fsg_common *common)
+{
+	struct fsg_buffhd	*bh;
+	int			rc = 0;
+
+	/* Wait for the next buffer to become available */
+	bh = common->next_buffhd_to_fill;
+	rc = sleep_thread(common, true, bh);
+	if (rc)
+		return rc;
+
+	/* Queue a request to read a Bulk-only CBW */
+	set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN);
+	if (!start_out_transfer(common, bh))
+		/* Don't know what to do if common->fsg is NULL */
+		return -EIO;
+
+	/*
+	 * We will drain the buffer in software, which means we
+	 * can reuse it for the next filling.  No need to advance
+	 * next_buffhd_to_fill.
+	 */
+
+	/* Wait for the CBW to arrive */
+	rc = sleep_thread(common, true, bh);
+	if (rc)
+		return rc;
+
+	rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
+	bh->state = BUF_STATE_EMPTY;
+
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int alloc_request(struct fsg_common *common, struct usb_ep *ep,
+		struct usb_request **preq)
+{
+	*preq = usb_ep_alloc_request(ep, GFP_ATOMIC);
+	if (*preq)
+		return 0;
+	ERROR(common, "can't allocate request for %s\n", ep->name);
+	return -ENOMEM;
+}
+
+/* Reset interface setting and re-init endpoint state (toggle etc). */
+static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg)
+{
+	struct fsg_dev *fsg;
+	int i, rc = 0;
+
+	if (common->running)
+		DBG(common, "reset interface\n");
+
+reset:
+	/* Deallocate the requests */
+	if (common->fsg) {
+		fsg = common->fsg;
+
+		for (i = 0; i < common->fsg_num_buffers; ++i) {
+			struct fsg_buffhd *bh = &common->buffhds[i];
+
+			if (bh->inreq) {
+				usb_ep_free_request(fsg->bulk_in, bh->inreq);
+				bh->inreq = NULL;
+			}
+			if (bh->outreq) {
+				usb_ep_free_request(fsg->bulk_out, bh->outreq);
+				bh->outreq = NULL;
+			}
+		}
+
+		/* Disable the endpoints */
+		if (fsg->bulk_in_enabled) {
+			usb_ep_disable(fsg->bulk_in);
+			fsg->bulk_in_enabled = 0;
+		}
+		if (fsg->bulk_out_enabled) {
+			usb_ep_disable(fsg->bulk_out);
+			fsg->bulk_out_enabled = 0;
+		}
+
+		common->fsg = NULL;
+		wake_up(&common->fsg_wait);
+	}
+
+	common->running = 0;
+	if (!new_fsg || rc)
+		return rc;
+
+	common->fsg = new_fsg;
+	fsg = common->fsg;
+
+	/* Enable the endpoints */
+	rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in);
+	if (rc)
+		goto reset;
+	rc = usb_ep_enable(fsg->bulk_in);
+	if (rc)
+		goto reset;
+	fsg->bulk_in->driver_data = common;
+	fsg->bulk_in_enabled = 1;
+
+	rc = config_ep_by_speed(common->gadget, &(fsg->function),
+				fsg->bulk_out);
+	if (rc)
+		goto reset;
+	rc = usb_ep_enable(fsg->bulk_out);
+	if (rc)
+		goto reset;
+	fsg->bulk_out->driver_data = common;
+	fsg->bulk_out_enabled = 1;
+	common->bulk_out_maxpacket = usb_endpoint_maxp(fsg->bulk_out->desc);
+	clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+
+	/* Allocate the requests */
+	for (i = 0; i < common->fsg_num_buffers; ++i) {
+		struct fsg_buffhd	*bh = &common->buffhds[i];
+
+		rc = alloc_request(common, fsg->bulk_in, &bh->inreq);
+		if (rc)
+			goto reset;
+		rc = alloc_request(common, fsg->bulk_out, &bh->outreq);
+		if (rc)
+			goto reset;
+		bh->inreq->buf = bh->outreq->buf = bh->buf;
+		bh->inreq->context = bh->outreq->context = bh;
+		bh->inreq->complete = bulk_in_complete;
+		bh->outreq->complete = bulk_out_complete;
+	}
+
+	common->running = 1;
+	for (i = 0; i < ARRAY_SIZE(common->luns); ++i)
+		if (common->luns[i])
+			common->luns[i]->unit_attention_data =
+				SS_RESET_OCCURRED;
+	return rc;
+}
+
+
+/****************************** ALT CONFIGS ******************************/
+
+static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct fsg_dev *fsg = fsg_from_func(f);
+
+	__raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, fsg);
+	return USB_GADGET_DELAYED_STATUS;
+}
+
+static void fsg_disable(struct usb_function *f)
+{
+	struct fsg_dev *fsg = fsg_from_func(f);
+
+	__raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, NULL);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void handle_exception(struct fsg_common *common)
+{
+	int			i;
+	struct fsg_buffhd	*bh;
+	enum fsg_state		old_state;
+	struct fsg_lun		*curlun;
+	unsigned int		exception_req_tag;
+	struct fsg_dev		*new_fsg;
+
+	/*
+	 * Clear the existing signals.  Anything but SIGUSR1 is converted
+	 * into a high-priority EXIT exception.
+	 */
+	for (;;) {
+		int sig = kernel_dequeue_signal();
+		if (!sig)
+			break;
+		if (sig != SIGUSR1) {
+			spin_lock_irq(&common->lock);
+			if (common->state < FSG_STATE_EXIT)
+				DBG(common, "Main thread exiting on signal\n");
+			common->state = FSG_STATE_EXIT;
+			spin_unlock_irq(&common->lock);
+		}
+	}
+
+	/* Cancel all the pending transfers */
+	if (likely(common->fsg)) {
+		for (i = 0; i < common->fsg_num_buffers; ++i) {
+			bh = &common->buffhds[i];
+			if (bh->state == BUF_STATE_SENDING)
+				usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
+			if (bh->state == BUF_STATE_RECEIVING)
+				usb_ep_dequeue(common->fsg->bulk_out,
+					       bh->outreq);
+
+			/* Wait for a transfer to become idle */
+			if (sleep_thread(common, false, bh))
+				return;
+		}
+
+		/* Clear out the controller's fifos */
+		if (common->fsg->bulk_in_enabled)
+			usb_ep_fifo_flush(common->fsg->bulk_in);
+		if (common->fsg->bulk_out_enabled)
+			usb_ep_fifo_flush(common->fsg->bulk_out);
+	}
+
+	/*
+	 * Reset the I/O buffer states and pointers, the SCSI
+	 * state, and the exception.  Then invoke the handler.
+	 */
+	spin_lock_irq(&common->lock);
+
+	for (i = 0; i < common->fsg_num_buffers; ++i) {
+		bh = &common->buffhds[i];
+		bh->state = BUF_STATE_EMPTY;
+	}
+	common->next_buffhd_to_fill = &common->buffhds[0];
+	common->next_buffhd_to_drain = &common->buffhds[0];
+	exception_req_tag = common->exception_req_tag;
+	new_fsg = common->exception_arg;
+	old_state = common->state;
+	common->state = FSG_STATE_NORMAL;
+
+	if (old_state != FSG_STATE_ABORT_BULK_OUT) {
+		for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
+			curlun = common->luns[i];
+			if (!curlun)
+				continue;
+			curlun->prevent_medium_removal = 0;
+			curlun->sense_data = SS_NO_SENSE;
+			curlun->unit_attention_data = SS_NO_SENSE;
+			curlun->sense_data_info = 0;
+			curlun->info_valid = 0;
+		}
+	}
+	spin_unlock_irq(&common->lock);
+
+	/* Carry out any extra actions required for the exception */
+	switch (old_state) {
+	case FSG_STATE_NORMAL:
+		break;
+
+	case FSG_STATE_ABORT_BULK_OUT:
+		send_status(common);
+		break;
+
+	case FSG_STATE_PROTOCOL_RESET:
+		/*
+		 * In case we were forced against our will to halt a
+		 * bulk endpoint, clear the halt now.  (The SuperH UDC
+		 * requires this.)
+		 */
+		if (!fsg_is_set(common))
+			break;
+		if (test_and_clear_bit(IGNORE_BULK_OUT,
+				       &common->fsg->atomic_bitflags))
+			usb_ep_clear_halt(common->fsg->bulk_in);
+
+		if (common->ep0_req_tag == exception_req_tag)
+			ep0_queue(common);	/* Complete the status stage */
+
+		/*
+		 * Technically this should go here, but it would only be
+		 * a waste of time.  Ditto for the INTERFACE_CHANGE and
+		 * CONFIG_CHANGE cases.
+		 */
+		/* for (i = 0; i < common->ARRAY_SIZE(common->luns); ++i) */
+		/*	if (common->luns[i]) */
+		/*		common->luns[i]->unit_attention_data = */
+		/*			SS_RESET_OCCURRED;  */
+		break;
+
+	case FSG_STATE_CONFIG_CHANGE:
+		do_set_interface(common, new_fsg);
+		if (new_fsg)
+			usb_composite_setup_continue(common->cdev);
+		break;
+
+	case FSG_STATE_EXIT:
+		do_set_interface(common, NULL);		/* Free resources */
+		spin_lock_irq(&common->lock);
+		common->state = FSG_STATE_TERMINATED;	/* Stop the thread */
+		spin_unlock_irq(&common->lock);
+		break;
+
+	case FSG_STATE_TERMINATED:
+		break;
+	}
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_main_thread(void *common_)
+{
+	struct fsg_common	*common = common_;
+	int			i;
+
+	/*
+	 * Allow the thread to be killed by a signal, but set the signal mask
+	 * to block everything but INT, TERM, KILL, and USR1.
+	 */
+	allow_signal(SIGINT);
+	allow_signal(SIGTERM);
+	allow_signal(SIGKILL);
+	allow_signal(SIGUSR1);
+
+	/* Allow the thread to be frozen */
+	set_freezable();
+
+	/* The main loop */
+	while (common->state != FSG_STATE_TERMINATED) {
+		if (exception_in_progress(common) || signal_pending(current)) {
+			handle_exception(common);
+			continue;
+		}
+
+		if (!common->running) {
+			sleep_thread(common, true, NULL);
+			continue;
+		}
+
+		if (get_next_command(common) || exception_in_progress(common))
+			continue;
+		if (do_scsi_command(common) || exception_in_progress(common))
+			continue;
+		if (finish_reply(common) || exception_in_progress(common))
+			continue;
+		send_status(common);
+	}
+
+	spin_lock_irq(&common->lock);
+	common->thread_task = NULL;
+	spin_unlock_irq(&common->lock);
+
+	/* Eject media from all LUNs */
+
+	down_write(&common->filesem);
+	for (i = 0; i < ARRAY_SIZE(common->luns); i++) {
+		struct fsg_lun *curlun = common->luns[i];
+
+		if (curlun && fsg_lun_is_open(curlun))
+			fsg_lun_close(curlun);
+	}
+	up_write(&common->filesem);
+
+	/* Let fsg_unbind() know the thread has exited */
+	complete_and_exit(&common->thread_notifier, 0);
+}
+
+
+/*************************** DEVICE ATTRIBUTES ***************************/
+
+static ssize_t ro_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+
+	return fsg_show_ro(curlun, buf);
+}
+
+static ssize_t nofua_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+
+	return fsg_show_nofua(curlun, buf);
+}
+
+static ssize_t file_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+	struct rw_semaphore	*filesem = dev_get_drvdata(dev);
+
+	return fsg_show_file(curlun, filesem, buf);
+}
+
+static ssize_t ro_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+	struct rw_semaphore	*filesem = dev_get_drvdata(dev);
+
+	return fsg_store_ro(curlun, filesem, buf, count);
+}
+
+static ssize_t nofua_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+
+	return fsg_store_nofua(curlun, buf, count);
+}
+
+static ssize_t file_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct fsg_lun		*curlun = fsg_lun_from_dev(dev);
+	struct rw_semaphore	*filesem = dev_get_drvdata(dev);
+
+	return fsg_store_file(curlun, filesem, buf, count);
+}
+
+static DEVICE_ATTR_RW(nofua);
+/* mode wil be set in fsg_lun_attr_is_visible() */
+static DEVICE_ATTR(ro, 0, ro_show, ro_store);
+static DEVICE_ATTR(file, 0, file_show, file_store);
+
+/****************************** FSG COMMON ******************************/
+
+static void fsg_lun_release(struct device *dev)
+{
+	/* Nothing needs to be done */
+}
+
+static struct fsg_common *fsg_common_setup(struct fsg_common *common)
+{
+	if (!common) {
+		common = kzalloc(sizeof(*common), GFP_KERNEL);
+		if (!common)
+			return ERR_PTR(-ENOMEM);
+		common->free_storage_on_release = 1;
+	} else {
+		common->free_storage_on_release = 0;
+	}
+	init_rwsem(&common->filesem);
+	spin_lock_init(&common->lock);
+	init_completion(&common->thread_notifier);
+	init_waitqueue_head(&common->io_wait);
+	init_waitqueue_head(&common->fsg_wait);
+	common->state = FSG_STATE_TERMINATED;
+	memset(common->luns, 0, sizeof(common->luns));
+
+	return common;
+}
+
+void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs)
+{
+	common->sysfs = sysfs;
+}
+EXPORT_SYMBOL_GPL(fsg_common_set_sysfs);
+
+static void _fsg_common_free_buffers(struct fsg_buffhd *buffhds, unsigned n)
+{
+	if (buffhds) {
+		struct fsg_buffhd *bh = buffhds;
+		while (n--) {
+			kfree(bh->buf);
+			++bh;
+		}
+		kfree(buffhds);
+	}
+}
+
+int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n)
+{
+	struct fsg_buffhd *bh, *buffhds;
+	int i;
+
+	buffhds = kcalloc(n, sizeof(*buffhds), GFP_KERNEL);
+	if (!buffhds)
+		return -ENOMEM;
+
+	/* Data buffers cyclic list */
+	bh = buffhds;
+	i = n;
+	goto buffhds_first_it;
+	do {
+		bh->next = bh + 1;
+		++bh;
+buffhds_first_it:
+		bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL);
+		if (unlikely(!bh->buf))
+			goto error_release;
+	} while (--i);
+	bh->next = buffhds;
+
+	_fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+	common->fsg_num_buffers = n;
+	common->buffhds = buffhds;
+
+	return 0;
+
+error_release:
+	/*
+	 * "buf"s pointed to by heads after n - i are NULL
+	 * so releasing them won't hurt
+	 */
+	_fsg_common_free_buffers(buffhds, n);
+
+	return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(fsg_common_set_num_buffers);
+
+void fsg_common_remove_lun(struct fsg_lun *lun)
+{
+	if (device_is_registered(&lun->dev))
+		device_unregister(&lun->dev);
+	fsg_lun_close(lun);
+	kfree(lun);
+}
+EXPORT_SYMBOL_GPL(fsg_common_remove_lun);
+
+static void _fsg_common_remove_luns(struct fsg_common *common, int n)
+{
+	int i;
+
+	for (i = 0; i < n; ++i)
+		if (common->luns[i]) {
+			fsg_common_remove_lun(common->luns[i]);
+			common->luns[i] = NULL;
+		}
+}
+
+void fsg_common_remove_luns(struct fsg_common *common)
+{
+	_fsg_common_remove_luns(common, ARRAY_SIZE(common->luns));
+}
+EXPORT_SYMBOL_GPL(fsg_common_remove_luns);
+
+void fsg_common_free_buffers(struct fsg_common *common)
+{
+	_fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+	common->buffhds = NULL;
+}
+EXPORT_SYMBOL_GPL(fsg_common_free_buffers);
+
+int fsg_common_set_cdev(struct fsg_common *common,
+			 struct usb_composite_dev *cdev, bool can_stall)
+{
+	struct usb_string *us;
+
+	common->gadget = cdev->gadget;
+	common->ep0 = cdev->gadget->ep0;
+	common->ep0req = cdev->req;
+	common->cdev = cdev;
+
+	us = usb_gstrings_attach(cdev, fsg_strings_array,
+				 ARRAY_SIZE(fsg_strings));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+
+	fsg_intf_desc.iInterface = us[FSG_STRING_INTERFACE].id;
+
+	/*
+	 * Some peripheral controllers are known not to be able to
+	 * halt bulk endpoints correctly.  If one of them is present,
+	 * disable stalls.
+	 */
+	common->can_stall = can_stall &&
+			gadget_is_stall_supported(common->gadget);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fsg_common_set_cdev);
+
+static struct attribute *fsg_lun_dev_attrs[] = {
+	&dev_attr_ro.attr,
+	&dev_attr_file.attr,
+	&dev_attr_nofua.attr,
+	NULL
+};
+
+static umode_t fsg_lun_dev_is_visible(struct kobject *kobj,
+				      struct attribute *attr, int idx)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct fsg_lun *lun = fsg_lun_from_dev(dev);
+
+	if (attr == &dev_attr_ro.attr)
+		return lun->cdrom ? S_IRUGO : (S_IWUSR | S_IRUGO);
+	if (attr == &dev_attr_file.attr)
+		return lun->removable ? (S_IWUSR | S_IRUGO) : S_IRUGO;
+	return attr->mode;
+}
+
+static const struct attribute_group fsg_lun_dev_group = {
+	.attrs = fsg_lun_dev_attrs,
+	.is_visible = fsg_lun_dev_is_visible,
+};
+
+static const struct attribute_group *fsg_lun_dev_groups[] = {
+	&fsg_lun_dev_group,
+	NULL
+};
+
+int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg,
+			  unsigned int id, const char *name,
+			  const char **name_pfx)
+{
+	struct fsg_lun *lun;
+#if 0
+	char *pathbuf, *p;
+#endif
+	int rc = -ENOMEM;
+
+	if (id >= ARRAY_SIZE(common->luns))
+		return -ENODEV;
+
+	if (common->luns[id])
+		return -EBUSY;
+
+	if (!cfg->filename && !cfg->removable) {
+		pr_err("no file given for LUN%d\n", id);
+		return -EINVAL;
+	}
+
+	lun = kzalloc(sizeof(*lun), GFP_KERNEL);
+	if (!lun)
+		return -ENOMEM;
+
+	lun->name_pfx = name_pfx;
+
+	lun->cdrom = !!cfg->cdrom;
+	lun->ro = cfg->cdrom || cfg->ro;
+	lun->initially_ro = lun->ro;
+	lun->removable = !!cfg->removable;
+
+	if (!common->sysfs) {
+		/* we DON'T own the name!*/
+		lun->name = name;
+	} else {
+		lun->dev.release = fsg_lun_release;
+		lun->dev.parent = &common->gadget->dev;
+		lun->dev.groups = fsg_lun_dev_groups;
+		dev_set_drvdata(&lun->dev, &common->filesem);
+		dev_set_name(&lun->dev, "%s", name);
+		lun->name = dev_name(&lun->dev);
+
+		rc = device_register(&lun->dev);
+		if (rc) {
+			pr_info("failed to register LUN%d: %d\n", id, rc);
+			put_device(&lun->dev);
+			goto error_sysfs;
+		}
+	}
+
+	common->luns[id] = lun;
+#if 0
+	if (cfg->filename) {
+		rc = fsg_lun_open(lun, cfg->filename);
+		if (rc)
+			goto error_lun;
+	}
+
+	pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+	p = "(no medium)";
+	if (fsg_lun_is_open(lun)) {
+		p = "(error)";
+		if (pathbuf) {
+			p = file_path(lun->filp, pathbuf, PATH_MAX);
+			if (IS_ERR(p))
+				p = "(error)";
+		}
+	}
+	pr_info("LUN: %s%s%sfile: %s\n",
+	      lun->removable ? "removable " : "",
+	      lun->ro ? "read only " : "",
+	      lun->cdrom ? "CD-ROM " : "",
+	      p);
+	kfree(pathbuf);
+#else
+	common->luns[id]->filename = cfg->filename;
+	pr_info("LUN: %s%s%sfile: %s\n",
+		  lun->removable ? "removable " : "",
+		  lun->ro ? "read only " : "",
+		  lun->cdrom ? "CD-ROM " : "", cfg->filename);
+#endif
+	return 0;
+
+#if 0
+error_lun:
+	if (device_is_registered(&lun->dev))
+		device_unregister(&lun->dev);
+	fsg_lun_close(lun);
+	common->luns[id] = NULL;
+#endif
+
+error_sysfs:
+	kfree(lun);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fsg_common_create_lun);
+
+int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg)
+{
+	char buf[8]; /* enough for 100000000 different numbers, decimal */
+	int i, rc;
+
+	fsg_common_remove_luns(common);
+
+	for (i = 0; i < cfg->nluns; ++i) {
+		snprintf(buf, sizeof(buf), "lun%d", i);
+		rc = fsg_common_create_lun(common, &cfg->luns[i], i, buf, NULL);
+		if (rc)
+			goto fail;
+	}
+
+	pr_info("Number of LUNs=%d\n", cfg->nluns);
+
+	return 0;
+
+fail:
+	_fsg_common_remove_luns(common, i);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fsg_common_create_luns);
+
+void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn,
+				   const char *pn)
+{
+	int i;
+
+	/* Prepare inquiryString */
+	i = get_default_bcdDevice();
+	snprintf(common->inquiry_string, sizeof(common->inquiry_string),
+		 "%-8s%-16s%04x", vn ?: "Linux",
+		 /* Assume product name dependent on the first LUN */
+		 pn ?: ((*common->luns)->cdrom
+		     ? "File-CD Gadget"
+		     : "File-Stor Gadget"),
+		 i);
+}
+EXPORT_SYMBOL_GPL(fsg_common_set_inquiry_string);
+
+static void fsg_common_release(struct fsg_common *common)
+{
+	int i;
+
+	/* If the thread isn't already dead, tell it to exit now */
+	if (common->state != FSG_STATE_TERMINATED) {
+		raise_exception(common, FSG_STATE_EXIT);
+		wait_for_completion(&common->thread_notifier);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
+		struct fsg_lun *lun = common->luns[i];
+		if (!lun)
+			continue;
+		fsg_lun_close(lun);
+		if (device_is_registered(&lun->dev))
+			device_unregister(&lun->dev);
+		kfree(lun);
+	}
+
+	_fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+	if (common->free_storage_on_release)
+		kfree(common);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct fsg_dev		*fsg = fsg_from_func(f);
+	struct fsg_common	*common = fsg->common;
+	struct usb_gadget	*gadget = c->cdev->gadget;
+	int			i;
+	struct usb_ep		*ep;
+	unsigned		max_burst;
+	int			ret;
+	struct fsg_opts		*opts;
+#if 1
+	char *pathbuf, *p;
+	struct fsg_lun		*lun;
+	int rc;
+#endif
+
+
+	/* Don't allow to bind if we don't have at least one LUN */
+	ret = _fsg_common_get_max_lun(common);
+	if (ret < 0) {
+		pr_err("There should be at least one LUN.\n");
+		return -EINVAL;
+	}
+
+#if 1
+	lun = common->luns[ret];
+	if (lun->filename) {
+		rc = fsg_lun_open(lun, lun->filename);
+		if (rc) {
+			pr_err("fsg_bind failed on file open\n");
+			return -EIO;
+		}
+	}
+
+	pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+	p = "(no medium)";
+	if (fsg_lun_is_open(lun)) {
+		p = "(error)";
+		if (pathbuf) {
+			p = file_path(lun->filp, pathbuf, PATH_MAX);
+			if (IS_ERR(p)) {
+				pr_err("fsg_bind failed on err file path\n");
+				kfree(pathbuf);
+				return -EIO;
+			}
+		}
+	}
+	pr_info("LUN%d: %s open ok\n", ret, lun->filename);
+	kfree(pathbuf);
+#endif
+
+	opts = fsg_opts_from_func_inst(f->fi);
+	if (!opts->no_configfs) {
+		ret = fsg_common_set_cdev(fsg->common, c->cdev,
+					  fsg->common->can_stall);
+		if (ret)
+			return ret;
+		fsg_common_set_inquiry_string(fsg->common, NULL, NULL);
+	}
+
+	if (!common->thread_task) {
+		common->state = FSG_STATE_NORMAL;
+		common->thread_task =
+			kthread_create(fsg_main_thread, common, "file-storage");
+		if (IS_ERR(common->thread_task)) {
+			ret = PTR_ERR(common->thread_task);
+			common->thread_task = NULL;
+			common->state = FSG_STATE_TERMINATED;
+			return ret;
+		}
+		DBG(common, "I/O thread pid: %d\n",
+		    task_pid_nr(common->thread_task));
+		wake_up_process(common->thread_task);
+	}
+
+	fsg->gadget = gadget;
+
+	/* New interface */
+	i = usb_interface_id(c, f);
+	if (i < 0)
+		goto fail;
+	fsg_intf_desc.bInterfaceNumber = i;
+	fsg->interface_number = i;
+
+	/* Find all the endpoints we will use */
+	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
+	if (!ep)
+		goto autoconf_fail;
+	fsg->bulk_in = ep;
+
+	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
+	if (!ep)
+		goto autoconf_fail;
+	fsg->bulk_out = ep;
+
+	/* Assume endpoint addresses are the same for both speeds */
+	fsg_hs_bulk_in_desc.bEndpointAddress =
+		fsg_fs_bulk_in_desc.bEndpointAddress;
+	fsg_hs_bulk_out_desc.bEndpointAddress =
+		fsg_fs_bulk_out_desc.bEndpointAddress;
+
+	/* Calculate bMaxBurst, we know packet size is 1024 */
+	max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15);
+
+	fsg_ss_bulk_in_desc.bEndpointAddress =
+		fsg_fs_bulk_in_desc.bEndpointAddress;
+	fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst;
+
+	fsg_ss_bulk_out_desc.bEndpointAddress =
+		fsg_fs_bulk_out_desc.bEndpointAddress;
+	fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst;
+
+	ret = usb_assign_descriptors(f, fsg_fs_function, fsg_hs_function,
+			fsg_ss_function, fsg_ss_function);
+	if (ret)
+		goto autoconf_fail;
+
+	return 0;
+
+autoconf_fail:
+	ERROR(fsg, "unable to autoconfigure all endpoints\n");
+	i = -ENOTSUPP;
+fail:
+	/* terminate the thread */
+	if (fsg->common->state != FSG_STATE_TERMINATED) {
+		raise_exception(fsg->common, FSG_STATE_EXIT);
+		wait_for_completion(&fsg->common->thread_notifier);
+	}
+	return i;
+}
+
+/****************************** ALLOCATE FUNCTION *************************/
+
+static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct fsg_dev		*fsg = fsg_from_func(f);
+	struct fsg_common	*common = fsg->common;
+
+	DBG(fsg, "unbind\n");
+	if (fsg->common->fsg == fsg) {
+		__raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, NULL);
+		/* FIXME: make interruptible or killable somehow? */
+		wait_event(common->fsg_wait, common->fsg != fsg);
+	}
+
+	usb_free_all_descriptors(&fsg->function);
+}
+
+#if 0
+static inline struct fsg_lun_opts *to_fsg_lun_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct fsg_lun_opts, group);
+}
+
+static inline struct fsg_opts *to_fsg_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct fsg_opts,
+			    func_inst.group);
+}
+
+static void fsg_lun_attr_release(struct config_item *item)
+{
+	struct fsg_lun_opts *lun_opts;
+
+	lun_opts = to_fsg_lun_opts(item);
+	kfree(lun_opts);
+}
+
+static struct configfs_item_operations fsg_lun_item_ops = {
+	.release		= fsg_lun_attr_release,
+};
+
+static ssize_t fsg_lun_opts_file_show(struct config_item *item, char *page)
+{
+	struct fsg_lun_opts *opts = to_fsg_lun_opts(item);
+	struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
+
+	return fsg_show_file(opts->lun, &fsg_opts->common->filesem, page);
+}
+
+static ssize_t fsg_lun_opts_file_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct fsg_lun_opts *opts = to_fsg_lun_opts(item);
+	struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
+
+	return fsg_store_file(opts->lun, &fsg_opts->common->filesem, page, len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, file);
+
+static ssize_t fsg_lun_opts_ro_show(struct config_item *item, char *page)
+{
+	return fsg_show_ro(to_fsg_lun_opts(item)->lun, page);
+}
+
+static ssize_t fsg_lun_opts_ro_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct fsg_lun_opts *opts = to_fsg_lun_opts(item);
+	struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
+
+	return fsg_store_ro(opts->lun, &fsg_opts->common->filesem, page, len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, ro);
+
+static ssize_t fsg_lun_opts_removable_show(struct config_item *item,
+					   char *page)
+{
+	return fsg_show_removable(to_fsg_lun_opts(item)->lun, page);
+}
+
+static ssize_t fsg_lun_opts_removable_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	return fsg_store_removable(to_fsg_lun_opts(item)->lun, page, len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, removable);
+
+static ssize_t fsg_lun_opts_cdrom_show(struct config_item *item, char *page)
+{
+	return fsg_show_cdrom(to_fsg_lun_opts(item)->lun, page);
+}
+
+static ssize_t fsg_lun_opts_cdrom_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct fsg_lun_opts *opts = to_fsg_lun_opts(item);
+	struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
+
+	return fsg_store_cdrom(opts->lun, &fsg_opts->common->filesem, page,
+			       len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, cdrom);
+
+static ssize_t fsg_lun_opts_nofua_show(struct config_item *item, char *page)
+{
+	return fsg_show_nofua(to_fsg_lun_opts(item)->lun, page);
+}
+
+static ssize_t fsg_lun_opts_nofua_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	return fsg_store_nofua(to_fsg_lun_opts(item)->lun, page, len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, nofua);
+
+static ssize_t fsg_lun_opts_inquiry_string_show(struct config_item *item,
+						char *page)
+{
+	return fsg_show_inquiry_string(to_fsg_lun_opts(item)->lun, page);
+}
+
+static ssize_t fsg_lun_opts_inquiry_string_store(struct config_item *item,
+						 const char *page, size_t len)
+{
+	return fsg_store_inquiry_string(to_fsg_lun_opts(item)->lun, page, len);
+}
+
+CONFIGFS_ATTR(fsg_lun_opts_, inquiry_string);
+
+static struct configfs_attribute *fsg_lun_attrs[] = {
+	&fsg_lun_opts_attr_file,
+	&fsg_lun_opts_attr_ro,
+	&fsg_lun_opts_attr_removable,
+	&fsg_lun_opts_attr_cdrom,
+	&fsg_lun_opts_attr_nofua,
+	&fsg_lun_opts_attr_inquiry_string,
+	NULL,
+};
+
+static const struct config_item_type fsg_lun_type = {
+	.ct_item_ops	= &fsg_lun_item_ops,
+	.ct_attrs	= fsg_lun_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_group *fsg_lun_make(struct config_group *group,
+					 const char *name)
+{
+	struct fsg_lun_opts *opts;
+	struct fsg_opts *fsg_opts;
+	struct fsg_lun_config config;
+	char *num_str;
+	u8 num;
+	int ret;
+
+	num_str = strchr(name, '.');
+	if (!num_str) {
+		pr_err("Unable to locate . in LUN.NUMBER\n");
+		return ERR_PTR(-EINVAL);
+	}
+	num_str++;
+
+	ret = kstrtou8(num_str, 0, &num);
+	if (ret)
+		return ERR_PTR(ret);
+
+	fsg_opts = to_fsg_opts(&group->cg_item);
+	if (num >= FSG_MAX_LUNS)
+		return ERR_PTR(-ERANGE);
+	num = array_index_nospec(num, FSG_MAX_LUNS);
+
+	mutex_lock(&fsg_opts->lock);
+	if (fsg_opts->refcnt || fsg_opts->common->luns[num]) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	memset(&config, 0, sizeof(config));
+	config.removable = true;
+
+	ret = fsg_common_create_lun(fsg_opts->common, &config, num, name,
+				    (const char **)&group->cg_item.ci_name);
+	if (ret) {
+		kfree(opts);
+		goto out;
+	}
+	opts->lun = fsg_opts->common->luns[num];
+	opts->lun_id = num;
+	mutex_unlock(&fsg_opts->lock);
+
+	config_group_init_type_name(&opts->group, name, &fsg_lun_type);
+
+	return &opts->group;
+out:
+	mutex_unlock(&fsg_opts->lock);
+	return ERR_PTR(ret);
+}
+
+static void fsg_lun_drop(struct config_group *group, struct config_item *item)
+{
+	struct fsg_lun_opts *lun_opts;
+	struct fsg_opts *fsg_opts;
+
+	lun_opts = to_fsg_lun_opts(item);
+	fsg_opts = to_fsg_opts(&group->cg_item);
+
+	mutex_lock(&fsg_opts->lock);
+	if (fsg_opts->refcnt) {
+		struct config_item *gadget;
+
+		gadget = group->cg_item.ci_parent->ci_parent;
+		unregister_gadget_item(gadget);
+	}
+
+	fsg_common_remove_lun(lun_opts->lun);
+	fsg_opts->common->luns[lun_opts->lun_id] = NULL;
+	lun_opts->lun_id = 0;
+	mutex_unlock(&fsg_opts->lock);
+
+	config_item_put(item);
+}
+
+static void fsg_attr_release(struct config_item *item)
+{
+	struct fsg_opts *opts = to_fsg_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations fsg_item_ops = {
+	.release		= fsg_attr_release,
+};
+
+static ssize_t fsg_opts_stall_show(struct config_item *item, char *page)
+{
+	struct fsg_opts *opts = to_fsg_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%d", opts->common->can_stall);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t fsg_opts_stall_store(struct config_item *item, const char *page,
+				    size_t len)
+{
+	struct fsg_opts *opts = to_fsg_opts(item);
+	int ret;
+	bool stall;
+
+	mutex_lock(&opts->lock);
+
+	if (opts->refcnt) {
+		mutex_unlock(&opts->lock);
+		return -EBUSY;
+	}
+
+	ret = strtobool(page, &stall);
+	if (!ret) {
+		opts->common->can_stall = stall;
+		ret = len;
+	}
+
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(fsg_opts_, stall);
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+static ssize_t fsg_opts_num_buffers_show(struct config_item *item, char *page)
+{
+	struct fsg_opts *opts = to_fsg_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%d", opts->common->fsg_num_buffers);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t fsg_opts_num_buffers_store(struct config_item *item,
+					  const char *page, size_t len)
+{
+	struct fsg_opts *opts = to_fsg_opts(item);
+	int ret;
+	u8 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		goto end;
+
+	ret = fsg_common_set_num_buffers(opts->common, num);
+	if (ret)
+		goto end;
+	ret = len;
+
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(fsg_opts_, num_buffers);
+#endif
+
+static struct configfs_attribute *fsg_attrs[] = {
+	&fsg_opts_attr_stall,
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	&fsg_opts_attr_num_buffers,
+#endif
+	NULL,
+};
+
+static struct configfs_group_operations fsg_group_ops = {
+	.make_group	= fsg_lun_make,
+	.drop_item	= fsg_lun_drop,
+};
+
+static const struct config_item_type fsg_func_type = {
+	.ct_item_ops	= &fsg_item_ops,
+	.ct_group_ops	= &fsg_group_ops,
+	.ct_attrs	= fsg_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+#endif
+
+static void fsg_free_inst(struct usb_function_instance *fi)
+{
+	struct fsg_opts *opts;
+
+	opts = fsg_opts_from_func_inst(fi);
+	fsg_common_release(opts->common);
+	kfree(opts);
+}
+
+static struct usb_function_instance *fsg_alloc_inst(void)
+{
+	struct fsg_opts *opts;
+	struct fsg_lun_config config;
+	int rc;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = fsg_free_inst;
+	opts->common = fsg_common_setup(opts->common);
+	if (IS_ERR(opts->common)) {
+		rc = PTR_ERR(opts->common);
+		goto release_opts;
+	}
+
+	rc = fsg_common_set_num_buffers(opts->common,
+					CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS);
+	if (rc)
+		goto release_common;
+
+	pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
+
+	memset(&config, 0, sizeof(config));
+
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_CPU_ASR1901)
+	/* readonly cdrom */
+	config.cdrom = 1;
+	config.removable = 0;
+#ifdef CONFIG_USB_ANDROID_DETECT_HOST_OS
+	if (host_os_is_linux()) {
+		config.filename = LINUX_CDROM_PATH;
+	} else if (host_os_is_apple()) {
+		config.filename = APPLE_CDROM_PATH;
+	} else {
+		config.filename = WIN_CDROM_PATH;
+	}
+#else
+	config.filename = DEF_CDROM_PATH;
+#endif
+#else
+	config.removable = true;
+#endif
+	rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
+			(const char **)&opts->func_inst.group.cg_item.ci_name);
+	if (rc)
+		goto release_buffers;
+
+	opts->lun0.lun = opts->common->luns[0];
+	opts->lun0.lun_id = 0;
+
+#if 0
+	config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type);
+
+	config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
+	configfs_add_default_group(&opts->lun0.group, &opts->func_inst.group);
+#endif
+
+	return &opts->func_inst;
+
+release_buffers:
+	fsg_common_free_buffers(opts->common);
+release_common:
+	kfree(opts->common);
+release_opts:
+	kfree(opts);
+	return ERR_PTR(rc);
+}
+
+static void fsg_free(struct usb_function *f)
+{
+	struct fsg_dev *fsg;
+	struct fsg_opts *opts;
+
+	fsg = container_of(f, struct fsg_dev, function);
+	opts = container_of(f->fi, struct fsg_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	kfree(fsg);
+}
+
+static struct usb_function *fsg_alloc(struct usb_function_instance *fi)
+{
+	struct fsg_opts *opts = fsg_opts_from_func_inst(fi);
+	struct fsg_common *common = opts->common;
+	struct fsg_dev *fsg;
+
+	fsg = kzalloc(sizeof(*fsg), GFP_KERNEL);
+	if (unlikely(!fsg))
+		return ERR_PTR(-ENOMEM);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+	mutex_unlock(&opts->lock);
+
+	fsg->function.name	= FSG_DRIVER_DESC;
+	fsg->function.bind	= fsg_bind;
+	fsg->function.unbind	= fsg_unbind;
+	fsg->function.setup	= fsg_setup;
+	fsg->function.set_alt	= fsg_set_alt;
+	fsg->function.disable	= fsg_disable;
+	fsg->function.free_func	= fsg_free;
+
+	fsg->common               = common;
+
+	return &fsg->function;
+}
+
+DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal Nazarewicz");
+
+/************************* Module parameters *************************/
+
+
+void fsg_config_from_params(struct fsg_config *cfg,
+		       const struct fsg_module_parameters *params,
+		       unsigned int fsg_num_buffers)
+{
+	struct fsg_lun_config *lun;
+	unsigned i;
+
+	/* Configure LUNs */
+	cfg->nluns =
+		min(params->luns ?: (params->file_count ?: 1u),
+		    (unsigned)FSG_MAX_LUNS);
+	for (i = 0, lun = cfg->luns; i < cfg->nluns; ++i, ++lun) {
+		lun->ro = !!params->ro[i];
+		lun->cdrom = !!params->cdrom[i];
+		lun->removable = !!params->removable[i];
+		lun->filename =
+			params->file_count > i && params->file[i][0]
+			? params->file[i]
+			: NULL;
+	}
+
+	/* Let MSF use defaults */
+	cfg->vendor_name = NULL;
+	cfg->product_name = NULL;
+
+	cfg->ops = NULL;
+	cfg->private_data = NULL;
+
+	/* Finalise */
+	cfg->can_stall = params->stall;
+	cfg->fsg_num_buffers = fsg_num_buffers;
+}
+EXPORT_SYMBOL_GPL(fsg_config_from_params);
diff --git a/marvell/linux/drivers/usb/gadget/function/f_mass_storage.h b/marvell/linux/drivers/usb/gadget/function/f_mass_storage.h
new file mode 100644
index 0000000..3b8c4ce
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_mass_storage.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef USB_F_MASS_STORAGE_H
+#define USB_F_MASS_STORAGE_H
+
+#include <linux/usb/composite.h>
+#include "storage_common.h"
+
+struct fsg_module_parameters {
+	char		*file[FSG_MAX_LUNS];
+	bool		ro[FSG_MAX_LUNS];
+	bool		removable[FSG_MAX_LUNS];
+	bool		cdrom[FSG_MAX_LUNS];
+	bool		nofua[FSG_MAX_LUNS];
+
+	unsigned int	file_count, ro_count, removable_count, cdrom_count;
+	unsigned int	nofua_count;
+	unsigned int	luns;	/* nluns */
+	bool		stall;	/* can_stall */
+};
+
+#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc)	\
+	module_param_array_named(prefix ## name, params.name, type,	\
+				 &prefix ## params.name ## _count,	\
+				 S_IRUGO);				\
+	MODULE_PARM_DESC(prefix ## name, desc)
+
+#define _FSG_MODULE_PARAM(prefix, params, name, type, desc)		\
+	module_param_named(prefix ## name, params.name, type,		\
+			   S_IRUGO);					\
+	MODULE_PARM_DESC(prefix ## name, desc)
+
+#define __FSG_MODULE_PARAMETERS(prefix, params)				\
+	_FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp,		\
+				"names of backing files or devices");	\
+	_FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool,		\
+				"true to force read-only");		\
+	_FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool,	\
+				"true to simulate removable media");	\
+	_FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool,		\
+				"true to simulate CD-ROM instead of disk"); \
+	_FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool,		\
+				"true to ignore SCSI WRITE(10,12) FUA bit"); \
+	_FSG_MODULE_PARAM(prefix, params, luns, uint,			\
+			  "number of LUNs");				\
+	_FSG_MODULE_PARAM(prefix, params, stall, bool,			\
+			  "false to prevent bulk stalls")
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+
+#define FSG_MODULE_PARAMETERS(prefix, params)				\
+	__FSG_MODULE_PARAMETERS(prefix, params);			\
+	module_param_named(num_buffers, fsg_num_buffers, uint, S_IRUGO);\
+	MODULE_PARM_DESC(num_buffers, "Number of pipeline buffers")
+#else
+
+#define FSG_MODULE_PARAMETERS(prefix, params)				\
+	__FSG_MODULE_PARAMETERS(prefix, params)
+
+#endif
+
+struct fsg_common;
+
+/* FSF callback functions */
+struct fsg_lun_opts {
+	struct config_group group;
+	struct fsg_lun *lun;
+	int lun_id;
+};
+
+struct fsg_opts {
+	struct fsg_common *common;
+	struct usb_function_instance func_inst;
+	struct fsg_lun_opts lun0;
+	struct config_group *default_groups[2];
+	bool no_configfs; /* for legacy gadgets */
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+struct fsg_lun_config {
+	const char *filename;
+	char ro;
+	char removable;
+	char cdrom;
+	char nofua;
+	char inquiry_string[INQUIRY_STRING_LEN];
+};
+
+struct fsg_config {
+	unsigned nluns;
+	struct fsg_lun_config luns[FSG_MAX_LUNS];
+
+	/* Callback functions. */
+	const struct fsg_operations	*ops;
+	/* Gadget's private data. */
+	void			*private_data;
+
+	const char *vendor_name;		/*  8 characters or less */
+	const char *product_name;		/* 16 characters or less */
+
+	char			can_stall;
+	unsigned int		fsg_num_buffers;
+};
+
+static inline struct fsg_opts *
+fsg_opts_from_func_inst(const struct usb_function_instance *fi)
+{
+	return container_of(fi, struct fsg_opts, func_inst);
+}
+
+void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs);
+
+int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n);
+
+void fsg_common_free_buffers(struct fsg_common *common);
+
+int fsg_common_set_cdev(struct fsg_common *common,
+			struct usb_composite_dev *cdev, bool can_stall);
+
+void fsg_common_remove_lun(struct fsg_lun *lun);
+
+void fsg_common_remove_luns(struct fsg_common *common);
+
+int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg,
+			  unsigned int id, const char *name,
+			  const char **name_pfx);
+
+int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg);
+
+void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn,
+				   const char *pn);
+
+void fsg_config_from_params(struct fsg_config *cfg,
+			    const struct fsg_module_parameters *params,
+			    unsigned int fsg_num_buffers);
+
+#endif /* USB_F_MASS_STORAGE_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/f_mbim.c b/marvell/linux/drivers/usb/gadget/function/f_mbim.c
new file mode 100644
index 0000000..526d586
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_mbim.c
@@ -0,0 +1,4425 @@
+/*
+ * f_mbim.c -- USB CDC Network (MBIM) link function driver
+ *
+ * Copyright (C) 2014 Marvell
+ * Contact: Yaniv Yizhak <yyaniv@marvell.com>
+ *
+ * The driver borrows from f_ecm.c which is:
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/crc32.h>
+#include <linux/usb/cdc.h>
+#include <linux/if_vlan.h>
+#include <linux/if_arp.h>
+#include <net/arp.h>
+#include <linux/spinlock.h>
+#include "u_ether.h"
+
+#ifdef CONFIG_ASR_TOE
+#define CONFIG_NCM_INPUT_AS_RNDIS	1
+#define CONFIG_MBIM_INPUT_AS_RNDIS	1
+#include <linux/toe.h>
+#if (defined CONFIG_NCM_INPUT_AS_RNDIS) || (defined CONFIG_MBIM_INPUT_AS_RNDIS)
+#include "rndis.h"
+#endif
+#endif
+
+
+static struct sk_buff *ncm_wrap_ntb(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx);
+static struct sk_buff *mbim_wrap_ntb(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx);
+#ifdef CONFIG_USB_G_NCM_MULT_PKT_SUPPORT
+static struct sk_buff *ncm_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx);
+#endif
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+static struct sk_buff *mbim_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx);
+#endif
+static int ncm_unwrap_ntb(struct gether *port, struct sk_buff *skb,
+						struct sk_buff_head *list);
+static int mbim_unwrap_ntb(struct gether *port, struct sk_buff *skb,
+						struct sk_buff_head *list);
+#ifndef CONFIG_ASR_TOE
+static struct sk_buff *mbim_process_dgram(struct gether *port,
+					  struct sk_buff *skb);
+#endif
+static void __maybe_unused mbim_net_open(struct gether *geth);
+static void __maybe_unused mbim_net_close(struct gether *geth);
+
+/*
+ * This function is a "Mobile Broadband Interface Model" (MBIM) link.
+ * MBIM is intended to be used with high-speed network attachments.
+ *
+ * Note that MBIM requires the use of "alternate settings" for its data
+ * interface.  This means that the set_alt() method has real work to do,
+ * and also means that a get_alt() method is required.
+ */
+
+/* to trigger crc/non-crc NDP (NCM Datagram Pointer) signature */
+#define NCM_NDP_HDR_CRC_MASK	0x01000000
+#define NCM_NDP_HDR_CRC		0x01000000
+#define NCM_NDP_HDR_NOCRC	0x00000000
+
+/*
+ * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
+ * packet, to simplify cancellation; and a big transfer interval, to
+ * waste less bandwidth.
+ */
+#define LOG2_STATUS_INTERVAL_MSEC	5	/* 1 << 5 == 32 msec */
+#define NCM_STATUS_BYTECOUNT		16	/* 8 byte header + data */
+#define MBIM_STATUS_BYTECOUNT		(NCM_STATUS_BYTECOUNT)
+
+enum mbim_notify_state {
+	MBIM_NOTIFY_NONE,		/* don't notify */
+	MBIM_NOTIFY_CONNECT,		/* issue CONNECT next */
+	MBIM_NOTIFY_SPEED,		/* issue SPEED_CHANGE next */
+};
+
+enum ncm_mbim_state {
+	STATE_NCM,
+	STATE_MBIM,
+};
+
+enum ncm_mbim_mode {
+	MODE_NCM,
+	MODE_MBIM,
+	MODE_NCM_MBIM,
+	MODE_UNKNOWN,
+};
+
+/* Control packet definition */
+#define MAX_CTRL_PKT_SIZE	(4096)
+struct ctrl_pkt {
+	void			*buf;
+	int			len;
+	__le32		transaction_id;
+	struct list_head	list;
+};
+
+struct f_mbim {
+	struct gether			port;
+	char				dev_addr[14];
+	char				host_addr[ETH_ALEN];
+	struct usb_ep			*notify;
+	struct usb_endpoint_descriptor	*notify_desc;
+	struct usb_request		*notify_req;
+	atomic_t			notify_count;
+	u8				notify_state;
+	bool				is_open;
+	atomic_t			response_available;
+	atomic_t	online;
+	atomic_t	open_excl;
+	atomic_t	ioctl_excl;
+	atomic_t	read_excl;
+	atomic_t	write_excl;
+	wait_queue_head_t read_wq;
+	wait_queue_head_t write_wq;
+	wait_queue_head_t open_wq;
+	u8				port_num;
+	u8				ctrl_id, data_id;
+	struct ndp_parser_opts		*parser_opts;
+	bool				is_crc;
+	spinlock_t			lock;
+	struct list_head	cpkt_req_q;
+	struct list_head	cpkt_resp_q;
+	u32			ntb_input_size;
+	u16			ntb_max_datagrams;
+	atomic_t		error;
+
+	enum ncm_mbim_mode	mode;
+	enum ncm_mbim_state	state;
+};
+
+/* Number of MBIM ports */
+#define NR_MBIM_PORTS			1
+static struct mbim_ports {
+	struct f_mbim	*port;
+	unsigned	port_num;
+} mbim_ports[NR_MBIM_PORTS];
+
+static struct usb_request *g_notify_req;
+
+static inline struct f_mbim *func_to_mbim(struct usb_function *f)
+{
+	return container_of(f, struct f_mbim, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static inline unsigned mbim_bitrate(struct usb_gadget *g)
+{
+	if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return (13 * 512 * 8 * 1000 * 8);
+	else
+		return (19 *  64 * 1 * 1000 * 8);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * skbs of size less than that will not be aligned
+ * to MBIM's dwNtbInMaxSize to save bus bandwidth
+ */
+#define	MAX_TX_NONFIXED		(512 * 3)
+
+/*
+ * Prevent memory waste here.
+ * NTB size 0x4000 is 16kB, but during skb allocation
+ * it will be reserved some additional memory in headroom,
+ * so final chunk to allocate is a little bit more than 16k.
+ * This leads to waste of memory because SLAB allocator
+ * will use 32kB buffers.
+ * So reduce maximum NTB size by 0x200 bytes that
+ * is quite enough for preventing usage of 32k buffers
+ * and performance doesn't have noticeable degradation
+ */
+
+#define MAX_IN_SKB_NUM  (12) /* don't use too much headroom */
+#ifdef CONFIG_ASR_TOE
+#define NTB_IN_SIZE	(16384)
+/* min value: ncm header(24+40*4) + 1514 + padding = 1700
+*  max value: 2048 - 64(bm header) - sizeof(struct skb) = 1784
+*/
+#define NTB_OUT_SIZE	(1728)	/* 27 * 64 */
+#define MAX_DGRAMS_AGGREGATION  (1)
+#else
+#ifndef CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS
+#define NTB_IN_SIZE	(1600 * 10)	/* 1500+14+64+alignment+extra 8bytes*/
+#define NTB_OUT_SIZE	(2048)
+#define MAX_DGRAMS_AGGREGATION  (10)
+#else
+#define NTB_IN_SIZE	(16000)	/* 1500+14+64+alignment+extra 8bytes*/
+#define NTB_OUT_SIZE	(0x2000 - 256 - 2)
+#define MAX_DGRAMS_AGGREGATION  (40)
+#endif
+#endif
+
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+#ifdef CONFIG_USBNET_USE_SG
+#define MBIM_MAX_DGRAMS_AGGREGATION  (8)
+#else
+#define MBIM_MAX_DGRAMS_AGGREGATION  (10)
+#endif
+#endif
+
+#define NDP_IN_DIVISOR		(0x4)
+
+
+#define FORMATS_SUPPORTED	(USB_CDC_NCM_NTB16_SUPPORTED | USB_CDC_NCM_NTB32_SUPPORTED)
+
+/* NCM Transfer Block parameters (also used for GET_NTB_PARAMETERS request)*/
+static struct usb_cdc_ncm_ntb_parameters ntb_parameters = {
+	.wLength = sizeof ntb_parameters,
+	.bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED),
+	.dwNtbInMaxSize = cpu_to_le32(NTB_IN_SIZE),
+	.wNdpInDivisor = cpu_to_le16(NDP_IN_DIVISOR),
+	.wNdpInPayloadRemainder = cpu_to_le16(0),
+	.wNdpInAlignment = cpu_to_le16(4),
+
+	.dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE),
+	.wNdpOutDivisor = cpu_to_le16(4),
+	.wNdpOutPayloadRemainder = cpu_to_le16(0),
+	.wNdpOutAlignment = cpu_to_le16(4),
+#ifdef CONFIG_ASR_TOE
+	.wNtbOutMaxDatagrams = 1,
+#else
+	.wNtbOutMaxDatagrams = 0, /* No limit */
+#endif
+};
+
+/*
+ * Here are options for the NCM Datagram Pointer table (NDP) parser.
+ * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3),
+ * in NDP16 offsets and sizes fields are 1 16bit word wide,
+ * in NDP32 -- 2 16bit words wide. Also signatures are different.
+ * To make the parser code the same, put the differences in the structure,
+ * and switch pointers to the structures when the format is changed.
+ */
+struct ndp_parser_opts {
+	u32		nth_sign;
+	u32		ndp_sign;
+	unsigned	nth_size;
+	unsigned	ndp_size;
+	unsigned	ndplen_align;
+	/* sizes in u16 units */
+	unsigned	dgram_item_len; /* index or length */
+	unsigned	block_length;
+	unsigned	fp_index;
+	unsigned	reserved1;
+	unsigned	reserved2;
+	unsigned	next_fp_index;
+};
+
+#define INIT_NDP16_OPTS {					\
+		.nth_sign = USB_CDC_NCM_NTH16_SIGN,		\
+		.ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN,	\
+		.nth_size = sizeof(struct usb_cdc_ncm_nth16),	\
+		.ndp_size = sizeof(struct usb_cdc_ncm_ndp16),	\
+		.ndplen_align = 4,				\
+		.dgram_item_len = 1,				\
+		.block_length = 1,				\
+		.fp_index = 1,					\
+		.reserved1 = 0,					\
+		.reserved2 = 0,					\
+		.next_fp_index = 1,				\
+	}
+
+#define INIT_NDP32_OPTS {					\
+		.nth_sign = USB_CDC_NCM_NTH32_SIGN,		\
+		.ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN,	\
+		.nth_size = sizeof(struct usb_cdc_ncm_nth32),	\
+		.ndp_size = sizeof(struct usb_cdc_ncm_ndp32),	\
+		.ndplen_align = 8,				\
+		.dgram_item_len = 2,				\
+		.block_length = 2,				\
+		.fp_index = 2,					\
+		.reserved1 = 1,					\
+		.reserved2 = 2,					\
+		.next_fp_index = 2,				\
+	}
+
+static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
+static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS;
+
+/*---------------------------------------------------------------------------------*/
+/* USB descriptors */
+
+/* USB_DT_INTERFACE_ASSOCIATION: groups interfaces */
+static struct usb_interface_assoc_descriptor mbim_iad_desc = {
+	.bLength =		sizeof mbim_iad_desc,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,	/* control + data */
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_MBIM,
+	.bFunctionProtocol =	USB_CDC_PROTO_NONE,
+	/* .iFunction =		DYNAMIC */
+};
+
+static struct usb_interface_assoc_descriptor ncm_iad_desc = {
+	.bLength =		sizeof ncm_iad_desc,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,	/* control + data */
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_NCM,
+	.bFunctionProtocol =	USB_CDC_PROTO_NONE,
+	/* .iFunction =		DYNAMIC */
+};
+
+/* USB_DT_INTERFACE: Interface descriptor: CONTROL */
+static struct usb_interface_descriptor mbim_control_intf = {
+	.bLength =		sizeof mbim_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_MBIM,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor ncm_control_intf = {
+	.bLength =		sizeof ncm_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_NCM,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor ncm_mbim_control_intf = {
+	.bLength =		sizeof mbim_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting = 	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_MBIM,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+
+
+/* "Header Functional Descriptor" from CDC spec  5.2.3.1 */
+static struct usb_cdc_header_desc mbim_header_desc = {
+	.bLength =		sizeof mbim_header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+/* "Union Functional Descriptor" from CDC spec 5.2.3.8 */
+static struct usb_cdc_union_desc mbim_union_desc = {
+	.bLength =		sizeof(mbim_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+/* "Ethernet Networking Functional Descriptor" from CDC spec 5.2.3.16 */
+static struct usb_cdc_ether_desc ether_desc = {
+	.bLength =		sizeof ether_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ETHERNET_TYPE,
+
+	/* this descriptor actually adds value, surprise! */
+	/* .iMACAddress = DYNAMIC */
+	.bmEthernetStatistics =	cpu_to_le32(0), /* no statistics */
+	.wMaxSegmentSize =	cpu_to_le16(ETH_FRAME_LEN),
+	.wNumberMCFilters =	cpu_to_le16(0),
+	.bNumberPowerFilters =	0,
+};
+
+/* "MBIM Control Model Functional Descriptor" */
+static struct usb_cdc_mbim_desc mbim_desc = {
+	.bLength =		sizeof mbim_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_MBIM_TYPE,
+
+	.bcdMBIMVersion =	cpu_to_le16(0x0100),
+	.wMaxControlMessage =	cpu_to_le16(MAX_CTRL_PKT_SIZE),
+	.bNumberFilters =	0x10,
+	.bMaxFilterSize =	0x80,
+	.wMaxSegmentSize =	cpu_to_le16(0xfe0),
+	.bmNetworkCapabilities = 0x20,
+};
+
+/* "MBIM Control Model Extended Functional Descriptor" */
+static struct usb_cdc_mbim_ext_desc mbim_ext_desc = {
+	.bLength =		sizeof mbim_ext_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_MBIM_EXT_TYPE,
+
+	.bcdMBIMExtendedVersion = cpu_to_le16(0x0100),
+	.bMaxOutstandingCommandMessages = 1,
+	.wMTU = cpu_to_le16(1500),
+};
+
+#define NCAPS	(USB_CDC_NCM_NCAP_ETH_FILTER)
+
+static struct usb_cdc_ncm_desc ncm_desc = {
+	.bLength =		sizeof ncm_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_NCM_TYPE,
+
+	.bcdNcmVersion =	cpu_to_le16(0x0100),
+	/* can process SetEthernetPacketFilter */
+	.bmNetworkCapabilities = NCAPS,
+};
+
+
+/* USB_DT_INTERFACE: Interface descriptor: DATA disable */
+static struct usb_interface_descriptor mbim_data_nop_intf = {
+	.bLength =		sizeof mbim_data_nop_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_MBIM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor ncm_data_nop_intf = {
+	.bLength =		sizeof ncm_data_nop_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_NCM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+/* USB_DT_INTERFACE: Interface descriptor: DATA enable */
+static struct usb_interface_descriptor mbim_data_intf = {
+	.bLength =		sizeof mbim_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_MBIM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor ncm_data_intf = {
+	.bLength =		sizeof ncm_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_NCM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor ncm_mbim_data_intf = {
+	.bLength =		sizeof mbim_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	2,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_MBIM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed endpoints support: */
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: NOTIFY */
+static struct usb_endpoint_descriptor fs_mbim_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MBIM_STATUS_BYTECOUNT),
+	.bInterval =		1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: IN */
+static struct usb_endpoint_descriptor fs_mbim_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: OUT */
+static struct usb_endpoint_descriptor fs_mbim_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+/* List of all the full speed supported descriptors */
+static struct usb_descriptor_header *mbim_fs_function[] = {
+	(struct usb_descriptor_header *) &mbim_iad_desc,
+	/* CDC MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &fs_mbim_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_data_intf,
+	(struct usb_descriptor_header *) &fs_mbim_in_desc,
+	(struct usb_descriptor_header *) &fs_mbim_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_fs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &fs_mbim_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &fs_mbim_in_desc,
+	(struct usb_descriptor_header *) &fs_mbim_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_mbim_fs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors alt 0 */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &fs_mbim_notify_desc,
+	/* CDC MBIM control descriptors alt 1 */
+	(struct usb_descriptor_header *) &ncm_mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &fs_mbim_notify_desc,
+
+	/* data interface, altsettings 0, 1 and 2 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &fs_mbim_in_desc,
+	(struct usb_descriptor_header *) &fs_mbim_out_desc,
+	(struct usb_descriptor_header *) &ncm_mbim_data_intf,
+	(struct usb_descriptor_header *) &fs_mbim_in_desc,
+	(struct usb_descriptor_header *) &fs_mbim_out_desc,
+	NULL,
+};
+
+/* high speed endpoints support: */
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: NOTIFY */
+static struct usb_endpoint_descriptor hs_mbim_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MBIM_STATUS_BYTECOUNT),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: IN */
+static struct usb_endpoint_descriptor hs_mbim_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: OUT */
+static struct usb_endpoint_descriptor hs_mbim_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+/* List of all the high speed supported descriptors */
+static struct usb_descriptor_header *mbim_hs_function[] = {
+	(struct usb_descriptor_header *) &mbim_iad_desc,
+	/* CDC MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &hs_mbim_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_data_intf,
+	(struct usb_descriptor_header *) &hs_mbim_in_desc,
+	(struct usb_descriptor_header *) &hs_mbim_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_hs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &hs_mbim_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &hs_mbim_in_desc,
+	(struct usb_descriptor_header *) &hs_mbim_out_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_mbim_hs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &hs_mbim_notify_desc,
+
+	(struct usb_descriptor_header *) &ncm_mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &hs_mbim_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &hs_mbim_in_desc,
+	(struct usb_descriptor_header *) &hs_mbim_out_desc,
+	(struct usb_descriptor_header *) &ncm_mbim_data_intf,
+	(struct usb_descriptor_header *) &hs_mbim_in_desc,
+	(struct usb_descriptor_header *) &hs_mbim_out_desc,
+	NULL,
+};
+
+/* super speed endpoints support: */
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: NOTIFY */
+static struct usb_endpoint_descriptor ss_mbim_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MBIM_STATUS_BYTECOUNT),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_mbim_intr_comp_desc = {
+	.bLength =		sizeof ss_mbim_intr_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(MBIM_STATUS_BYTECOUNT),
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: IN */
+static struct usb_endpoint_descriptor ss_mbim_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+/* USB_DT_ENDPOINT: Endpoint descriptor: OUT */
+static struct usb_endpoint_descriptor ss_mbim_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_mbim_bulk_comp_desc = {
+	.bLength =		sizeof ss_mbim_bulk_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+#ifdef CONFIG_ASR_TOE
+	.bMaxBurst = 15,
+#endif
+	/* .bmAttributes =	0, */
+};
+
+/* List of all the supper speed supported descriptors */
+static struct usb_descriptor_header *mbim_ss_function[] = {
+	(struct usb_descriptor_header *) &mbim_iad_desc,
+	/* CDC MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &ss_mbim_notify_desc,
+	(struct usb_descriptor_header *) &ss_mbim_intr_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_data_intf,
+	(struct usb_descriptor_header *) &ss_mbim_in_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_mbim_out_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_ss_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &ss_mbim_notify_desc,
+	(struct usb_descriptor_header *) &ss_mbim_intr_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &ss_mbim_in_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_mbim_out_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *ncm_mbim_ss_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &ss_mbim_notify_desc,
+	(struct usb_descriptor_header *) &ss_mbim_intr_comp_desc,
+	(struct usb_descriptor_header *) &ncm_mbim_control_intf,
+	(struct usb_descriptor_header *) &mbim_header_desc,
+	(struct usb_descriptor_header *) &mbim_union_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &mbim_desc,
+	(struct usb_descriptor_header *) &mbim_ext_desc,
+	(struct usb_descriptor_header *) &ss_mbim_notify_desc,
+	(struct usb_descriptor_header *) &ss_mbim_intr_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &ss_mbim_in_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_mbim_out_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ncm_mbim_data_intf,
+	(struct usb_descriptor_header *) &ss_mbim_in_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_mbim_out_desc,
+	(struct usb_descriptor_header *) &ss_mbim_bulk_comp_desc,
+	NULL,
+};
+
+
+/*---------------------------------------------------------------------------------*/
+/* string descriptors: */
+
+#define STRING_CTRL_IDX	0
+#define STRING_MAC_IDX	1
+#define STRING_DATA_IDX	2
+#define STRING_IAD_IDX	3
+
+static struct usb_string mbim_string_defs[] = {
+	[STRING_CTRL_IDX].s = "MBIM Control",
+	[STRING_MAC_IDX].s = NULL /* DYNAMIC */,
+	[STRING_DATA_IDX].s = "MBIM Data",
+	[STRING_IAD_IDX].s = "CDC MBIM",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings mbim_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		mbim_string_defs,
+};
+
+static struct usb_gadget_strings mbim_string_table_2 = {
+	.language	= 0,
+	.strings	= mbim_string_defs,
+};
+
+static struct usb_gadget_strings *mbim_strings[] = {
+	&mbim_string_table,
+	&mbim_string_table_2,
+	NULL,
+};
+
+/*Microsoft defined the following OS Descriptor*/
+#define USB_VENDOR_OSVC_DEFAULT		0x04
+#define OS_STRING_ID			0xEE	/*Microsoft defined*/
+#define MS_EXTENDED_COMPAT_ID		0x04	/*Microsoft defined*/
+#define MS_EXTENDED_PROPERTY		0x05	/*Microsoft defined*/
+
+/* Microsoft MBIM OS String */
+static u8 mbim_os_string[] = {
+	18, /* sizeof(mtp_os_string) */
+	USB_DT_STRING,
+	/* Signature field: "MSFT100" */
+	'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+	/* vendor code */
+	USB_VENDOR_OSVC_DEFAULT,
+	/* padding */
+	0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mbim_ext_config_desc_header {
+	__le32	dwLength;
+	__u16	bcdVersion;
+	__le16	wIndex;
+	__u8	bCount;
+	__u8	reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mbim_ext_config_desc_function {
+	__u8	bFirstInterfaceNumber;
+	__u8	bInterfaceCount;
+	__u8	compatibleID[8];
+	__u8	subCompatibleID[8];
+	__u8	reserved[6];
+};
+
+/* MBIM Extended Configuration Descriptor */
+struct {
+	struct mbim_ext_config_desc_header	header;
+	struct mbim_ext_config_desc_function    function;
+} mbim_ext_config_desc = {
+	.header = {
+		.dwLength = __constant_cpu_to_le32(sizeof(mbim_ext_config_desc)),
+		.bcdVersion = __constant_cpu_to_le16(0x0100),
+		.wIndex = __constant_cpu_to_le16(4),
+		.bCount = __constant_cpu_to_le16(1),
+	},
+	.function = {
+		.bFirstInterfaceNumber = 0,
+		.bInterfaceCount = 2,
+		.compatibleID = { 'M', 'B', 'I', 'M' },
+	},
+};
+
+#define MBIM_MAX_RESP_Q (64)
+/*-----------------------------------------------------------------------*/
+
+static inline void put_mbim(__le16 **p, unsigned size, unsigned val)
+{
+	switch (size) {
+	case 1:
+		put_unaligned_le16((u16)val, *p);
+		break;
+	case 2:
+		put_unaligned_le32((u32)val, *p);
+		break;
+	default:
+		BUG();
+	}
+
+	*p += size;
+}
+
+static inline void put_mbim_no_inc(void *p, unsigned size, unsigned val)
+{
+	switch (size) {
+	case 1:
+		put_unaligned_le16((u16)val, p);
+		break;
+	case 2:
+		put_unaligned_le32((u32)val, p);
+		break;
+	default:
+		BUG();
+	}
+}
+
+static inline unsigned get_mbim(__le16 **p, unsigned size)
+{
+	unsigned tmp;
+
+	switch (size) {
+	case 1:
+		tmp = get_unaligned_le16(*p);
+		break;
+	case 2:
+		tmp = get_unaligned_le32(*p);
+		break;
+	default:
+		BUG();
+	}
+
+	*p += size;
+	return tmp;
+}
+
+/*-------------------------------------------------------------------------*/
+static inline int mbim_lock(atomic_t *excl)
+{
+	if (atomic_inc_return(excl) == 1) {
+		return 0;
+	} else {
+		atomic_dec(excl);
+		return -EBUSY;
+	}
+}
+
+static inline void mbim_unlock(atomic_t *excl)
+{
+	atomic_dec(excl);
+}
+
+static struct ctrl_pkt *mbim_alloc_ctrl_pkt(unsigned len, gfp_t flags)
+{
+	struct ctrl_pkt *pkt;
+
+	pkt = kzalloc(sizeof(struct ctrl_pkt), flags);
+	if (!pkt)
+		return NULL;
+
+	pkt->buf = kmalloc(len, flags);
+	if (!pkt->buf) {
+		kfree(pkt);
+		return NULL;
+	}
+	pkt->len = len;
+
+	return pkt;
+}
+
+static void mbim_free_ctrl_pkt(struct ctrl_pkt *pkt)
+{
+	if (pkt) {
+		kfree(pkt->buf);
+		kfree(pkt);
+	}
+}
+
+static struct usb_request *mbim_alloc_req(struct usb_ep *ep, int buffer_size)
+{
+	struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!req)
+		return NULL;
+
+	req->buf = kmalloc(buffer_size, GFP_KERNEL);
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+	req->length = buffer_size;
+	return req;
+}
+
+void fmbim_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(ep, req);
+	}
+}
+
+static void fmbim_ctrl_response_available_with_lock(struct f_mbim *dev, char need_lock)
+{
+	struct usb_request		*req = dev->notify_req;
+	struct usb_cdc_notification	*event = NULL;
+	unsigned long			flags;
+	int				ret;
+	struct ctrl_pkt		*cpkt = NULL;
+
+	int notif_c = 0;
+
+	if (need_lock)
+		spin_lock_irqsave(&dev->lock, flags);
+
+	if (!atomic_read(&dev->online)) {
+		pr_info("dev:%p is not online\n", dev);
+		if (need_lock)
+			spin_unlock_irqrestore(&dev->lock, flags);
+		return;
+	}
+
+	if (!req) {
+		/* Hold up to 64 responses in the circular queue */
+		atomic_inc(&dev->response_available);
+		pr_info_ratelimited("notify_req is NULL, try to queue the response, increment response available to %d\n",
+			 atomic_read(&dev->response_available));
+		if (atomic_read(&dev->response_available) > MBIM_MAX_RESP_Q) {
+			cpkt = list_first_entry(&dev->cpkt_resp_q, struct ctrl_pkt, list);
+			BUG_ON(!cpkt);
+			list_del(&cpkt->list);
+			pr_info("cpkt=%p, transaction id=%d, removed from response queue head (too much responses)"
+				, cpkt, cpkt->transaction_id);
+			mbim_free_ctrl_pkt(cpkt);
+			atomic_dec(&dev->response_available);
+		}
+		if (need_lock)
+			spin_unlock_irqrestore(&dev->lock, flags);
+		return;
+	}
+
+	if (!req->buf) {
+		pr_info("dev:%p req->buf is NULL\n", dev);
+		if (need_lock)
+			spin_unlock_irqrestore(&dev->lock, flags);
+		return;
+	}
+
+	notif_c = atomic_inc_return(&dev->notify_count);
+	pr_info_ratelimited("atomic_inc_return[notif_c] = %d\n", notif_c);
+
+	event = req->buf;
+	event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+			| USB_RECIP_INTERFACE;
+	event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE;
+	event->wValue = cpu_to_le16(0);
+	event->wIndex = cpu_to_le16(dev->ctrl_id);
+	event->wLength = cpu_to_le16(0);
+	dev->notify_req = NULL;
+
+	if (need_lock)
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+	pr_debug("Call usb_ep_queue\n");
+
+	ret = usb_ep_queue(dev->notify, req, GFP_ATOMIC);
+	if (ret) {
+		dev->notify_req = req;
+		atomic_dec(&dev->notify_count);
+		pr_err("ep enqueue error %d\n", ret);
+	}
+}
+static void fmbim_ctrl_response_available(struct f_mbim *dev)
+{
+	/* Tell fmbim_ctrl_response_available_ext to use lock */
+	fmbim_ctrl_response_available_with_lock(dev,1);
+}
+
+static int
+fmbim_send_cpkt_response(struct f_mbim *gr, struct ctrl_pkt *cpkt)
+{
+	struct f_mbim	*dev = gr;
+	unsigned long	flags;
+
+	if (!gr || !cpkt) {
+		pr_err("Invalid cpkt, dev:%p cpkt:%p\n",
+				gr, cpkt);
+		return -ENODEV;
+	}
+
+	if (!atomic_read(&dev->online)) {
+		pr_info("dev:%p is not connected\n", dev);
+		mbim_free_ctrl_pkt(cpkt);
+		return 0;
+	}
+
+	pr_debug("Add packet to cpkt_resp_q trans_id=%d\n", cpkt->transaction_id);
+	spin_lock_irqsave(&dev->lock, flags);
+	list_add_tail(&cpkt->list, &dev->cpkt_resp_q);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	fmbim_ctrl_response_available(dev);
+
+	return 0;
+}
+
+static void mbim_reset_values(struct f_mbim *mbim)
+{
+	mbim->parser_opts = &ndp16_opts;
+	mbim->is_crc = false;
+	mbim->port.cdc_filter = DEFAULT_FILTER;
+	mbim->ntb_input_size = le32_to_cpu(ntb_parameters.dwNtbInMaxSize);;
+	mbim->port.header_len = USB_CDC_NCM_MIN_HEADER16_SIZE;
+	atomic_set(&mbim->notify_count, 0);
+	atomic_set(&mbim->response_available, 0);
+	atomic_set(&mbim->online, 0);
+	mbim->port.fixed_out_len = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+	mbim->port.fixed_in_len = le32_to_cpu(ntb_parameters.dwNtbInMaxSize);
+}
+
+static void mbim_reset_function_queue(struct f_mbim *dev)
+{
+	struct ctrl_pkt	*cpkt = NULL;
+
+	spin_lock(&dev->lock);
+
+	cpkt = mbim_alloc_ctrl_pkt(0, GFP_ATOMIC);
+	if (!cpkt) {
+		pr_err("Unable to allocate reset function pkt\n");
+		spin_unlock(&dev->lock);
+		return;
+	}
+
+	list_add_tail(&cpkt->list, &dev->cpkt_req_q);
+	spin_unlock(&dev->lock);
+
+	wake_up(&dev->read_wq);
+}
+
+static void fmbim_reset_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_mbim		*dev = req->context;
+
+	mbim_reset_function_queue(dev);
+}
+
+static void mbim_clear_queues(struct f_mbim *mbim)
+{
+	struct ctrl_pkt	*cpkt = NULL;
+	struct list_head *act, *tmp;
+
+	spin_lock(&mbim->lock);
+	list_for_each_safe(act, tmp, &mbim->cpkt_req_q) {
+		cpkt = list_entry(act, struct ctrl_pkt, list);
+		list_del(&cpkt->list);
+		mbim_free_ctrl_pkt(cpkt);
+	}
+	list_for_each_safe(act, tmp, &mbim->cpkt_resp_q) {
+		cpkt = list_entry(act, struct ctrl_pkt, list);
+		list_del(&cpkt->list);
+		mbim_free_ctrl_pkt(cpkt);
+	}
+	spin_unlock(&mbim->lock);
+}
+/*
+ * Context: mbim->lock held
+ */
+static void mbim_do_notify(struct f_mbim *mbim)
+{
+	struct usb_request		*req = mbim->notify_req;
+	struct usb_cdc_notification	*event;
+	struct usb_composite_dev	*cdev = mbim->port.func.config->cdev;
+	struct usb_cdc_speed_change	*speed_data;
+	int				status;
+
+	pr_info("state = %d\n", mbim->notify_state);
+
+	if (unlikely(!req)) {
+		pr_err("No request available\n");
+		return;
+	}
+
+	event = req->buf;
+	switch (mbim->notify_state) {
+	case MBIM_NOTIFY_NONE:
+		return;
+
+	case MBIM_NOTIFY_CONNECT:
+		event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+		if (mbim->is_open)
+			event->wValue = cpu_to_le16(1);
+		else
+			event->wValue = cpu_to_le16(0);
+		event->wLength = 0;
+		req->length = sizeof(struct usb_cdc_notification);
+
+		pr_info("notify connect %s\n", mbim->is_open ? "true" : "false");
+		mbim->notify_state = MBIM_NOTIFY_NONE;
+		break;
+
+	case MBIM_NOTIFY_SPEED:
+		event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(8);
+
+		/* SPEED_CHANGE data is up/down speeds in bits/sec */
+		speed_data = (req->buf + sizeof(struct usb_cdc_notification));
+		speed_data->DLBitRRate = cpu_to_le32(mbim_bitrate(cdev->gadget));
+		speed_data->ULBitRate = speed_data->DLBitRRate;
+
+		req->length = (sizeof(struct usb_cdc_notification) + sizeof(struct usb_cdc_speed_change));
+
+		pr_info("notify speed %d bps\n", mbim_bitrate(cdev->gadget));
+		mbim->notify_state = MBIM_NOTIFY_CONNECT;
+		break;
+	}
+	event->bmRequestType = 0xA1;
+	event->wIndex = cpu_to_le16(mbim->ctrl_id);
+
+	atomic_inc(&mbim->notify_count);
+	pr_info("notif_c = %d\n", atomic_read(&mbim->notify_count));
+	mbim->notify_req = NULL;
+	/*
+	 * In double buffering if there is a space in FIFO,
+	 * completion callback can be called right after the call,
+	 * so unlocking
+	 */
+	spin_unlock(&mbim->lock);
+	status = usb_ep_queue(mbim->notify, req, GFP_ATOMIC);
+	spin_lock(&mbim->lock);
+	if (status < 0) {
+		mbim->notify_req = req;
+		atomic_dec(&mbim->notify_count);
+		mbim->notify_state = MBIM_NOTIFY_NONE;
+		pr_err("usb_ep_queue failed, err: %d\n", status);
+	}
+}
+
+/*
+ * Context: mbim->lock held
+ */
+static void mbim_notify(struct f_mbim *mbim)
+{
+	/*
+	 * NOTE on most versions of Linux, host side cdc-ethernet
+	 * won't listen for notifications until its netdevice opens.
+	 * The first notification then sits in the FIFO for a long
+	 * time, and the second one is queued.
+	 *
+	 * If mbim_notify() is called before the second (CONNECT)
+	 * notification is sent, then it will reset to send the SPEED
+	 * notificaion again (and again, and again), but it's not a problem
+	 */
+	if (mbim->notify_state == MBIM_NOTIFY_NONE) {
+		mbim->notify_state = MBIM_NOTIFY_SPEED;
+		mbim_do_notify(mbim);
+	} else {
+		pr_info("skipped, notify in progress (state=%d)\n",
+			  mbim->notify_state);
+	}
+}
+
+static void mbim_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_mbim			*mbim = req->context;
+	struct usb_cdc_notification	*event = req->buf;
+	int notif_c;
+
+	spin_lock(&mbim->lock);
+	switch (req->status) {
+	case 0:
+		pr_info_ratelimited("Notification 0x%02x sent\n", event->bNotificationType);
+		if (unlikely(!atomic_read(&mbim->online))) {
+			pr_info("%s: dev is offline\n", __func__);
+			pr_info("notif_c = %d\n", atomic_read(&mbim->notify_count));
+			mbim->notify_state = MBIM_NOTIFY_NONE;
+			/* atomic_set(&mbim->notify_count, 0); */
+			mbim->notify_req = req;
+			spin_unlock(&mbim->lock);
+			return;
+		}
+
+		notif_c = atomic_dec_return(&mbim->notify_count);
+		if (unlikely(notif_c)) {
+			pr_err("notification count != 0 (%d)\n", notif_c);
+			BUG();
+		}
+
+		if (event->bNotificationType == USB_CDC_NOTIFY_RESPONSE_AVAILABLE) {
+			BUG_ON(mbim->notify_state != MBIM_NOTIFY_NONE);
+			mbim->notify_req = req;
+#ifndef SERIAL_RESPONSE
+			if (atomic_read(&mbim->response_available)) {
+				pr_debug("Response available=%d (was busy"
+					  "before), try to send it again.\n",
+					  atomic_read(&mbim->response_available));
+				atomic_dec(&mbim->response_available);
+				fmbim_ctrl_response_available_with_lock(mbim,0);
+			}
+#endif
+			spin_unlock(&mbim->lock);
+			return;
+		} else if (event->bNotificationType == USB_CDC_NOTIFY_SPEED_CHANGE ||
+			   event->bNotificationType == USB_CDC_NOTIFY_NETWORK_CONNECTION) {
+			pr_debug("Continue to mbim_do_notify()\n");
+			break;
+		} else {
+			pr_err("Unknown notification!!!\n");
+			BUG();
+		}
+		break;
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		pr_info("ESHUTDOWN/ECONNRESET, connection gone\n");
+		mbim->notify_state = MBIM_NOTIFY_NONE;
+		atomic_set(&mbim->online, 0);
+		atomic_set(&mbim->notify_count, 0);
+		atomic_set(&mbim->response_available, 0);
+		spin_unlock(&mbim->lock);
+		mbim_clear_queues(mbim);
+		mbim_reset_function_queue(mbim);
+		spin_lock(&mbim->lock);
+		break;
+	default:
+		pr_err("Unknown event %02x --> %d\n",
+			event->bNotificationType, req->status);
+		break;
+	}
+
+	mbim->notify_req = req;
+	mbim_do_notify(mbim);
+
+	spin_unlock(&mbim->lock);
+}
+
+static void mbim_ep0out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	/* now for SET_NTB_INPUT_SIZE only */
+	unsigned		in_size = 0;
+	struct usb_function	*f = req->context;
+	struct f_mbim		*mbim = func_to_mbim(f);
+	struct mbim_ntb_input_size {
+		u32	ntb_input_size;
+		u16	ntb_max_datagrams;
+		u16	reserved;
+	} *ntb = NULL;
+	req->context = NULL;
+	if (req->status || req->actual != req->length) {
+		pr_err("Bad control-OUT transfer\n");
+		goto invalid;
+	}
+
+	if (req->length == 4) {
+		in_size = get_unaligned_le32(req->buf);
+		if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+		    in_size > mbim->ntb_input_size) {
+			pr_err("Illegal INPUT SIZE (%d) from host\n", in_size);
+			goto invalid;
+		}
+	} else if (req->length == 8) {
+		ntb = (struct mbim_ntb_input_size *)req->buf;
+		in_size = get_unaligned_le32(&(ntb->ntb_input_size));
+		if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+		    in_size > mbim->ntb_input_size) {
+			pr_err("Illegal INPUT SIZE (%d) from host\n", in_size);
+			goto invalid;
+		}
+		mbim->ntb_max_datagrams =
+			get_unaligned_le16(&(ntb->ntb_max_datagrams));
+	} else {
+		pr_err("Illegal NTB length %d\n", in_size);
+		goto invalid;
+	}
+
+	pr_debug("Set NTB INPUT SIZE %d\n", in_size);
+
+	mbim->ntb_input_size = in_size;
+	mbim->port.fixed_in_len = in_size;
+	return;
+
+invalid:
+	usb_ep_set_halt(ep);
+
+	pr_err("dev:%p Failed\n", mbim);
+
+	return;
+}
+
+static void
+fmbim_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_mbim		*dev = req->context;
+	struct ctrl_pkt		*cpkt = NULL;
+	int			len = req->actual;
+	struct usb_cdc_mbim_ctrl_msg *ctrl_msg = req->buf;
+
+	if (!dev) {
+		pr_err("mbim dev is null\n");
+		return;
+	}
+
+	if (req->status < 0) {
+		pr_err("mbim command error %d\n", req->status);
+		return;
+	}
+
+	spin_lock(&dev->lock);
+	cpkt = mbim_alloc_ctrl_pkt(len, GFP_ATOMIC);
+	if (!cpkt) {
+		pr_err("Unable to allocate ctrl pkt\n");
+		spin_unlock(&dev->lock);
+		return;
+	}
+
+	pr_debug("Add to cpkt_req_q packet with len = %d\n", len);
+	pr_debug("MessagType = %d, TransactionId = %d\n",
+		le32_to_cpu(ctrl_msg->dwMessagType),
+		le32_to_cpu(ctrl_msg->dwTransactionId));
+	cpkt->transaction_id = le32_to_cpu(ctrl_msg->dwTransactionId);
+	memcpy(cpkt->buf, req->buf, len);
+	list_add_tail(&cpkt->list, &dev->cpkt_req_q);
+	spin_unlock(&dev->lock);
+
+	/* wakeup read thread */
+	pr_debug("Wake up read queue\n");
+	wake_up(&dev->read_wq);
+
+	return;
+}
+
+static int
+mbim_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_mbim		*mbim = func_to_mbim(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	struct ctrl_pkt		*cpkt = NULL;
+	int	value = -EOPNOTSUPP;
+	u16	w_index = le16_to_cpu(ctrl->wIndex);
+	u16	w_value = le16_to_cpu(ctrl->wValue);
+	u16	w_length = le16_to_cpu(ctrl->wLength);
+	int	response_available = 0;
+
+	/*
+	 * composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 */
+
+	/*
+	 * For NCM+MBIM function we need to define and handle
+	 * Microsoft OS specific strings/requests. This allows Win8
+	 * to select correct driver (MBIM) for NCM+MBIM fallback mode
+	 */
+	if (ctrl->bRequestType ==
+			(USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+			&& ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+			&& (w_value >> 8) == USB_DT_STRING
+			&& (w_value & 0xFF) == OS_STRING_ID) {
+
+		pr_debug("OS string request: %d index: %d value: %d length: %d\n",
+			ctrl->bRequest, w_index, w_value, w_length);
+
+		if(mbim->mode == MODE_NCM_MBIM) {
+				value = (w_length < sizeof(mbim_os_string)
+				? w_length : sizeof(mbim_os_string));
+				memcpy(cdev->req->buf, mbim_os_string, value);
+		}
+
+		/* return here since composite.c will send for us */
+		return value;
+	}
+
+	/* Handle MBIM OS descriptor */
+	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR
+			&& (ctrl->bRequest == USB_VENDOR_OSVC_DEFAULT)
+			&& (ctrl->bRequestType & USB_DIR_IN)
+			&& (w_index == MS_EXTENDED_COMPAT_ID
+			|| w_index == MS_EXTENDED_PROPERTY)) {
+
+		pr_debug("vendor request: %d index: %d value: %d length: %d\n",
+			ctrl->bRequest, w_index, w_value, w_length);
+
+		if (mbim->mode == MODE_NCM_MBIM) {
+			value = (w_length < sizeof(mbim_ext_config_desc) ?
+					w_length : sizeof(mbim_ext_config_desc));
+			memcpy(cdev->req->buf, &mbim_ext_config_desc, value);
+		}
+
+		return value;
+	}
+
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SET_ETHERNET_PACKET_FILTER:
+		/*
+		 * see 6.2.30: no data, wIndex = interface,
+		 * wValue = packet filter bitmap
+		 */
+		if (w_length != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		pr_info_ratelimited("packet filter %02x\n", w_value);
+		/*
+		 * REVISIT locking of cdc_filter.  This assumes the UDC
+		 * driver won't have a concurrent packet TX irq running on
+		 * another CPU; or that if it does, this write is atomic...
+		 */
+		mbim->port.cdc_filter = w_value;
+		value = 0;
+		break;
+	/*
+	 * and optionally:
+	 * case USB_CDC_SEND_ENCAPSULATED_COMMAND:
+	 * case USB_CDC_GET_ENCAPSULATED_RESPONSE:
+	 * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
+	 * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_STATISTIC:
+	 */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_RESET_FUNCTION:
+
+		pr_info_ratelimited("USB_CDC_RESET_FUNCTION\n");
+		value = 0;
+		req->complete = fmbim_reset_cmd_complete;
+		req->context = mbim;
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SEND_ENCAPSULATED_COMMAND:
+
+		pr_info_ratelimited("USB_CDC_SEND_ENCAPSULATED_COMMAND\n");
+
+		value = w_length;
+		req->complete = fmbim_cmd_complete;
+		req->context = mbim;
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_ENCAPSULATED_RESPONSE:
+
+		pr_debug("USB_CDC_GET_ENCAPSULATED_RESPONSE\n");
+
+		if (w_value) {
+			pr_err("w_value > 0: %d\n", w_value);
+			break;
+		}
+
+		spin_lock(&mbim->lock);
+		if (list_empty(&mbim->cpkt_resp_q)) {
+			pr_err("ctrl resp queue empty\n");
+			spin_unlock(&mbim->lock);
+			break;
+		}
+
+		response_available = atomic_read(&mbim->response_available);
+
+		cpkt = list_first_entry(&mbim->cpkt_resp_q,
+					struct ctrl_pkt, list);
+		list_del(&cpkt->list);
+		spin_unlock(&mbim->lock);
+
+		value = min_t(unsigned, w_length, cpkt->len);
+		memcpy(req->buf, cpkt->buf, value);
+		pr_debug("cpkt=%p, transaction id=%d, \n", cpkt, cpkt->transaction_id);
+		mbim_free_ctrl_pkt(cpkt);
+
+		pr_info_ratelimited("copied encapsulated_response %d bytes\n",
+			value);
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_PARAMETERS:
+
+		pr_info_ratelimited("USB_CDC_GET_NTB_PARAMETERS\n");
+
+		if (w_length == 0 || w_value != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		value = w_length > sizeof ntb_parameters ?
+			sizeof ntb_parameters : w_length;
+		memcpy(req->buf, &ntb_parameters, value);
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_INPUT_SIZE:
+
+		pr_info_ratelimited("USB_CDC_GET_NTB_INPUT_SIZE\n");
+
+		if (w_length < 4 || w_value != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		put_unaligned_le32(mbim->ntb_input_size, req->buf);
+		value = 4;
+		pr_info_ratelimited("Reply to host INPUT SIZE %d\n",
+		     mbim->ntb_input_size);
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_NTB_INPUT_SIZE:
+
+		pr_info_ratelimited("USB_CDC_SET_NTB_INPUT_SIZE\n");
+
+		if (w_length != 4 && w_length != 8) {
+			pr_err("wrong NTB length %d\n", w_length);
+			goto invalid;;
+		}
+
+		if (w_value != 0 || w_index != mbim->ctrl_id)
+			goto invalid;;
+
+		req->complete = mbim_ep0out_complete;
+		req->length = w_length;
+		req->context = f;
+
+		value = req->length;
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_FORMAT:
+	{
+		uint16_t format;
+
+		pr_info_ratelimited("USB_CDC_GET_NTB_FORMAT\n");
+
+		if (w_length < 2 || w_value != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		format = (mbim->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001;
+		put_unaligned_le16(format, req->buf);
+		value = 2;
+		pr_info_ratelimited("NTB FORMAT: sending %d\n", format);
+		break;
+	}
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_NTB_FORMAT:
+	{
+		pr_info_ratelimited("USB_CDC_SET_NTB_FORMAT ");
+
+		if (w_length != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		switch (w_value) {
+		case 0x0000:
+			mbim->parser_opts = &ndp16_opts;
+			pr_info_ratelimited("MBIM16 selected\n");
+			break;
+		case 0x0001:
+			mbim->parser_opts = &ndp32_opts;
+			pr_info_ratelimited("MBIM32 selected\n");
+			break;
+		default:
+			goto invalid;
+		}
+		value = 0;
+		break;
+	}
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_CRC_MODE:
+	{
+		uint16_t is_crc;
+
+		if (w_length < 2 || w_value != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		is_crc = mbim->is_crc ? 0x0001 : 0x0000;
+		put_unaligned_le16(is_crc, req->buf);
+		value = 2;
+		pr_debug("Host asked CRC MODE, sending %d\n", is_crc);
+		break;
+	}
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_CRC_MODE:
+	{
+		int ndp_hdr_crc = 0;
+
+		if (w_length != 0 || w_index != mbim->ctrl_id)
+			goto invalid;
+		switch (w_value) {
+		case 0x0000:
+			mbim->is_crc = false;
+			ndp_hdr_crc = NCM_NDP_HDR_NOCRC;
+			pr_debug("non-CRC mode selected\n");
+			break;
+		case 0x0001:
+			mbim->is_crc = true;
+			ndp_hdr_crc = NCM_NDP_HDR_CRC;
+			pr_debug("CRC mode selected\n");
+			break;
+		default:
+			goto invalid;
+		}
+		mbim->parser_opts->ndp_sign &= ~NCM_NDP_HDR_CRC_MASK;
+		mbim->parser_opts->ndp_sign |= ndp_hdr_crc;
+		value = 0;
+		break;
+	}
+
+	/* and disabled in mbim descriptor: */
+	/* case USB_CDC_GET_NET_ADDRESS: */
+	/* case USB_CDC_SET_NET_ADDRESS: */
+	/* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */
+	/* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */
+
+	default:
+invalid:
+	pr_err("invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		pr_debug("control request: %02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = (value < w_length);
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			pr_err("queueing req failed: %02x.%02x, err %d\n",
+				ctrl->bRequestType,
+			       ctrl->bRequest, value);
+		}
+#ifdef SERIAL_RESPONSE
+		if (response_available) {
+			pr_debug("Response available=%d (was busy before), try to send it again.\n", response_available);
+			atomic_dec(&mbim->response_available);
+			fmbim_ctrl_response_available(mbim);
+		}
+#endif
+	} else {
+		pr_err("ctrl req err %d: %02x.%02x v%04x i%04x l%d\n",
+			value, ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int mbim_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	int ret;
+	struct f_mbim		*mbim = func_to_mbim(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* Control interface has only altsetting 0 */
+	if (intf == mbim->ctrl_id) {
+
+		pr_info("CONTROL_INTERFACE: alt = %d\n", alt);
+
+		if ((mbim->mode != MODE_NCM_MBIM && alt != 0) ||
+				(mbim->mode == MODE_NCM_MBIM && alt > 1))
+			goto fail;
+
+		if (mbim->notify->driver_data) {
+			pr_info("reset mbim control %d\n", intf);
+			usb_ep_disable(mbim->notify);
+		}
+
+		if (mbim->mode == MODE_NCM || (mbim->mode == MODE_NCM_MBIM && alt == 0))
+			mbim->state = STATE_NCM;
+		else if (mbim->mode == MODE_MBIM || (mbim->mode == MODE_NCM_MBIM && alt == 1))
+			mbim->state = STATE_MBIM;
+		else
+			BUG();
+
+		if (!(mbim->notify->desc)) {
+			DBG(cdev, "init mbim ctrl %d\n", intf);
+			if (config_ep_by_speed(cdev->gadget, f, mbim->notify))
+				goto fail;
+		}
+
+		usb_ep_enable(mbim->notify);
+		mbim->notify->driver_data = mbim;
+
+	/* Data interface has up to 3 altsettings, 0-2 */
+	} else if (intf == mbim->data_id) {
+
+		pr_info("DATA_INTERFACE: alt = %d\n", alt);
+
+		if ((mbim->mode != MODE_NCM_MBIM && alt > 1) ||
+				(mbim->mode == MODE_NCM_MBIM && alt > 2))
+			goto fail;
+
+		if (mbim->port.in_ep->driver_data) {
+			pr_info("reset mbim\n");
+			/* notify req is still queued on usb controller
+			* suppose this should return the req to mbim->notify_req
+ 			*/
+			if (unlikely(NULL == mbim->notify_req)) {
+				ret = usb_ep_dequeue(mbim->notify, g_notify_req);
+				pr_info("dequeue notify_req done ret: %d\n", ret);
+			}
+
+			mbim_reset_values(mbim);
+			mbim_clear_queues(mbim);
+			mbim_reset_function_queue(mbim);
+			gether_disconnect(&mbim->port);
+		}
+
+		/*
+		 * CDC Network only sends data in non-default altsettings.
+		 * Changing altsettings resets filters, statistics, etc.
+		 */
+		if (alt == 1 || alt == 2) {
+			struct net_device	*net;
+			pr_info("Alt set %d, initialize ports\n", alt);
+
+			if (!mbim->port.in_ep->desc ||
+			    !mbim->port.out_ep->desc) {
+				pr_info("init mbim\n");
+				if (config_ep_by_speed(cdev->gadget, f,
+						mbim->port.in_ep) ||
+				    config_ep_by_speed(cdev->gadget, f,
+						mbim->port.out_ep)) {
+					mbim->port.in_ep->desc = NULL;
+					mbim->port.out_ep->desc = NULL;
+					goto fail;
+				}
+			}
+
+			if (mbim->state == STATE_NCM) {
+				mbim->port.open = mbim_net_open;
+				mbim->port.close = mbim_net_close;
+#ifdef CONFIG_USB_G_NCM_MULT_PKT_SUPPORT
+				mbim->port.wrap = ncm_wrap_ntb_multipkt;
+#else
+				mbim->port.wrap = ncm_wrap_ntb;
+#endif
+				mbim->port.unwrap = ncm_unwrap_ntb;
+				mbim->port.unwrap_fixup = NULL;
+			} else { /* STATE_MBIM */
+				/* Ruslan: originally was commented out in this driver*/
+				mbim->port.open = NULL;
+				mbim->port.close = NULL;
+
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+				mbim->port.wrap = mbim_wrap_ntb_multipkt;
+#ifdef CONFIG_USBNET_USE_SG
+				mbim->port.is_sg_mode = 1;
+#endif
+#else
+				mbim->port.wrap = mbim_wrap_ntb;
+#endif
+				mbim->port.unwrap = mbim_unwrap_ntb;
+#ifndef CONFIG_ASR_TOE
+				mbim->port.unwrap_fixup = mbim_process_dgram;
+#endif
+			}
+			/* TODO */
+			/* Enable zlps by default for MBIM conformance;
+			 * override for musb_hdrc (avoids txdma ovhead)
+			 */
+			mbim->port.is_zlp_ok = !(
+				gadget_is_musbhdrc(cdev->gadget)
+				);
+			mbim->port.cdc_filter = DEFAULT_FILTER;
+
+			atomic_set(&mbim->online, 1);
+
+			spin_lock(&mbim->lock);
+			if (mbim->is_open)
+				mbim_notify(mbim);
+			spin_unlock(&mbim->lock);
+
+#ifdef CONFIG_ASR_TOE
+			if (mbim->state == STATE_NCM) {
+				mbim->port.ueth_type = UETHER_NCM;
+			} else { /* STATE_MBIM */
+				mbim->port.ueth_type = UETHER_MBIM;
+			}
+#endif
+			pr_info("activate mbim\n");
+			net = gether_connect(&mbim->port);
+			if (IS_ERR(net))
+				return PTR_ERR(net);
+		}
+	} else
+		goto fail;
+
+	/* wakeup file threads */
+	wake_up(&mbim->open_wq);
+	wake_up(&mbim->read_wq);
+	wake_up(&mbim->write_wq);
+
+	return 0;
+
+fail:
+	pr_err("ERROR: Illegal Interface\n");
+	return -EINVAL;
+}
+
+/*
+ * Because the data interface supports multiple altsettings,
+ * this MBIM function *MUST* implement a get_alt() method.
+ */
+static int mbim_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_mbim		*mbim = func_to_mbim(f);
+
+	if (intf == mbim->ctrl_id)
+		return 0;
+	return mbim->port.in_ep->driver_data ? 1 : 0;
+}
+
+#define MBIM_SK_CB_SIGN		(0x4D49424D) /* MBIM */
+
+struct mbim_cb {
+	u32 sign;
+	u16 tci;
+};
+
+#define MBIM_SKB_CB(skb) ((struct mbim_cb *)((skb)->cb))
+struct nth {
+	__le32	dwSignature;
+	__le16	wHeaderLength;
+	__le16	wSequence;
+	union {
+		struct {
+			__le16  wBlockLength;
+			__le16  wFpIndex;
+		} nth16 __attribute__ ((packed));
+		struct {
+			__le32  wBlockLength;
+			__le32  wFpIndex;
+		} nth32 __attribute__ ((packed));
+	} nth_type;
+} __attribute__ ((packed));
+
+struct ndp {
+	__le32	dwSignature;
+	__le16	wLength;
+	union {
+		struct {
+			__le16  wNextFpIndex;
+		} ndp16 __attribute__ ((packed));
+		struct {
+			__le16	wReserved6;
+			__le32  wNextFpIndex;
+			__le32  wReserved12;
+		} ndp32 __attribute__ ((packed));
+	} ndp_type;
+} __attribute__ ((packed));
+
+
+#define NCM_16BIT_SIGN	(0x484d434e)
+
+#define NTH_FIELD(nth_header, field) \
+	(mbim->parser_opts->nth_sign == NCM_16BIT_SIGN ?	\
+	(void *)&(nth_header->nth_type.nth16.field) :	\
+	(void *)&(nth_header->nth_type.nth32.field))
+
+#define NDP_FIELD(ndp_header, field) \
+	(mbim->parser_opts->nth_sign == NCM_16BIT_SIGN ?	\
+	(void *)&(ndp_header->ndp_type.ndp16.field) :	\
+	(void *)&(ndp_header->ndp_type.ndp32.field))
+
+#define NTB_MAX_HEADER_SIZE(skb) (*(unsigned int *)skb->cb)
+
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+#ifndef CONFIG_USBNET_USE_SG
+#define MBIM_NOT_USE_SG
+#endif
+#endif
+
+#ifndef CONFIG_ASR_TOE
+#if (defined CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS) || (defined MBIM_NOT_USE_SG)
+static struct sk_buff *add_ndp_header(struct gether *port,
+				struct sk_buff *skb, struct aggr_ctx *aggr_ctx, __le32 sign)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	struct sk_buff *first_skb, *prev_skb;
+	int		ncb_len;
+	struct ndp *ndp_header;
+	__le16		*tmp;
+	__le16		offset = 0;
+	int		ndp_len = 0, ndp_align = ntb_parameters.wNdpInAlignment;
+	int		div = ntb_parameters.wNdpInDivisor;
+	int		rem = ntb_parameters.wNdpInPayloadRemainder;
+	int		pad = 0;
+	int		ndp_pad;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+
+	ndp_len = opts->ndp_size;
+	ndp_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ndp_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(skb_queue_empty(&aggr_ctx->skb_list));
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(!aggr_ctx->total_size);
+
+	ndp_header = (void *)skb_push(skb, ndp_len);
+	memset(ndp_header, 0, ndp_len);
+
+	/* NDP */
+	put_unaligned_le32(sign, &ndp_header->dwSignature);
+	put_unaligned_le16(ndp_len, &ndp_header->wLength);
+
+	tmp = (void *)ndp_header + opts->ndp_size;
+	/* (d)wDatagramIndex[0] */
+	put_mbim(&tmp, opts->dgram_item_len, skb->data + ndp_len - first_skb->data);
+	/* (d)wDatagramLength[0] */
+	put_mbim(&tmp, opts->dgram_item_len, skb->len - ndp_len);
+	/* (d)wDatagramIndex[1] and  (d)wDatagramLength[1] already zeroed */
+	if (unlikely(mbim->is_crc)) {
+		if (mbim->mode == MODE_MBIM) {
+			pr_err("MBIM doesn't support CRC!!\n");
+		} else{
+			pr_err("NCM fast-path doesn't support CRC in multiple packets!!"
+					   "CI needs to allocate additional space for CRC at the end of the packet.\n");
+		}
+		BUG();
+	}
+
+	/* update prev NDP header with new NDP index */
+	if (first_skb == prev_skb) {
+		ncb_len = opts->nth_size;
+		ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+		ncb_len += ndp_pad;
+		pad = ALIGN(ncb_len, div) + rem - ncb_len;
+		ncb_len += pad;
+		offset = ncb_len;
+	}
+	ndp_header = (void *)(prev_skb->data + offset);
+	put_mbim_no_inc(NDP_FIELD(ndp_header, wNextFpIndex), opts->next_fp_index, skb->data - first_skb->data);
+	return skb;
+}
+#endif
+#endif
+
+
+static struct sk_buff *mbim_wrap_ntb(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	struct sk_buff	*skb2;
+	int		ncb_len = 0;
+	int		headroom;
+	__le16		*tmp;
+	int		div = ntb_parameters.wNdpInDivisor;
+	int		rem = ntb_parameters.wNdpInPayloadRemainder;
+	int		pad;
+	struct	nth *nth_header;
+	struct	ndp *ndp_header;
+	int		ndp_align = ntb_parameters.wNdpInAlignment;
+	int		ndp_pad;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	unsigned	crc_len = mbim->is_crc ? sizeof(uint32_t) : 0;
+	u16 tci = 0;
+	u8 *c = NULL;
+	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN);
+	struct net_device *net_dev = port->ioport->net;
+
+	BUG_ON(!skb);
+	if (skb->len <= ETH_HLEN)
+		goto error;
+
+	/* Reply for outgoing ARP request cause MBIM HOST will NOT answer! */
+	if (((struct ethhdr *)skb->data)->h_proto == htons(ETH_P_ARP)) {
+		struct arphdr *arp = (struct arphdr *)(skb->data + ETH_HLEN);
+		if (arp->ar_op == htons(ARPOP_REQUEST)) {
+			unsigned char * arp_ptr = (unsigned char *)(arp + 1);
+			__be32 dest_ip, src_ip;
+			pr_info_ratelimited("Reply for outgoing ARP request cause MBIM HOST will NOT answer\n");
+			arp_ptr += ETH_ALEN;
+			memcpy(&dest_ip, arp_ptr, 4);
+			arp_ptr += 4;
+			arp_ptr += ETH_ALEN;
+			memcpy(&src_ip, arp_ptr, 4);
+			skb2 = arp_create(ARPOP_REPLY, ETH_P_ARP, dest_ip, net_dev, src_ip,
+				net_dev->dev_addr, port->ioport->host_mac, net_dev->dev_addr);
+			BUG_ON(!skb2);
+			dev_kfree_skb_any(skb);
+
+			skb2->protocol = eth_type_trans(skb2, net_dev);
+			net_dev->stats.rx_packets++;
+			net_dev->stats.rx_bytes += skb2->len;
+
+			if (netif_rx(skb2) != NET_RX_SUCCESS)
+				pr_err("Faild to send ARP response to myself\n");
+			/* This ARP request will be dropped (tx_dropped++) */
+			return NULL;
+		}
+	}
+
+	/* mapping VLANs to MBIM sessions:
+	 *   no tag     => IPS session <0>
+	 *   1 - 255    => IPS session <vlanid>
+	 *   256 - 511  => DSS session <vlanid - 256>
+	 *   512 - 4095 => unsupported, drop
+	 */
+	if (vlan_get_tag(skb, &tci) >= 0 ) {
+		switch (tci & 0x0f00) {
+		case 0x0000: /* VLAN ID 0 - 255 */
+			break;
+		case 0x0100: /* VLAN ID 256 - 511 */
+			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);
+			break;
+		default:
+			pr_err("unsupported tci=0x%04x\n", tci);
+			goto error;
+		}
+
+		c = (u8 *)&sign;
+		c[3] = tci;
+		skb_pull(skb, ETH_HLEN + VLAN_HLEN);
+		pr_debug("VLAN tci=0x%04x\n", tci);
+	} else {
+		skb_pull(skb, ETH_HLEN);
+	}
+
+	ncb_len += opts->nth_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += opts->ndp_size;
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+	pad = ALIGN(ncb_len, div) + rem - ncb_len;
+	ncb_len += pad;
+
+	if (ncb_len + skb->len + crc_len > max_size)
+		goto error;
+
+	if (unlikely(skb->cloned || (skb_headroom(skb) < ncb_len)
+		 || (skb_tailroom(skb) < crc_len))) {
+		skb2 = skb_copy_expand(skb, ncb_len, crc_len, GFP_ATOMIC);
+		BUG_ON(!skb2);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+	}
+
+	headroom = ncb_len;
+	// NTH
+	nth_header = (void *) skb_push(skb, headroom);
+	memset(nth_header, 0, headroom);
+
+	put_unaligned_le32(opts->nth_sign, &nth_header->dwSignature); // dwSignature	
+	put_unaligned_le16(opts->nth_size, &nth_header->wHeaderLength); // wHeaderLength
+	// skip wSequence
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, skb->len); //(d)wBlockLength
+	// the first pointer NTH_FIELD(nth_header, wFpIndex) is right after the NTH + align
+	put_mbim_no_inc(NTH_FIELD(nth_header, wFpIndex), opts->fp_index, opts->nth_size + ndp_pad); //wNdpIndex(wFpIndex)
+
+	// NDP
+	ndp_header = (void*)nth_header + opts->nth_size + ndp_pad;
+	put_unaligned_le32(sign, &ndp_header->dwSignature); // dwSignature
+	put_unaligned_le16(ncb_len - opts->nth_size - pad, &ndp_header->wLength); // wLength
+
+	// skip reserved (d)wNextNdpIndex ((d)wNextFpIndex)
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; 
+	tmp += opts->reserved2;
+
+	if (mbim->is_crc) {
+		pr_err("MBIM doesn't support CRC!!\n");
+		BUG();
+	}
+
+	tmp = (void *)ndp_header + opts->ndp_size;
+	put_mbim(&tmp, opts->dgram_item_len, headroom); // (d)wDatagramIndex[0]
+	put_mbim(&tmp, opts->dgram_item_len, skb->len - headroom); // (d)wDatagramLength[0]
+	// (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed
+
+	return skb;
+error:
+	if (skb)
+		dev_kfree_skb_any(skb);
+	return NULL;
+}
+
+#ifdef CONFIG_ASR_TOE
+static struct sk_buff *mbim_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{	
+	struct sk_buff *first_skb, *prev_skb;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list) && list_empty(&aggr_ctx->toe_list)) {
+		skb = mbim_wrap_ntb(port, skb, aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		aggr_ctx->is_toe = false;
+		return skb;
+	}
+
+	if (aggr_ctx->is_toe) {
+		aggr_ctx->pending_skb = skb;
+		first_skb = (struct sk_buff *)list_first_entry(&aggr_ctx->toe_list, struct toe_pkt_desc, list); 
+		return first_skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1 != skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+
+	aggr_ctx->pending_skb = skb;
+	return first_skb;
+}
+
+static void mbim_add_eth_header(struct gether *port, struct sk_buff *skb)
+{
+	struct ethhdr *header;
+	struct net_device *dev = port->ioport->net;
+	__be16 proto = htons(ETH_P_802_3);
+
+	if (!skb)
+		return;
+
+	if (MBIM_SKB_CB(skb)->tci < 256) { /* IPS session? */
+		switch (*(skb->data) & 0xf0) {
+		case 0x40:
+			proto = htons(ETH_P_IP);
+			break;
+		case 0x60:
+#if 0
+			if (is_neigh_solicit(buf, len))
+				do_neigh_solicit(dev, buf, tci);
+#endif
+			proto = htons(ETH_P_IPV6);
+			break;
+		default:
+			return;
+		}
+	}
+
+	if (skb_headroom(skb) < sizeof(*header))
+	{
+		pr_err("%s: Headroom is not enough to add eth header\n",__func__);
+		return;
+	}
+	header = (void *)skb_push(skb,sizeof(*header));
+	memset(header, 0, sizeof(*header));
+
+	header->h_proto = proto;
+	memcpy(header->h_source, port->ioport->host_mac, ETH_ALEN);
+	memcpy(header->h_dest, dev->dev_addr, ETH_ALEN);
+#if 0
+	/* map MBIM session to VLAN */
+	if (tci)
+		vlan_put_tag(skb, htons(ETH_P_8021Q), tci);
+#endif
+	v7_dma_flush_range((void *)header, (void *)((u32)header + sizeof(*header)));
+}
+
+#ifdef CONFIG_MBIM_INPUT_AS_RNDIS
+static void mbim_add_rndis_header(struct sk_buff *skb)
+{
+	struct rndis_packet_msg_type *header;
+
+	if (!skb)
+		return;
+	if (skb_headroom(skb) < sizeof(*header))
+	{
+		pr_err("%s: Headroom is not enough to add rndis header\n",__func__);
+		return;
+	}
+	header = (void *)skb_push(skb, sizeof(*header));
+
+	memset(header, 0, sizeof *header);
+	header->MessageType = cpu_to_le32(RNDIS_MSG_PACKET);
+	header->MessageLength = cpu_to_le32(skb->len);
+	header->DataOffset = cpu_to_le32(36);
+	header->DataLength = cpu_to_le32(skb->len - sizeof(*header));
+	v7_dma_flush_range((void *)header, (void *)((u32)header + sizeof(*header)));
+}
+#endif
+
+#define MBIM_SKB_HEADROOM_RESERVE 	(64)
+#define MBIM_SKB_COPY_ALLOC_LENGTH 	(1514 + MBIM_SKB_HEADROOM_RESERVE + NET_IP_ALIGN)
+static struct sk_buff *mbim_copy_skb(struct sk_buff *skb, unsigned offset, unsigned len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = bm_usb_alloc_skb(MBIM_SKB_COPY_ALLOC_LENGTH,  GFP_ATOMIC);
+	if (!skb2)
+	{
+		pr_info("%s: No memory, dropping packet.\n",__func__);
+		return NULL;
+	}	
+	skb_reserve(skb2, MBIM_SKB_HEADROOM_RESERVE);
+	memcpy(skb_put(skb2, len), (skb->data + offset), len);
+
+	return skb2;
+}
+
+static int mbim_unwrap_ntb(struct gether *port,
+			  struct sk_buff *skb,
+			  struct sk_buff_head *list)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	__le16		*tmp = (void *) skb->data;
+	unsigned	index, index2;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	struct sk_buff	*skb2 = NULL;
+	struct net_device *net_dev = port->ioport->net;
+	int		ret = -EINVAL;
+	unsigned	max_size = mbim->port.fixed_out_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int		dgram_counter;
+	u16 tci = 0;
+	u8 *c = NULL;
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		pr_info("Wrong NTH SIGN, skblen %d\n",
+			skb->len);
+		print_hex_dump(KERN_INFO, "HEAD:\n", DUMP_PREFIX_ADDRESS, 32, 1,
+			       skb->data, 32, false);
+		goto err;
+	}
+
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		pr_info("Wrong NTB headersize\n");
+		goto err;
+	}
+
+	tmp++; /* skip wSequence */
+	/* (d)wBlockLength */
+	if (get_mbim(&tmp, opts->block_length) > max_size) {
+		pr_info("OUT size exceeded\n");
+		goto err;
+	}
+
+	index = get_mbim(&tmp, opts->fp_index);
+	/* MBIM 3.2 */
+	if (((index % 4) != 0) && (index < opts->nth_size)) {
+		pr_info("Bad index: %x\n",
+			index);
+		goto err;
+	}
+
+	/* walk through NDP */
+	tmp = ((void *)skb->data) + index;
+	switch (get_unaligned_le32(tmp) & cpu_to_le32(0x00ffffff)) {
+	case USB_CDC_MBIM_NDP16_IPS_SIGN:
+	case USB_CDC_MBIM_NDP32_IPS_SIGN:
+		c = (u8 *)tmp;
+		tci = c[3];
+		break;
+	case USB_CDC_MBIM_NDP16_DSS_SIGN:
+	case USB_CDC_MBIM_NDP32_DSS_SIGN:
+		c = (u8 *)tmp;
+		tci = c[3] + 256;
+		break;
+	default:
+		pr_info("unsupported NDP signature <0x%08x>\n", le32_to_cpu(tmp));
+		goto err;
+	}
+
+	tmp += 2;
+	ndp_len = get_unaligned_le16(tmp++);
+	/*
+	 * MBIM 3.3.1
+	 * entry is 2 items
+	 * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+	 * minimal: struct usb_cdc_mbim_ndpX + normal entry + zero entry
+	 */
+	if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+	    || (ndp_len % opts->ndplen_align != 0)) {
+		pr_info("Bad NDP length: %x\n", ndp_len);
+		goto err;
+	}
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	ndp_len -= opts->ndp_size;
+	index2 = get_mbim(&tmp, opts->dgram_item_len);
+	dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+	dgram_counter = 0;
+
+	do {
+		index = index2;
+		dg_len = dg_len2;
+
+		if (mbim->is_crc) {
+			pr_err("MBIM doesn't support CRC!!\n");
+			BUG();
+		}
+		index2 = get_mbim(&tmp, opts->dgram_item_len);
+		dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+
+		if (index2 == 0 || dg_len2 == 0) {
+			skb2 = skb;
+		} else {
+			skb2 = mbim_copy_skb(skb, index, dg_len);
+			if (skb2 == NULL) {
+				goto err;
+			} else {
+				goto skb_nopull;
+			}
+		}
+
+		if (!skb_pull(skb2, index)) {
+			ret = -EOVERFLOW;
+			goto err;
+		}
+
+skb_nopull:
+		skb_trim(skb2, dg_len);
+
+		skb2->dev = net_dev;
+		MBIM_SKB_CB(skb2)->sign = MBIM_SK_CB_SIGN;
+		MBIM_SKB_CB(skb2)->tci = tci;
+
+		BUG_ON(tci > 255); //Fix this
+		/* map MBIM session to VLAN */
+		if (tci) {//maybe do it always, also for VLAN ID 0
+			pr_err("Adding VLAN\n");
+			BUG();
+			vlan_put_tag(skb2, htons(ETH_P_8021Q), tci);
+		}
+		
+		mbim_add_eth_header(port, skb2);
+#ifdef CONFIG_MBIM_INPUT_AS_RNDIS
+		mbim_add_rndis_header(skb2);
+#endif
+		skb_queue_tail(list, skb2);
+		ndp_len -= 2 * (opts->dgram_item_len * 2);
+		dgram_counter++;
+		if (index2 == 0 || dg_len2 == 0)
+			break;
+	} while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+	return 0;
+err:
+	skb_queue_purge(list);
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+
+#else  /* CONFIG_ASR_TOE */
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+#ifdef CONFIG_USBNET_USE_SG
+static struct sk_buff *mbim_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	int		aggregate_length, skb_qlen;
+	struct nth *nth_header;
+	struct ndp *ndp_header;
+	int		div = ntb_parameters.wNdpInDivisor;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int skb_len;
+	int reserve_headroom_size;
+	int single_entry_ndp_size = opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2);
+	struct sk_buff *first_skb, *prev_skb;
+	u16 tci = 0;
+	u8 *c = NULL;
+	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN);
+	__le16		*tmp;
+	int ndp_len;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	skb_qlen = skb_queue_len(&aggr_ctx->skb_list);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		skb = mbim_wrap_ntb(port,skb,aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+			aggr_ctx->num_sgs = 1;
+		} else {
+			aggr_ctx->total_size = 0;
+			aggr_ctx->num_sgs = 0;
+			pr_err("error: %s 1st pkt failed\n", __func__);
+		}		
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1!=skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+	
+	/* Reply for outgoing ARP request cause MBIM HOST will NOT answer! */
+	if (((struct ethhdr *)skb->data)->h_proto == htons(ETH_P_ARP)) {
+		struct arphdr *arp = (struct arphdr *)(skb->data + ETH_HLEN);
+		if (arp->ar_op == htons(ARPOP_REQUEST)) {
+			aggr_ctx->pending_skb = skb;
+			return first_skb;
+		}
+	}
+
+	skb_len = skb->len;
+	if (vlan_get_tag(skb, &tci) >= 0 ) {
+		skb_len = skb_len - ETH_HLEN - VLAN_HLEN;
+		reserve_headroom_size = skb_headroom(skb) + ETH_HLEN + VLAN_HLEN;
+	} else {
+		skb_len = skb_len - ETH_HLEN;
+		reserve_headroom_size = skb_headroom(skb) + ETH_HLEN;
+	}
+
+	skb_len += single_entry_ndp_size;
+	aggregate_length = aggr_ctx->total_size + skb_len;
+	
+	if((ALIGN(skb_len, div) - skb_len) > 0 ||
+			  (ALIGN(prev_skb->len, div) - prev_skb->len) > 0) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/* mbim aggregation condition */
+	if (aggregate_length > max_size ||
+		      (mbim->ntb_max_datagrams != 0 && mbim->ntb_max_datagrams <= skb_qlen) ||
+		      skb_qlen >= MBIM_MAX_DGRAMS_AGGREGATION ||
+		      skb_cloned(skb) ||
+		      reserve_headroom_size < single_entry_ndp_size ||
+			  aggr_ctx->num_sgs >= (USBNET_SG_NENTS - 1)) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/* mapping VLANs to MBIM sessions:
+	 *   no tag     => IPS session <0>
+	 *   1 - 255    => IPS session <vlanid>
+	 *   256 - 511  => DSS session <vlanid - 256>
+	 *   512 - 4095 => unsupported, drop
+	 */
+	if (vlan_get_tag(skb, &tci) >= 0 ) {
+		switch (tci & 0x0f00) {
+		case 0x0000: /* VLAN ID 0 - 255 */
+			break;
+		case 0x0100: /* VLAN ID 256 - 511 */
+			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);
+			break;
+		default:
+			pr_err("unsupported tci=0x%04x\n", tci);
+			goto error;
+		}
+		c = (u8 *)&sign;
+		c[3] = tci;
+		skb_pull(skb, ETH_HLEN + VLAN_HLEN);
+		pr_debug("VLAN tci=0x%04x\n", tci);
+	} else {
+		skb_pull(skb, ETH_HLEN);
+	}
+
+	// update NDP
+	ndp_len = opts->ndp_size;
+	ndp_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ndp_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+	ndp_header = (void *)skb_push(skb, ndp_len);
+	memset(ndp_header, 0, ndp_len);
+	
+	// dwSignature and wLength
+	put_unaligned_le32(sign, &ndp_header->dwSignature);
+	put_unaligned_le16(ndp_len, &ndp_header->wLength);
+
+	tmp = (void *)ndp_header + opts->ndp_size;
+	// (d)wDatagramIndex[0] and (d)wDatagramLength[0]
+	put_mbim(&tmp, opts->dgram_item_len, aggr_ctx->total_size + ndp_len);
+	put_mbim(&tmp, opts->dgram_item_len, skb->len - ndp_len);
+	/* (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed */
+	if (unlikely(mbim->is_crc)) {
+		pr_err("MBIM doesn't support CRC!!\n");
+		BUG();
+	}
+
+	// update wNextFpIndex(wNextNdpIndex) of prev ntb
+	if (first_skb == prev_skb) {
+		ndp_header = (void *)prev_skb->data + opts->nth_size;
+	} else {
+		ndp_header = (void *)prev_skb->data;
+	}
+	put_mbim_no_inc(NDP_FIELD(ndp_header, wNextFpIndex), opts->next_fp_index, aggr_ctx->total_size);
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+
+	aggr_ctx->total_size += skb->len;
+	aggr_ctx->num_sgs++;
+
+	// update NTH
+	nth_header = (void *)first_skb->data;
+	// (d)wBlockLength
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, aggr_ctx->total_size);
+
+	return first_skb;
+
+error:
+	aggr_ctx->pending_skb = skb;
+	return first_skb;
+}
+#else
+static struct sk_buff *mbim_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	int		padding, aggregate_length, skb_qlen;
+	struct nth *nth_header;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int single_entry_ndp_size = opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2);
+	struct sk_buff *first_skb, *prev_skb;
+	u16 tci = 0;
+	u8 *c = NULL;
+	__le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN);
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	skb_qlen = skb_queue_len(&aggr_ctx->skb_list);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		skb = mbim_wrap_ntb(port,skb,aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1!=skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+	
+	/* Reply for outgoing ARP request cause MBIM HOST will NOT answer! */
+	if (((struct ethhdr *)skb->data)->h_proto == htons(ETH_P_ARP)) {
+		struct arphdr *arp = (struct arphdr *)(skb->data + ETH_HLEN);
+		if (arp->ar_op == htons(ARPOP_REQUEST)) {
+			aggr_ctx->pending_skb = skb;
+			return first_skb;
+		}
+	}
+
+	padding = (unsigned)skb->data - single_entry_ndp_size - (unsigned)prev_skb->tail;
+	aggregate_length = aggr_ctx->total_size + skb->len + padding + single_entry_ndp_size;
+	BUG_ON(aggregate_length != skb->tail - first_skb->data);
+
+	if (vlan_get_tag(skb, &tci) >= 0 ) {
+		padding = padding + ETH_HLEN + VLAN_HLEN;
+	} else {
+		padding = padding + ETH_HLEN;
+	}
+
+	/* mbim aggregation condition */
+	if (aggregate_length > max_size ||
+		      (mbim->ntb_max_datagrams != 0 && mbim->ntb_max_datagrams <= skb_qlen) ||
+		      skb_qlen >= MBIM_MAX_DGRAMS_AGGREGATION ||
+		      skb_cloned(skb) ||
+		      skb_headroom(skb) < single_entry_ndp_size ||
+		      padding > AGGR_MAX_PADDING ||
+		      padding < 0) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/* mapping VLANs to MBIM sessions:
+	 *   no tag     => IPS session <0>
+	 *   1 - 255    => IPS session <vlanid>
+	 *   256 - 511  => DSS session <vlanid - 256>
+	 *   512 - 4095 => unsupported, drop
+	 */
+	if (vlan_get_tag(skb, &tci) >= 0 ) {
+		switch (tci & 0x0f00) {
+		case 0x0000: /* VLAN ID 0 - 255 */
+			break;
+		case 0x0100: /* VLAN ID 256 - 511 */
+			sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN);
+			break;
+		default:
+			pr_err("unsupported tci=0x%04x\n", tci);
+			goto error;
+		}
+		c = (u8 *)&sign;
+		c[3] = tci;
+		skb_pull(skb, ETH_HLEN + VLAN_HLEN);
+		pr_debug("VLAN tci=0x%04x\n", tci);
+	} else {
+		skb_pull(skb, ETH_HLEN);
+	}
+
+	// update NTH
+	nth_header = (void *)first_skb->data;
+	// (d)wBlockLength
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, skb->tail - first_skb->data);
+	add_ndp_header(port, skb, aggr_ctx, sign);
+	
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+	BUG_ON(skb->tail - first_skb->data != aggr_ctx->total_size + skb->len + padding);
+	aggr_ctx->total_size = skb->tail - first_skb->data;
+	return first_skb;
+error:
+	aggr_ctx->pending_skb = skb;
+	return first_skb;
+}
+#endif //CONFIG_USBNET_USE_SG
+#endif //CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+
+static int mbim_unwrap_ntb(struct gether *port,
+			  struct sk_buff *skb,
+			  struct sk_buff_head *list)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	__le16		*tmp = (void *) skb->data;
+	unsigned	index, index2;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	struct sk_buff	*skb2 = NULL;
+	struct net_device *net_dev = port->ioport->net;
+	int		ret = -EINVAL;
+	unsigned	max_size = mbim->port.fixed_out_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	unsigned	crc_len = mbim->is_crc ? sizeof(uint32_t) : 0;
+	int		dgram_counter;
+	u16 tci = 0;
+	u8 *c = NULL;
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		pr_info("Wrong NTH SIGN, skblen %d\n",
+			skb->len);
+		print_hex_dump(KERN_INFO, "HEAD:\n", DUMP_PREFIX_ADDRESS, 32, 1,
+			       skb->data, 32, false);
+
+		goto err;
+	}
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		pr_info("Wrong NTB headersize\n");
+		goto err;
+	}
+	tmp++; /* skip wSequence */
+
+	/* (d)wBlockLength */
+	if (get_mbim(&tmp, opts->block_length) > max_size) {
+		pr_info("OUT size exceeded\n");
+		goto err;
+	}
+
+	index = get_mbim(&tmp, opts->fp_index);
+	/* MBIM 3.2 */
+	if (((index % 4) != 0) && (index < opts->nth_size)) {
+		pr_info("Bad index: %x\n",
+			index);
+		goto err;
+	}
+
+	/* walk through NDP */
+	tmp = ((void *)skb->data) + index;
+	switch (get_unaligned_le32(tmp) & cpu_to_le32(0x00ffffff)) {
+	case USB_CDC_MBIM_NDP16_IPS_SIGN:
+	case USB_CDC_MBIM_NDP32_IPS_SIGN:
+		c = (u8 *)tmp;
+		tci = c[3];
+		break;
+	case USB_CDC_MBIM_NDP16_DSS_SIGN:
+	case USB_CDC_MBIM_NDP32_DSS_SIGN:
+		c = (u8 *)tmp;
+		tci = c[3] + 256;
+		break;
+	default:
+		pr_info("unsupported NDP signature <0x%08x>\n", le32_to_cpu(tmp));
+		goto err;
+	}
+
+	tmp += 2;
+
+	ndp_len = get_unaligned_le16(tmp++);
+	/*
+	 * MBIM 3.3.1
+	 * entry is 2 items
+	 * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+	 * minimal: struct usb_cdc_mbim_ndpX + normal entry + zero entry
+	 */
+	if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+	    || (ndp_len % opts->ndplen_align != 0)) {
+		pr_info("Bad NDP length: %x\n", ndp_len);
+		goto err;
+	}
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	ndp_len -= opts->ndp_size;
+	index2 = get_mbim(&tmp, opts->dgram_item_len);
+	dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+	dgram_counter = 0;
+
+	do {
+		index = index2;
+		dg_len = dg_len2;
+		if (dg_len < 14 + crc_len) { /* ethernet header + crc */
+			pr_info("Bad dgram length: %x\n",
+			     dg_len);
+			goto err;
+		}
+		if (mbim->is_crc) {
+			uint32_t crc, crc2;
+
+			crc = get_unaligned_le32(skb->data +
+						 index + dg_len - crc_len);
+			crc2 = ~crc32_le(~0,
+					 skb->data + index,
+					 dg_len - crc_len);
+			if (crc != crc2) {
+				pr_info("Bad CRC\n");
+				goto err;
+			}
+		}
+
+		index2 = get_mbim(&tmp, opts->dgram_item_len);
+		dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+
+		if (index2 == 0 || dg_len2 == 0) {
+			skb2 = skb;
+		} else {
+			skb2 = skb_clone(skb, GFP_ATOMIC);
+			BUG_ON(!skb2);
+		}
+
+		if (!skb_pull(skb2, index)) {
+			ret = -EOVERFLOW;
+			goto err;
+		}
+
+		skb_trim(skb2, dg_len - crc_len);
+		skb2->dev = net_dev;
+
+		MBIM_SKB_CB(skb2)->sign = MBIM_SK_CB_SIGN;
+		MBIM_SKB_CB(skb2)->tci = tci;
+
+		BUG_ON(tci > 255); //Fix this
+		/* map MBIM session to VLAN */
+		if (tci) {//maybe do it always, also for VLAN ID 0
+			pr_err("Adding VLAN\n");
+			BUG();
+			vlan_put_tag(skb2, htons(ETH_P_8021Q), tci);
+		}
+
+		skb_queue_tail(list, skb2);
+
+		ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+		dgram_counter++;
+
+		if (index2 == 0 || dg_len2 == 0)
+			break;
+	} while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+
+	return 0;
+err:
+	skb_queue_purge(list);
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+
+static struct sk_buff *mbim_process_dgram(struct gether *port,
+					  struct sk_buff *skb)
+{
+	struct net_device *dev = port->ioport->net;
+	struct sk_buff *skb2 = NULL;
+	__be16 proto = htons(ETH_P_802_3);
+
+	BUG_ON(!dev || !port || !skb || !skb->data || !skb->len);
+
+
+
+	if (MBIM_SKB_CB(skb)->tci < 256) { /* IPS session? */
+
+		switch (*(skb->data) & 0xf0) {
+		case 0x40:
+			proto = htons(ETH_P_IP);
+			break;
+		case 0x60:
+#if 0
+			if (is_neigh_solicit(buf, len))
+				do_neigh_solicit(dev, buf, tci);
+#endif
+			proto = htons(ETH_P_IPV6);
+			break;
+		default:
+			goto err;
+		}
+	}
+
+	skb2 = netdev_alloc_skb_ip_align(dev, skb->len + ETH_HLEN);
+	if (!skb2)
+		goto err;
+	
+	/* add an ethernet header */
+	skb_put(skb2, ETH_HLEN);
+	skb_reset_mac_header(skb2);
+	eth_hdr(skb2)->h_proto = proto;
+
+	memcpy(eth_hdr(skb2)->h_source, port->ioport->host_mac, ETH_ALEN);
+	memcpy(eth_hdr(skb2)->h_dest, dev->dev_addr, ETH_ALEN);
+
+	/* add datagram */
+	memcpy(skb_put(skb2, skb->len), skb->data, skb->len);
+#if 0
+	/* map MBIM session to VLAN */
+	if (tci)
+		vlan_put_tag(skb, htons(ETH_P_8021Q), tci);
+#endif
+err:
+	return skb2;
+}
+#endif /* CONFIG_ASR_TOE */
+
+static void mbim_disable(struct usb_function *f)
+{
+	struct f_mbim	*mbim = func_to_mbim(f);
+
+	pr_info("SET DEVICE OFFLINE\n");
+#ifdef CONFIG_ASR_TOE
+	mbim->port.ueth_type = UETHER_UNKNOWN;
+#endif
+	atomic_set(&mbim->online, 0);
+	atomic_set(&mbim->response_available, 0);
+	atomic_set(&mbim->notify_count, 0);
+
+	mbim_clear_queues(mbim);
+	mbim_reset_function_queue(mbim);
+
+	if (mbim->port.in_ep->driver_data)
+		gether_disconnect(&mbim->port);
+
+	if (mbim->notify->driver_data) {
+		usb_ep_disable(mbim->notify);
+		mbim->notify->driver_data = NULL;
+		mbim->notify_desc = NULL;
+	}
+}
+
+
+static struct sk_buff *ncm_wrap_ntb(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	struct sk_buff	*skb2;
+	int		ncb_len, headroom;
+	int		div = ntb_parameters.wNdpInDivisor;
+	int		rem = ntb_parameters.wNdpInPayloadRemainder;
+	int		pad = 0;
+	struct nth	*nth_header;
+	struct ndp  *ndp_header;
+	__le16	  *tmp;
+	int		ndp_align = ntb_parameters.wNdpInAlignment;
+	int		ndp_pad;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	unsigned	crc_len = mbim->is_crc ? sizeof(uint32_t) : 0;
+
+	ncb_len = opts->nth_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += opts->ndp_size;
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+	pad = ALIGN(ncb_len, div) + rem - ncb_len;
+	ncb_len += pad;
+
+	if (ncb_len + skb->len + crc_len > max_size) {
+		dev_kfree_skb_any(skb);
+		return NULL;
+	}
+
+	if (unlikely(skb->cloned || skb_headroom(skb) < ncb_len
+			|| skb_tailroom(skb) < crc_len)) {
+		skb2 = skb_copy_expand(skb, ncb_len,
+			       crc_len,
+			       GFP_ATOMIC);
+		dev_kfree_skb_any(skb);
+		if (!skb2)
+			return NULL;
+
+		skb = skb2;
+		skb2 = NULL;
+	}
+
+#ifndef CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS
+	headroom = skb_headroom(skb);
+#ifndef CONFIG_ASR_TOE
+	if (headroom >= (ncb_len + 4 * (MAX_IN_SKB_NUM + 1))) {
+		headroom = (ncb_len + 4 * MAX_IN_SKB_NUM);
+		headroom += ((u32)(skb->data - headroom) & 0x3);
+	}
+#endif
+#else
+	headroom = ncb_len;
+#endif
+	nth_header = (void *)skb_push(skb, headroom);
+	memset(nth_header, 0, headroom);
+	put_unaligned_le32(opts->nth_sign, &nth_header->dwSignature);
+	put_unaligned_le16(opts->nth_size, &nth_header->wHeaderLength);
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, skb->len);  /* the first pointer is right after the NTH + align */
+	put_mbim_no_inc(NTH_FIELD(nth_header, wFpIndex), opts->fp_index, opts->nth_size + ndp_pad);  /* NDP */
+	ndp_header = (void *)nth_header + opts->nth_size + ndp_pad;
+	put_unaligned_le32(opts->ndp_sign, &ndp_header->dwSignature);
+	put_unaligned_le16(ncb_len - opts->nth_size, &ndp_header->wLength);
+	if (unlikely(mbim->is_crc)) {
+		pr_err("NCM fast-path doesn't support CRC in multiple packets!!"
+			"CI needs to allocate additional space for CRC at the end of the packet.\n");
+		BUG();
+	}
+	tmp = (void *)ndp_header + opts->ndp_size;  /* (d)wDatagramIndex[0] */
+	put_mbim(&tmp, opts->dgram_item_len, headroom);  /* (d)wDatagramLength[0] */
+	put_mbim(&tmp, opts->dgram_item_len, skb->len - headroom);  /* (d)wDatagramIndex[1] and  (d)wDatagramLength[1] already zeroed */
+	return skb;
+}
+
+#ifdef CONFIG_ASR_TOE
+static struct sk_buff *ncm_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	int		ndp_align = ntb_parameters.wNdpInAlignment;
+	int		ndp_pad;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int ncb_len;
+	struct sk_buff *first_skb, *prev_skb;
+	unsigned int max_nth_size, ndp_entry_pointer_len;
+
+	ncb_len = opts->nth_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += opts->ndp_size;
+	ndp_entry_pointer_len = 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list) && list_empty(&aggr_ctx->toe_list)) {
+		struct sk_buff *old_skb = skb;	/*check instead in aggregation condition that mem is shmem */
+		max_nth_size = skb_headroom(skb);
+		skb = ncm_wrap_ntb(port,skb,aggr_ctx);
+		if (likely(skb)) {
+			/*save first skb headroom for future aggregations limit*/
+			NTB_MAX_HEADER_SIZE(skb) = old_skb==skb ? max_nth_size : 0;
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		aggr_ctx->is_toe = false;
+		return skb;
+	}
+
+	if (aggr_ctx->is_toe) {
+		aggr_ctx->pending_skb = skb;
+		first_skb = (struct sk_buff *)list_first_entry(&aggr_ctx->toe_list, struct toe_pkt_desc, list); 
+		return first_skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1 != skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+
+	aggr_ctx->pending_skb = skb;
+	return first_skb;
+}
+
+#define NCM_LONG_SKB_LEN		(1000)
+#define SKB_HEADROOM_RESERVE 	(64)
+#define SKB_COPY_ALLOC_LENGTH 	(1514 + SKB_HEADROOM_RESERVE + NET_IP_ALIGN)
+
+#ifdef CONFIG_NCM_INPUT_AS_RNDIS
+static void ncm_add_rndis_header(struct sk_buff *skb)
+{
+	struct rndis_packet_msg_type *header;
+
+	if (!skb)
+		return;
+	header = (void *)skb_push(skb, sizeof(*header));
+	memset(header, 0, sizeof *header);
+	header->MessageType = cpu_to_le32(RNDIS_MSG_PACKET);
+	header->MessageLength = cpu_to_le32(skb->len);
+	header->DataOffset = cpu_to_le32(36);
+	header->DataLength = cpu_to_le32(skb->len - sizeof(*header));
+	v7_dma_flush_range((void *)header, (void *)((u32)header + sizeof(*header)));
+}
+#endif
+
+/* only short skb is copy by SW or DMA */
+static struct sk_buff *ncm_copy_skb(struct sk_buff *skb, unsigned offset, unsigned len)
+{
+	struct sk_buff *skb2;
+
+	skb2 = bm_usb_alloc_skb(SKB_COPY_ALLOC_LENGTH,  GFP_ATOMIC);
+	if (!skb2)
+		return NULL;
+
+	skb_reserve(skb2, SKB_HEADROOM_RESERVE);
+	memcpy(skb_put(skb2, len), (skb->data + offset), len);
+
+	return skb2;
+}
+
+static int ncm_unwrap_ntb(struct gether *port,
+			  struct sk_buff *skb,
+			  struct sk_buff_head *list)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	__le16		*tmp = (void *) skb->data;
+	unsigned	index, index2;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	struct sk_buff	*skb2;
+	int		ret = -EINVAL;
+	unsigned	max_size = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	unsigned	crc_len = mbim->is_crc ? sizeof(uint32_t) : 0;
+	int		dgram_counter, nocopy_dgram_counter;
+	bool	skb_meet_nocopy = false;
+	unsigned	nocopy_offset, nocopy_len;
+
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		pr_err("Wrong NTH SIGN, skblen %d\n",
+			skb->len);
+		print_hex_dump(KERN_INFO, "HEAD:\n", DUMP_PREFIX_ADDRESS, 32, 1,
+			       skb->data, 32, false);
+
+		goto err;
+	}
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		pr_err("Wrong NTB headersize\n");
+		goto err;
+	}
+	tmp++; /* skip wSequence */
+
+	/* (d)wBlockLength */
+	if (get_mbim(&tmp, opts->block_length) > max_size) {
+		pr_err("OUT size exceeded\n");
+		goto err;
+	}
+
+	index = get_mbim(&tmp, opts->fp_index);
+	/* NCM 3.2 */
+	if (((index % 4) != 0) && (index < opts->nth_size)) {
+		pr_err("Bad index: %x\n",
+			index);
+		goto err;
+	}
+
+	/* walk through NDP */
+	tmp = ((void *)skb->data) + index;
+	if (get_unaligned_le32(tmp) != opts->ndp_sign) {
+		pr_err("Wrong NDP SIGN\n");
+		goto err;
+	}
+	tmp += 2;
+
+	ndp_len = get_unaligned_le16(tmp++);
+	/*
+	 * NCM 3.3.1
+	 * entry is 2 items
+	 * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+	 * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry
+	 */
+	if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+	    || (ndp_len % opts->ndplen_align != 0)) {
+		pr_err("Bad NDP length: %x\n", ndp_len);
+		goto err;
+	}
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	ndp_len -= opts->ndp_size;
+	index2 = get_mbim(&tmp, opts->dgram_item_len);
+	dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+	dgram_counter = 0;
+
+	do {
+		index = index2;
+		dg_len = dg_len2;
+
+		if (!skb_meet_nocopy && dg_len > NCM_LONG_SKB_LEN) {
+			skb_meet_nocopy = true;
+			nocopy_offset = index;
+			nocopy_len = dg_len;
+			nocopy_dgram_counter = dgram_counter;
+		}
+
+		if (dg_len < 14 + crc_len) { /* ethernet header + crc */
+			pr_err("Bad dgram length: %x\n",
+			     dg_len);
+			goto err;
+		}
+		if (mbim->is_crc) {
+			uint32_t crc, crc2;
+
+			crc = get_unaligned_le32(skb->data +
+						 index + dg_len - crc_len);
+			crc2 = ~crc32_le(~0,
+					 skb->data + index,
+					 dg_len - crc_len);
+			if (crc != crc2) {
+				pr_err("Bad CRC\n");
+				goto err;
+			}
+		}
+
+		index2 = get_mbim(&tmp, opts->dgram_item_len);
+		dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+
+		/* last skb */
+		if (index2 == 0 || dg_len2 == 0) {
+			/* only one skb, no copy */
+			if (dgram_counter == 0) {
+				skb2 = skb;
+				goto skb_nocpy_pull;
+			} else {
+				if (skb_meet_nocopy) {
+					/* last skb is no cpy */
+					if (nocopy_dgram_counter == (dgram_counter - 1)) {
+						skb2 = skb;
+						goto skb_nocpy_pull;
+					} else {
+						/* last pkt need to be copied
+						* handle the last skb and non-copy skb
+						*/
+						skb2 = ncm_copy_skb(skb, index, dg_len);
+						if (skb2 == NULL) {
+							pr_info("%s: skb cp err\n", __func__);
+							goto err;
+						} else {
+							skb_trim(skb2, dg_len - crc_len);
+							skb_queue_tail(list, skb2);
+
+							/* handle the non copy skb */
+							skb2 = skb;
+							index = nocopy_offset;
+							dg_len = nocopy_len;
+							goto skb_nocpy_pull;
+						}
+					}
+				} else {
+					skb2 = skb;
+					goto skb_nocpy_pull;
+				}
+			}
+		} else {
+			if (skb_meet_nocopy && nocopy_dgram_counter == dgram_counter) {
+				/* TODO: skip the skb copy, do nothing? */
+				goto skip_skb_copy;
+			} else {
+				skb2 = ncm_copy_skb(skb, index, dg_len);
+				if (skb2 == NULL) {
+					pr_info("%s: skb cp err2\n", __func__);
+					goto err;
+				} else {
+					goto skb_nopull;
+				}
+			}
+		}
+
+skb_nocpy_pull:
+		if (!skb_pull(skb2, index)) {
+			ret = -EOVERFLOW;
+			pr_err("%s: skb2 pull error\n", __func__);
+			goto err;
+		}
+
+skb_nopull:
+		skb_trim(skb2, dg_len - crc_len);
+#ifdef CONFIG_NCM_INPUT_AS_RNDIS
+		ncm_add_rndis_header(skb2);
+#endif
+		skb_queue_tail(list, skb2);
+
+skip_skb_copy:
+		ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+		dgram_counter++;
+
+		if (index2 == 0 || dg_len2 == 0)
+			break;
+	} while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+
+	VDBG(port->func.config->cdev,
+	     "Parsed NTB with %d frames\n", dgram_counter);
+	return 0;
+err:
+	skb_queue_purge(list);
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+
+#else
+#ifdef CONFIG_USB_G_NCM_MULT_PKT_SUPPORT
+#ifdef CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS
+static struct sk_buff *ncm_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	int		padding, aggregate_length, skb_qlen;
+	struct nth *nth_header;
+	int		ndp_align = ntb_parameters.wNdpInAlignment;
+	int		ndp_pad;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int ncb_len;
+	int single_entry_ndp_size = opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2);
+	struct sk_buff *first_skb, *prev_skb;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	skb_qlen = skb_queue_len(&aggr_ctx->skb_list);
+	ncb_len = opts->nth_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += opts->ndp_size;
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		skb = ncm_wrap_ntb(port,skb,aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1!=skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+	padding = (unsigned)skb->data - single_entry_ndp_size - (unsigned)prev_skb->tail;
+	aggregate_length = aggr_ctx->total_size + skb->len + padding + single_entry_ndp_size;
+	BUG_ON(aggregate_length != skb->tail - first_skb->data);
+
+	/* ncm aggregation condition */
+	if (aggregate_length > max_size ||
+		      (mbim->ntb_max_datagrams != 0 && mbim->ntb_max_datagrams <= skb_qlen) ||
+		      skb_qlen >= MAX_DGRAMS_AGGREGATION ||
+		      skb_cloned(skb) ||
+		      skb_headroom(skb) < single_entry_ndp_size ||
+		      padding > AGGR_MAX_PADDING ||
+		      padding < 0) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/* update NTH */
+	nth_header = (void *)first_skb->data;
+	/* (d)wBlockLength */
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, skb->tail - first_skb->data);
+
+	add_ndp_header(port, skb, aggr_ctx, opts->ndp_sign);
+
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+	BUG_ON(skb->tail - first_skb->data != aggr_ctx->total_size + skb->len + padding);
+	aggr_ctx->total_size = skb->tail - first_skb->data;
+	/* aggr_ctx->total_padding += padding;*/
+	return first_skb;
+}
+#else
+static struct sk_buff *ncm_wrap_ntb_multipkt(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	int		padding, aggregate_length, skb_qlen;
+	__le16		*tmp;
+	int		ndp_align = ntb_parameters.wNdpInAlignment;
+	int		ndp_pad;
+	struct nth *nth_header;
+	struct ndp *ndp_header;
+	unsigned	max_size = mbim->port.fixed_in_len;
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	int ncb_len;
+	struct sk_buff *first_skb, *prev_skb;
+	unsigned int max_nth_size, ndp_entry_pointer_len;
+
+	ncb_len = opts->nth_size;
+	ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+	ncb_len += ndp_pad;
+	ncb_len += opts->ndp_size;
+	ndp_entry_pointer_len = 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+	ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+ 
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		struct sk_buff *old_skb = skb;	/*check instead in aggregation condition that mem is shmem */
+		max_nth_size = skb_headroom(skb);
+		skb = ncm_wrap_ntb(port,skb,aggr_ctx);
+		if (likely(skb)) {
+			/*save first skb headroom for future aggregations limit*/
+			if (max_nth_size > (ncb_len + 4 * MAX_IN_SKB_NUM)) {
+				max_nth_size = (ncb_len + 4 * MAX_IN_SKB_NUM);
+			}
+			NTB_MAX_HEADER_SIZE(skb) = (old_skb==skb)? max_nth_size : 0;
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+	max_nth_size = NTB_MAX_HEADER_SIZE(first_skb);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(first_skb == prev_skb && 1!=skb_queue_len(&aggr_ctx->skb_list));
+	BUG_ON(!aggr_ctx->total_size);
+
+	padding = (unsigned)skb->data - (unsigned)prev_skb->tail;
+	aggregate_length = aggr_ctx->total_size + skb->len + padding;
+	skb_qlen = skb_queue_len(&aggr_ctx->skb_list);
+
+	/* ncm aggregation condition */
+	if (aggregate_length >= max_size ||
+		      (max_nth_size < (ncb_len + ndp_entry_pointer_len * (skb_qlen + 1))) ||
+		      (mbim->ntb_max_datagrams != 0 && mbim->ntb_max_datagrams <= skb_qlen) ||
+		      skb_qlen >= MAX_DGRAMS_AGGREGATION ||
+		      skb_cloned(skb) ||
+		      padding > AGGR_MAX_PADDING ||
+		      padding < 0) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/* update NTH */
+	nth_header = (void *)first_skb->data;
+	/* (d)wBlockLength */
+	put_mbim_no_inc(NTH_FIELD(nth_header, wBlockLength), opts->block_length, skb->tail - first_skb->data);
+
+	/* update NDP */
+	ndp_header = (void *)nth_header + opts->nth_size + ndp_pad;
+	/* wLength */
+	put_unaligned_le16(opts->ndp_size + ndp_entry_pointer_len * (skb_qlen + 2), &ndp_header->wLength);
+
+	/* skip previous packets ndp entries*/
+	tmp = (void *)ndp_header + opts->ndp_size + ndp_entry_pointer_len * skb_qlen;
+	/* (d)wDatagramIndex[i] */
+	put_mbim(&tmp, opts->dgram_item_len, skb->data - first_skb->data);
+	/* (d)wDatagramLength[i] */
+	put_mbim(&tmp, opts->dgram_item_len, skb->len);
+	/* (d)wDatagramIndex[N] and  (d)wDatagramLength[N] already zeroed */
+
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+	BUG_ON(skb->tail - first_skb->data != aggr_ctx->total_size + skb->len + padding);
+	aggr_ctx->total_size = skb->tail - first_skb->data;
+	//aggr_ctx->total_padding += padding;
+	return first_skb;
+}
+#endif
+#endif
+
+static int ncm_unwrap_ntb(struct gether *port,
+			  struct sk_buff *skb,
+			  struct sk_buff_head *list)
+{
+	struct f_mbim	*mbim = func_to_mbim(&port->func);
+	__le16		*tmp = (void *) skb->data;
+	unsigned	index, index2;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	struct sk_buff	*skb2;
+	int		ret = -EINVAL;
+	unsigned	max_size = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+	struct ndp_parser_opts *opts = mbim->parser_opts;
+	unsigned	crc_len = mbim->is_crc ? sizeof(uint32_t) : 0;
+	int		dgram_counter;
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		pr_err("Wrong NTH SIGN, skblen %d\n",
+			skb->len);
+		print_hex_dump(KERN_INFO, "HEAD:\n", DUMP_PREFIX_ADDRESS, 32, 1,
+			       skb->data, 32, false);
+
+		goto err;
+	}
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		pr_err("Wrong NTB headersize\n");
+		goto err;
+	}
+	tmp++; /* skip wSequence */
+
+	/* (d)wBlockLength */
+	if (get_mbim(&tmp, opts->block_length) > max_size) {
+		pr_err("OUT size exceeded\n");
+		goto err;
+	}
+
+	index = get_mbim(&tmp, opts->fp_index);
+	/* NCM 3.2 */
+	if (((index % 4) != 0) && (index < opts->nth_size)) {
+		pr_err("Bad index: %x\n",
+			index);
+		goto err;
+	}
+
+	/* walk through NDP */
+	tmp = ((void *)skb->data) + index;
+	if (get_unaligned_le32(tmp) != opts->ndp_sign) {
+		pr_err("Wrong NDP SIGN\n");
+		goto err;
+	}
+	tmp += 2;
+
+	ndp_len = get_unaligned_le16(tmp++);
+	/*
+	 * NCM 3.3.1
+	 * entry is 2 items
+	 * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+	 * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry
+	 */
+	if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+	    || (ndp_len % opts->ndplen_align != 0)) {
+		pr_err("Bad NDP length: %x\n", ndp_len);
+		goto err;
+	}
+	tmp += opts->reserved1;
+	tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+	tmp += opts->reserved2;
+
+	ndp_len -= opts->ndp_size;
+	index2 = get_mbim(&tmp, opts->dgram_item_len);
+	dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+	dgram_counter = 0;
+
+	do {
+		index = index2;
+		dg_len = dg_len2;
+		if (dg_len < 14 + crc_len) { /* ethernet header + crc */
+			pr_err("Bad dgram length: %x\n",
+			     dg_len);
+			goto err;
+		}
+		if (mbim->is_crc) {
+			uint32_t crc, crc2;
+
+			crc = get_unaligned_le32(skb->data +
+						 index + dg_len - crc_len);
+			crc2 = ~crc32_le(~0,
+					 skb->data + index,
+					 dg_len - crc_len);
+			if (crc != crc2) {
+				pr_err("Bad CRC\n");
+				goto err;
+			}
+		}
+
+		index2 = get_mbim(&tmp, opts->dgram_item_len);
+		dg_len2 = get_mbim(&tmp, opts->dgram_item_len);
+
+		if (index2 == 0 || dg_len2 == 0) {
+			skb2 = skb;
+		} else {
+			skb2 = skb_clone(skb, GFP_ATOMIC);
+			if (skb2 == NULL)
+				goto err;
+		}
+
+		if (!skb_pull(skb2, index)) {
+			ret = -EOVERFLOW;
+			goto err;
+		}
+
+		skb_trim(skb2, dg_len - crc_len);
+		skb_queue_tail(list, skb2);
+
+		ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+		dgram_counter++;
+
+		if (index2 == 0 || dg_len2 == 0)
+			break;
+	} while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+
+	VDBG(port->func.config->cdev,
+	     "Parsed NTB with %d frames\n", dgram_counter);
+	return 0;
+err:
+	skb_queue_purge(list);
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+#endif
+
+
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Callbacks let us notify the host about connect/disconnect when the
+ * net device is opened or closed.
+ *
+ * For testing, note that link states on this side include both opened
+ * and closed variants of:
+ *
+ *   - disconnected/unconfigured
+ *   - configured but inactive (data alt 0)
+ *   - configured and active (data alt 1)
+ *
+ * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
+ * SET_INTERFACE (altsetting).  Remember also that "configured" doesn't
+ * imply the host is actually polling the notification endpoint, and
+ * likewise that "active" doesn't imply it's actually using the data
+ * endpoints for traffic.
+ */
+
+static void __maybe_unused mbim_net_open(struct gether *geth)
+{
+	struct f_mbim		*mbim = func_to_mbim(&geth->func);
+
+	spin_lock(&mbim->lock);
+	mbim->is_open = true;
+	mbim_notify(mbim);
+	spin_unlock(&mbim->lock);
+}
+
+static void __maybe_unused mbim_net_close(struct gether *geth)
+{
+	struct f_mbim		*mbim = func_to_mbim(&geth->func);
+
+	spin_lock(&mbim->lock);
+	mbim->is_open = false;
+	mbim_notify(mbim);
+	spin_unlock(&mbim->lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int
+mbim_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_mbim		*mbim = func_to_mbim(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	mbim->ctrl_id = status;
+	mbim_iad_desc.bFirstInterface = status;
+
+	ncm_control_intf.bInterfaceNumber = status;
+	mbim_control_intf.bInterfaceNumber = status;
+	ncm_mbim_control_intf.bInterfaceNumber = status;
+	mbim_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	mbim->data_id = status;
+
+	ncm_data_nop_intf.bInterfaceNumber = status;
+	ncm_data_intf.bInterfaceNumber = status;
+	mbim_data_nop_intf.bInterfaceNumber = status;
+	mbim_data_intf.bInterfaceNumber = status;
+	mbim_union_desc.bSlaveInterface0 = status;
+	ncm_mbim_data_intf.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_in_desc);
+	if (!ep)
+		goto fail;
+	mbim->port.in_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_out_desc);
+	if (!ep)
+		goto fail;
+	mbim->port.out_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_notify_desc);
+	if (!ep)
+		goto fail;
+	mbim->notify = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	status = -ENOMEM;
+
+	/* allocate notification request and buffer */
+	mbim->notify_req = mbim_alloc_req(ep, MBIM_STATUS_BYTECOUNT);
+	if (!mbim->notify_req || !mbim->notify_req->buf)
+		goto fail;
+
+	mbim->notify_req->context = mbim;
+	mbim->notify_req->complete = mbim_notify_complete;
+
+	g_notify_req = mbim->notify_req;
+	/*
+	 * support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	hs_mbim_in_desc.bEndpointAddress =
+				fs_mbim_in_desc.bEndpointAddress;
+	hs_mbim_out_desc.bEndpointAddress =
+				fs_mbim_out_desc.bEndpointAddress;
+	hs_mbim_notify_desc.bEndpointAddress =
+				fs_mbim_notify_desc.bEndpointAddress;
+
+	ss_mbim_in_desc.bEndpointAddress =
+				fs_mbim_in_desc.bEndpointAddress;
+	ss_mbim_out_desc.bEndpointAddress =
+				fs_mbim_out_desc.bEndpointAddress;
+	ss_mbim_notify_desc.bEndpointAddress =
+				fs_mbim_notify_desc.bEndpointAddress;
+
+	/* copy descriptors, and track endpoint copies */
+	switch (mbim->mode) {
+	case MODE_NCM:
+		status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function,
+			ncm_ss_function, ncm_ss_function);
+		break;
+	case MODE_MBIM:
+		status = usb_assign_descriptors(f, mbim_fs_function, mbim_hs_function,
+			mbim_ss_function, mbim_ss_function);
+		break;
+	case MODE_NCM_MBIM:
+		status = usb_assign_descriptors(f, ncm_mbim_fs_function, ncm_mbim_hs_function,
+			ncm_mbim_ss_function, ncm_mbim_ss_function);
+		break;
+	default:
+		BUG();
+	}
+#if 0
+	/*
+	 * support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		hs_mbim_in_desc.bEndpointAddress =
+				fs_mbim_in_desc.bEndpointAddress;
+		hs_mbim_out_desc.bEndpointAddress =
+				fs_mbim_out_desc.bEndpointAddress;
+		hs_mbim_notify_desc.bEndpointAddress =
+				fs_mbim_notify_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		switch (mbim->mode) {
+		case MODE_NCM:
+			f->hs_descriptors = usb_copy_descriptors(ncm_hs_function);
+			break;
+		case MODE_MBIM:
+			f->hs_descriptors = usb_copy_descriptors(mbim_hs_function);
+			break;
+		case MODE_NCM_MBIM:
+			f->hs_descriptors = usb_copy_descriptors(ncm_mbim_hs_function);
+			break;
+		default:
+			BUG();
+		}
+		if (!f->hs_descriptors)
+			goto fail;
+	}
+#endif
+	/*
+	 * NOTE:  all that is done without knowing or caring about
+	 * the network link ... which is unavailable to this code
+	 * until we're activated via set_alt().
+	 */
+
+	if (mbim->mode == MODE_NCM) {
+		mbim->port.open = mbim_net_open;
+		mbim->port.close = mbim_net_close;
+	} else {
+		/* Ruslan: originally was commented out in this driver*/
+		mbim->port.open = NULL;
+		mbim->port.close = NULL;
+	}
+
+	pr_info("%s speed IN/%s OUT/%s NOTIFY/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			mbim->port.in_ep->name, mbim->port.out_ep->name,
+			mbim->notify->name);
+#ifdef CONFIG_ASR_TOE
+	if (mbim->mode == MODE_NCM) {
+		mbim->port.ueth_type = UETHER_NCM;
+	} else {
+		mbim->port.ueth_type = UETHER_MBIM;
+	}
+#endif
+
+	return 0;
+
+fail:
+	pr_err("%s failed to bind, err %d\n", f->name, status);
+
+	usb_free_all_descriptors(f);
+
+	if (mbim->notify_req) {
+		kfree(mbim->notify_req->buf);
+		usb_ep_free_request(mbim->notify, mbim->notify_req);
+	}
+
+	/* we might as well release our claims on endpoints */
+	if (mbim->notify)
+		mbim->notify->driver_data = NULL;
+	if (mbim->port.out_ep)
+		mbim->port.out_ep->driver_data = NULL;
+	if (mbim->port.in_ep)
+		mbim->port.in_ep->driver_data = NULL;
+
+	return status;
+}
+
+static void
+mbim_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_mbim		*mbim = func_to_mbim(f);
+
+#ifdef CONFIG_ASR_TOE
+	mbim->port.ueth_type = UETHER_UNKNOWN;
+#endif
+	usb_free_all_descriptors(f);
+
+	kfree(mbim->notify_req->buf);	
+	g_notify_req = NULL;
+	usb_ep_free_request(mbim->notify, mbim->notify_req);
+	#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+			mbim_string_defs[0].id = 0;
+	#endif
+}
+
+/**
+ * mbim_bind_config - add MBIM link to a configuration
+ * @c: the configuration to support the network link
+ * @ethaddr: a buffer in which the ethernet address of the host side
+ *	side of the link was recorded
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gether_setup().  Caller is also responsible
+ * for calling @gether_cleanup() before module unload.
+ */
+int mbim_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], struct eth_dev *dev, enum ncm_mbim_mode mode)
+{
+	struct f_mbim	*mbim;
+	int		status, i;
+
+#ifdef CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS
+	pr_info("CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS defined\n");
+#endif
+	if (!can_support_ecm(c->cdev->gadget) || !ethaddr)
+		return -EINVAL;
+
+	/* maybe allocate device-global string IDs */
+	if (mbim_string_defs[0].id == 0) {
+
+		/* control interface label */
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		mbim_string_defs[STRING_CTRL_IDX].id = status;
+		mbim_control_intf.iInterface = status;
+
+		/* data interface label */
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		mbim_string_defs[STRING_DATA_IDX].id = status;
+		mbim_data_nop_intf.iInterface = status;
+		mbim_data_intf.iInterface = status;
+
+		/* MAC address */
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		mbim_string_defs[STRING_MAC_IDX].id = status;
+		ether_desc.iMACAddress = status;
+
+		/* IAD */
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		mbim_string_defs[STRING_IAD_IDX].id = status;
+		mbim_iad_desc.iFunction = status;
+	}
+
+	/* allocate and initialize one new instance */
+	mbim = mbim_ports[0].port;
+	if (!mbim) {
+		pr_info("mbim struct not allocated\n");
+		return -ENOMEM;
+	}
+
+	/* export host's Ethernet address in CDC format */
+	snprintf(mbim->dev_addr, sizeof mbim->dev_addr,
+		"%02X%02X%02X%02X%02X%02X\n",
+		ethaddr[0], ethaddr[1], ethaddr[2],
+		ethaddr[3], ethaddr[4], ethaddr[5]);
+	mbim_string_defs[1].s = mbim->dev_addr;
+
+	mbim->host_addr[0] = 0x00;
+	mbim->host_addr[1] = 0xFF;
+	mbim->host_addr[2] = 0xFF;
+	mbim->host_addr[3] = 0xFF;
+	mbim->host_addr[4] = 0xFF;
+	mbim->host_addr[5] = 0x00;
+
+	spin_lock_init(&mbim->lock);
+	mbim_reset_values(mbim);
+	mbim->port.is_fixed = true;
+	mbim->port.ioport = dev;
+	mbim->mode = mode;
+
+	switch (mbim->mode) {
+	case MODE_NCM:
+		mbim->port.func.name = "ncm";
+		break;
+	case MODE_MBIM:
+		mbim->port.func.name = "mbim";
+		break;
+	case MODE_NCM_MBIM:
+		mbim->port.func.name = "ncm_mbim";
+		break;
+	default:
+		BUG();
+	}
+
+	mbim->port.func.strings = mbim_strings;
+	/* descriptors are per-instance copies */
+	mbim->port.func.bind = mbim_bind;
+	mbim->port.func.unbind = mbim_unbind;
+	mbim->port.func.set_alt = mbim_set_alt;
+	mbim->port.func.get_alt = mbim_get_alt;
+	mbim->port.func.setup = mbim_setup;
+	mbim->port.func.disable = mbim_disable;
+
+	INIT_LIST_HEAD(&mbim->cpkt_req_q);
+	INIT_LIST_HEAD(&mbim->cpkt_resp_q);
+
+	if (mbim->mode == MODE_NCM || mbim->mode == MODE_NCM_MBIM) {
+#ifdef CONFIG_USB_G_NCM_MULT_PKT_SUPPORT
+		mbim->port.wrap = ncm_wrap_ntb_multipkt;
+#else
+		mbim->port.wrap = ncm_wrap_ntb;
+#endif
+		mbim->port.unwrap = ncm_unwrap_ntb;
+		mbim->port.unwrap_fixup = NULL;
+	}
+	if (mbim->mode == MODE_MBIM) {
+#ifdef CONFIG_USB_G_MBIM_MULT_PKT_SUPPORT
+		mbim->port.wrap = mbim_wrap_ntb_multipkt;
+#ifdef CONFIG_USBNET_USE_SG
+		mbim->port.is_sg_mode = 1;
+#endif
+#else
+		mbim->port.wrap = mbim_wrap_ntb;
+#endif
+		mbim->port.unwrap = mbim_unwrap_ntb;
+#ifndef CONFIG_ASR_TOE
+		mbim->port.unwrap_fixup = mbim_process_dgram;
+#endif
+	}
+
+	for (i =  (ETH_ALEN-2); i < (ETH_ALEN-1); i++) {
+		mbim->host_addr[i] = i;
+	}
+
+	status = usb_add_function(c, &mbim->port.func);
+	if (status) {
+		mbim_string_defs[1].s = NULL;
+		kfree(mbim);
+	}
+	return status;
+}
+
+/* ------------ MBIM DRIVER File Operations API for USER SPACE ------------ */
+
+#define MBIM_BULK_BUFFER_SIZE		4096
+#define MBIM_IOCTL_MAGIC		'o'
+#define MBIM_GET_NTB_SIZE		_IOR(MBIM_IOCTL_MAGIC, 2, u32)
+#define MBIM_GET_DATAGRAM_COUNT		_IOR(MBIM_IOCTL_MAGIC, 3, u16)
+
+/* temporary variable used between mbim_open() and mbim_gadget_bind() */
+static struct f_mbim *_mbim_dev;
+static unsigned int nr_mbim_ports;
+static	int		mbim_inited = 0;
+static ssize_t
+mbim_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	struct f_mbim *dev = fp->private_data;
+	struct ctrl_pkt *cpkt = NULL;
+	int ret = 0;
+
+	pr_debug("(%d)\n", count);
+
+	if (!dev) {
+		pr_err("Received NULL mbim pointer\n");
+		return -ENODEV;
+	}
+
+	if (dev->mode != MODE_MBIM && dev->mode != MODE_NCM_MBIM) {
+		pr_err("NOT mbim mode\n");
+		return -ENODEV;
+	}
+
+	if (count > MBIM_BULK_BUFFER_SIZE) {
+		pr_err("Buffer size is too big %d, should be at most %d\n",
+			count, MBIM_BULK_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	if (mbim_lock(&dev->read_excl)) {
+		pr_err("Previous reading is not finished yet\n");
+		return -EBUSY;
+	}
+
+	if (atomic_read(&dev->error)) {
+		mbim_unlock(&dev->read_excl);
+		return -EIO;
+	}
+
+	while (list_empty(&dev->cpkt_req_q)) {
+		pr_debug("Requests list is empty. Wait.\n");
+		ret = wait_event_interruptible(dev->read_wq,
+			!list_empty(&dev->cpkt_req_q));
+		if (ret < 0) {
+			pr_debug("Waiting interrupted\n");
+			mbim_unlock(&dev->read_excl);
+			return 0;
+		}
+		pr_debug("Received request packet\n");
+	}
+
+	cpkt = list_first_entry(&dev->cpkt_req_q, struct ctrl_pkt,
+							list);
+	if (cpkt->len > count) {
+		mbim_unlock(&dev->read_excl);
+		pr_err("cpkt size too big:%d > buf size:%d\n",
+				cpkt->len, count);
+		return -ENOMEM;
+	}
+
+	pr_debug("cpkt size:%d trans_id:%d\n", cpkt->len, cpkt->transaction_id);
+
+	list_del(&cpkt->list);
+	mbim_unlock(&dev->read_excl);
+
+	ret = copy_to_user(buf, cpkt->buf, cpkt->len);
+	if (ret) {
+		pr_err("copy_to_user failed: err %d\n", ret);
+		ret = 0;
+	} else {
+		pr_debug("copied %d bytes to user\n", cpkt->len);
+		ret = cpkt->len;
+	}
+
+	mbim_free_ctrl_pkt(cpkt);
+
+	return ret;
+}
+
+static ssize_t
+mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
+{
+	struct f_mbim *dev = fp->private_data;
+	struct ctrl_pkt *cpkt = NULL;
+	int ret = 0;
+	struct usb_cdc_mbim_ctrl_msg *ctrl_msg = (struct usb_cdc_mbim_ctrl_msg *)buf;
+
+	pr_debug("(%d)\n", count);
+
+	if (!dev) {
+		pr_err("Received NULL mbim pointer\n");
+		return -ENODEV;
+	}
+
+	if (dev->mode != MODE_MBIM && dev->mode != MODE_NCM_MBIM) {
+		pr_err("NOT mbim mode\n");
+		return -ENODEV;
+	}
+
+	if (!count) {
+		pr_err("zero length ctrl pkt\n");
+		return -ENODEV;
+	}
+
+	if (count > MAX_CTRL_PKT_SIZE) {
+		pr_err("given pkt size too big:%d > max_pkt_size:%d\n",
+				count, MAX_CTRL_PKT_SIZE);
+		return -ENOMEM;
+	}
+
+	if (mbim_lock(&dev->write_excl)) {
+		pr_err("Previous writing not finished yet\n");
+		return -EBUSY;
+	}
+
+	if (!atomic_read(&dev->online)) {
+		pr_info("MBIM not connected\n");
+		mbim_unlock(&dev->write_excl);
+		return -EPIPE;
+	}
+
+	/* Hold up to 64 responses in the queue */
+	if (atomic_read(&dev->response_available) > MBIM_MAX_RESP_Q) {
+		pr_err("Too much responses pending!\n");
+		mbim_unlock(&dev->write_excl);
+		return -EBUSY;
+	}
+
+	cpkt = mbim_alloc_ctrl_pkt(count, GFP_KERNEL);
+	if (!cpkt) {
+		pr_err("failed to allocate ctrl pkt\n");
+		mbim_unlock(&dev->write_excl);
+		return -ENOMEM;
+	}
+
+	cpkt->transaction_id = ctrl_msg->dwTransactionId;
+	ret = copy_from_user(cpkt->buf, buf, count);
+	if (ret) {
+		pr_err("copy_from_user failed err:%d\n", ret);
+		mbim_free_ctrl_pkt(cpkt);
+		mbim_unlock(&dev->write_excl);
+		return 0;
+	}
+
+	fmbim_send_cpkt_response(dev, cpkt);
+
+	mbim_unlock(&dev->write_excl);
+
+	return count;
+}
+
+static int mbim_open(struct inode *ip, struct file *fp)
+{
+	if (!_mbim_dev) {
+		pr_err("mbim_dev not created yet\n");
+		return -ENODEV;
+	}
+
+	if (_mbim_dev->mode != MODE_MBIM && _mbim_dev->mode != MODE_NCM_MBIM) {
+		pr_err("NOT mbim mode\n");
+		return -ENODEV;
+	}
+
+	if (mbim_lock(&_mbim_dev->open_excl)) {
+		pr_err("Already opened\n");
+		return -EBUSY;
+	}
+
+	if (!atomic_read(&_mbim_dev->online))
+		pr_info("MBIM not connected\n");
+
+	pr_debug("Lock mbim_dev->open_excl for open\n");
+
+	fp->private_data = _mbim_dev;
+
+	atomic_set(&_mbim_dev->error, 0);
+
+	// TODO - Check if needed
+	spin_lock(&_mbim_dev->lock);
+	_mbim_dev->is_open = true;
+	if (atomic_read(&_mbim_dev->online))
+		mbim_notify(_mbim_dev);
+	spin_unlock(&_mbim_dev->lock);
+
+	pr_debug("Exit, mbim file opened\n");
+
+	return 0;
+}
+
+static int mbim_release(struct inode *ip, struct file *fp)
+{
+	struct f_mbim *mbim = fp->private_data;
+	u8 max_loop = 10;
+
+	if (mbim->mode != MODE_MBIM && mbim->mode != MODE_NCM_MBIM) {
+		pr_err("NOT mbim mode\n");
+		return -ENODEV;
+	}
+
+	spin_lock(&mbim->lock);
+	mbim->is_open = false;
+	spin_unlock(&mbim->lock);
+
+	do {
+		spin_lock(&mbim->lock);
+		if (atomic_read(&mbim->response_available) == 0 &&
+		    mbim->notify_req != NULL) {
+			spin_unlock(&mbim->lock);
+			break;
+		}
+
+		pr_err("in progress, waiting for all notifications to complete,"
+		       "response_available = %d, notify_req = %p\n",
+		       atomic_read(&mbim->response_available), mbim->notify_req);
+		spin_unlock(&mbim->lock);
+
+		if (--max_loop == 0) {
+			pr_err("Closing the file handler without notifying the host\n");
+			mbim_unlock(&_mbim_dev->open_excl);
+			return 0;
+		}
+
+		msleep(2000);
+	} while (1);
+
+	BUG_ON(!mbim->notify_req);
+	BUG_ON(atomic_read(&mbim->response_available));
+
+	/* add spinlock to protect the mbim_notify */
+	spin_lock(&mbim->lock);
+	if (atomic_read(&_mbim_dev->online))
+		mbim_notify(mbim);
+	spin_unlock(&mbim->lock);
+
+	mbim_unlock(&_mbim_dev->open_excl);
+
+	return 0;
+}
+
+static long mbim_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
+{
+	struct f_mbim *mbim = fp->private_data;
+	int ret = 0;
+
+	pr_debug("Received command %d\n", cmd);
+
+	if (mbim->mode != MODE_MBIM && mbim->mode != MODE_NCM_MBIM) {
+		pr_err("NOT mbim mode\n");
+		return -ENODEV;
+	}
+
+	if (mbim_lock(&mbim->ioctl_excl))
+		return -EBUSY;
+
+	switch (cmd) {
+	case MBIM_GET_NTB_SIZE:
+		ret = copy_to_user((void __user *)arg,
+			&mbim->ntb_input_size, sizeof(mbim->ntb_input_size));
+		if (ret) {
+			pr_err("copying to user space failed\n");
+			ret = -EFAULT;
+		}
+		pr_info("Sent NTB size %d\n", mbim->ntb_input_size);
+		break;
+	case MBIM_GET_DATAGRAM_COUNT:
+		ret = copy_to_user((void __user *)arg,
+			&mbim->ntb_max_datagrams,
+			sizeof(mbim->ntb_max_datagrams));
+		if (ret) {
+			pr_err("copying to user space failed\n");
+			ret = -EFAULT;
+		}
+		pr_info("Sent NTB datagrams count %d\n",
+			mbim->ntb_max_datagrams);
+		break;
+	default:
+		pr_err("wrong parameter\n");
+		ret = -EINVAL;
+	}
+
+	mbim_unlock(&mbim->ioctl_excl);
+
+	return ret;
+}
+
+/* file operations for MBIM device /dev/mbim */
+static const struct file_operations mbim_fops = {
+	.owner = THIS_MODULE,
+	.open = mbim_open,
+	.release = mbim_release,
+	.read = mbim_read,
+	.write = mbim_write,
+	.unlocked_ioctl	= mbim_ioctl,
+};
+
+static struct miscdevice mbim_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "mbim",
+	.fops = &mbim_fops,
+};
+
+static int mbim_init(int instances)
+{
+	int i;
+	struct f_mbim *dev = NULL;
+	int ret;
+
+	if	(mbim_inited)
+		return 0;
+
+	pr_info("initialize %d instances\n", instances);
+
+	if (instances > NR_MBIM_PORTS) {
+		pr_err("Max-%d instances supported\n", NR_MBIM_PORTS);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < instances; i++) {
+		dev = kzalloc(sizeof(struct f_mbim), GFP_KERNEL);
+		if (!dev) {
+			pr_err("Failed to allocate mbim dev\n");
+			ret = -ENOMEM;
+			goto fail_probe;
+		}
+
+		dev->port_num = i;
+		spin_lock_init(&dev->lock);
+		INIT_LIST_HEAD(&dev->cpkt_req_q);
+		INIT_LIST_HEAD(&dev->cpkt_resp_q);
+
+		mbim_ports[i].port = dev;
+		mbim_ports[i].port_num = i;
+
+		dev->mode = MODE_MBIM;
+		dev->state = STATE_MBIM;
+
+		init_waitqueue_head(&dev->read_wq);
+		init_waitqueue_head(&dev->write_wq);
+		init_waitqueue_head(&dev->open_wq);
+		atomic_set(&dev->open_excl, 0);
+		atomic_set(&dev->ioctl_excl, 0);
+		atomic_set(&dev->read_excl, 0);
+		atomic_set(&dev->write_excl, 0);
+		atomic_set(&dev->response_available, 0);
+
+		nr_mbim_ports++;
+
+	}
+
+	_mbim_dev = dev;
+	ret = misc_register(&mbim_device);
+	if (ret) {
+		pr_err("mbim driver failed to register\n");
+		goto fail_probe;
+	}
+
+	pr_info("Initialized %d ports\n", nr_mbim_ports);
+	mbim_inited	=	1;
+	return ret;
+
+fail_probe:
+	pr_err("Failed\n");
+	for (i = 0; i < nr_mbim_ports; i++) {
+		kfree(mbim_ports[i].port);
+		mbim_ports[i].port = NULL;
+	}
+
+	return ret;
+}
+
+static void __maybe_unused fmbim_cleanup(void)
+{
+#if defined	(ALLOW_MBIM_CLEANUP)
+	int i = 0;
+
+	for (i = 0; i < nr_mbim_ports; i++) {
+		/* to avoid memory leak */
+		mbim_clear_queues(mbim_ports[i].port);
+		kfree(mbim_ports[i].port);
+		mbim_ports[i].port = NULL;
+	}
+	nr_mbim_ports = 0;
+
+	misc_deregister(&mbim_device);
+	mbim_device.minor = MISC_DYNAMIC_MINOR;
+	_mbim_dev = NULL;
+#endif
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_midi.c b/marvell/linux/drivers/usb/gadget/function/f_midi.c
new file mode 100644
index 0000000..6745919
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_midi.c
@@ -0,0 +1,1428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_midi.c -- USB MIDI class function driver
+ *
+ * Copyright (C) 2006 Thumtronics Pty Ltd.
+ * Developed for Thumtronics by Grey Innovation
+ * Ben Williamson <ben.williamson@greyinnovation.com>
+ *
+ * Rewritten for the composite framework
+ *   Copyright (C) 2011 Daniel Mack <zonque@gmail.com>
+ *
+ * Based on drivers/usb/gadget/f_audio.c,
+ *   Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ *   Copyright (C) 2008 Analog Devices, Inc
+ *
+ * and drivers/usb/gadget/midi.c,
+ *   Copyright (C) 2006 Thumtronics Pty Ltd.
+ *   Ben Williamson <ben.williamson@greyinnovation.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/midi.h>
+
+#include "u_f.h"
+#include "u_midi.h"
+
+MODULE_AUTHOR("Ben Williamson");
+MODULE_LICENSE("GPL v2");
+
+static const char f_midi_shortname[] = "f_midi";
+static const char f_midi_longname[] = "MIDI Gadget";
+
+/*
+ * We can only handle 16 cables on one single endpoint, as cable numbers are
+ * stored in 4-bit fields. And as the interface currently only holds one
+ * single endpoint, this is the maximum number of ports we can allow.
+ */
+#define MAX_PORTS 16
+
+/* MIDI message states */
+enum {
+	STATE_INITIAL = 0,	/* pseudo state */
+	STATE_1PARAM,
+	STATE_2PARAM_1,
+	STATE_2PARAM_2,
+	STATE_SYSEX_0,
+	STATE_SYSEX_1,
+	STATE_SYSEX_2,
+	STATE_REAL_TIME,
+	STATE_FINISHED,		/* pseudo state */
+};
+
+/*
+ * This is a gadget, and the IN/OUT naming is from the host's perspective.
+ * USB -> OUT endpoint -> rawmidi
+ * USB <- IN endpoint  <- rawmidi
+ */
+struct gmidi_in_port {
+	struct snd_rawmidi_substream *substream;
+	int active;
+	uint8_t cable;
+	uint8_t state;
+	uint8_t data[2];
+};
+
+struct f_midi {
+	struct usb_function	func;
+	struct usb_gadget	*gadget;
+	struct usb_ep		*in_ep, *out_ep;
+	struct snd_card		*card;
+	struct snd_rawmidi	*rmidi;
+	u8			ms_id;
+
+	struct snd_rawmidi_substream *out_substream[MAX_PORTS];
+
+	unsigned long		out_triggered;
+	struct tasklet_struct	tasklet;
+	unsigned int in_ports;
+	unsigned int out_ports;
+	int index;
+	char *id;
+	unsigned int buflen, qlen;
+	/* This fifo is used as a buffer ring for pre-allocated IN usb_requests */
+	DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *);
+	spinlock_t transmit_lock;
+	unsigned int in_last_port;
+	unsigned char free_ref;
+
+	struct gmidi_in_port	in_ports_array[/* in_ports */];
+};
+
+static inline struct f_midi *func_to_midi(struct usb_function *f)
+{
+	return container_of(f, struct f_midi, func);
+}
+
+static void f_midi_transmit(struct f_midi *midi);
+static void f_midi_rmidi_free(struct snd_rawmidi *rmidi);
+static void f_midi_free_inst(struct usb_function_instance *f);
+
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
+DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1);
+DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16);
+
+/* B.3.1  Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber =	DYNAMIC */
+	/* .bNumEndpoints =	DYNAMIC */
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+	/* .iInterface =	DYNAMIC */
+};
+
+/* B.3.2  Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_1 ac_header_desc = {
+	.bLength =		UAC_DT_AC_HEADER_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_HEADER,
+	.bcdADC =		cpu_to_le16(0x0100),
+	.wTotalLength =		cpu_to_le16(UAC_DT_AC_HEADER_SIZE(1)),
+	.bInCollection =	1,
+	/* .baInterfaceNr =	DYNAMIC */
+};
+
+/* B.4.1  Standard MS Interface Descriptor */
+static struct usb_interface_descriptor ms_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber =	DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_MIDISTREAMING,
+	/* .iInterface =	DYNAMIC */
+};
+
+/* B.4.2  Class-Specific MS Interface Descriptor */
+static struct usb_ms_header_descriptor ms_header_desc = {
+	.bLength =		USB_DT_MS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	USB_MS_HEADER,
+	.bcdMSC =		cpu_to_le16(0x0100),
+	/* .wTotalLength =	DYNAMIC */
+};
+
+/* B.5.1  Standard Bulk OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_ss_ep_comp_descriptor bulk_out_ss_comp_desc = {
+	.bLength                = sizeof(bulk_out_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+};
+
+/* B.5.2  Class-specific MS Bulk OUT Endpoint Descriptor */
+static struct usb_ms_endpoint_descriptor_16 ms_out_desc = {
+	/* .bLength =		DYNAMIC */
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	USB_MS_GENERAL,
+	/* .bNumEmbMIDIJack =	DYNAMIC */
+	/* .baAssocJackID =	DYNAMIC */
+};
+
+/* B.6.1  Standard Bulk IN Endpoint Descriptor */
+static struct usb_endpoint_descriptor bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_ss_ep_comp_descriptor bulk_in_ss_comp_desc = {
+	.bLength                = sizeof(bulk_in_ss_comp_desc),
+	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+	/* .bMaxBurst           = 0, */
+	/* .bmAttributes        = 0, */
+};
+
+/* B.6.2  Class-specific MS Bulk IN Endpoint Descriptor */
+static struct usb_ms_endpoint_descriptor_16 ms_in_desc = {
+	/* .bLength =		DYNAMIC */
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	USB_MS_GENERAL,
+	/* .bNumEmbMIDIJack =	DYNAMIC */
+	/* .baAssocJackID =	DYNAMIC */
+};
+
+/* string IDs are assigned dynamically */
+
+#define STRING_FUNC_IDX			0
+
+static struct usb_string midi_string_defs[] = {
+	[STRING_FUNC_IDX].s = "MIDI function",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings midi_stringtab = {
+	.language	= 0x0409,	/* en-us */
+	.strings	= midi_string_defs,
+};
+
+static struct usb_gadget_strings *midi_strings[] = {
+	&midi_stringtab,
+	NULL,
+};
+
+static inline struct usb_request *midi_alloc_ep_req(struct usb_ep *ep,
+						    unsigned length)
+{
+	return alloc_ep_req(ep, length);
+}
+
+static const uint8_t f_midi_cin_length[] = {
+	0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
+};
+
+/*
+ * Receives a chunk of MIDI data.
+ */
+static void f_midi_read_data(struct usb_ep *ep, int cable,
+			     uint8_t *data, int length)
+{
+	struct f_midi *midi = ep->driver_data;
+	struct snd_rawmidi_substream *substream = midi->out_substream[cable];
+
+	if (!substream)
+		/* Nobody is listening - throw it on the floor. */
+		return;
+
+	if (!test_bit(cable, &midi->out_triggered))
+		return;
+
+	snd_rawmidi_receive(substream, data, length);
+}
+
+static void f_midi_handle_out_data(struct usb_ep *ep, struct usb_request *req)
+{
+	unsigned int i;
+	u8 *buf = req->buf;
+
+	for (i = 0; i + 3 < req->actual; i += 4)
+		if (buf[i] != 0) {
+			int cable = buf[i] >> 4;
+			int length = f_midi_cin_length[buf[i] & 0x0f];
+			f_midi_read_data(ep, cable, &buf[i + 1], length);
+		}
+}
+
+static void
+f_midi_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_midi *midi = ep->driver_data;
+	struct usb_composite_dev *cdev = midi->func.config->cdev;
+	int status = req->status;
+
+	switch (status) {
+	case 0:			 /* normal completion */
+		if (ep == midi->out_ep) {
+			/* We received stuff. req is queued again, below */
+			f_midi_handle_out_data(ep, req);
+		} else if (ep == midi->in_ep) {
+			/* Our transmit completed. See if there's more to go.
+			 * f_midi_transmit eats req, don't queue it again. */
+			req->length = 0;
+			f_midi_transmit(midi);
+			return;
+		}
+		break;
+
+	/* this endpoint is normally active while we're configured */
+	case -ECONNABORTED:	/* hardware forced ep reset */
+	case -ECONNRESET:	/* request dequeued */
+	case -ESHUTDOWN:	/* disconnect from host */
+		VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
+				req->actual, req->length);
+		if (ep == midi->out_ep) {
+			f_midi_handle_out_data(ep, req);
+			/* We don't need to free IN requests because it's handled
+			 * by the midi->in_req_fifo. */
+			free_ep_req(ep, req);
+		}
+		return;
+
+	case -EOVERFLOW:	/* buffer overrun on read means that
+				 * we didn't provide a big enough buffer.
+				 */
+	default:
+		DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name,
+				status, req->actual, req->length);
+		break;
+	case -EREMOTEIO:	/* short read */
+		break;
+	}
+
+	status = usb_ep_queue(ep, req, GFP_ATOMIC);
+	if (status) {
+		ERROR(cdev, "kill %s:  resubmit %d bytes --> %d\n",
+				ep->name, req->length, status);
+		usb_ep_set_halt(ep);
+		/* FIXME recover later ... somehow */
+	}
+}
+
+static void f_midi_drop_out_substreams(struct f_midi *midi)
+{
+	unsigned int i;
+
+	for (i = 0; i < midi->in_ports; i++) {
+		struct gmidi_in_port *port = midi->in_ports_array + i;
+		struct snd_rawmidi_substream *substream = port->substream;
+
+		if (port->active && substream)
+			snd_rawmidi_drop_output(substream);
+	}
+}
+
+static int f_midi_start_ep(struct f_midi *midi,
+			   struct usb_function *f,
+			   struct usb_ep *ep)
+{
+	int err;
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	usb_ep_disable(ep);
+
+	err = config_ep_by_speed(midi->gadget, f, ep);
+	if (err) {
+		ERROR(cdev, "can't configure %s: %d\n", ep->name, err);
+		return err;
+	}
+
+	err = usb_ep_enable(ep);
+	if (err) {
+		ERROR(cdev, "can't start %s: %d\n", ep->name, err);
+		return err;
+	}
+
+	ep->driver_data = midi;
+
+	return 0;
+}
+
+static int f_midi_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_midi *midi = func_to_midi(f);
+	unsigned i;
+	int err;
+
+	/* we only set alt for MIDIStreaming interface */
+	if (intf != midi->ms_id)
+		return 0;
+
+	err = f_midi_start_ep(midi, f, midi->in_ep);
+	if (err)
+		return err;
+
+	err = f_midi_start_ep(midi, f, midi->out_ep);
+	if (err)
+		return err;
+
+	/* pre-allocate write usb requests to use on f_midi_transmit. */
+	while (kfifo_avail(&midi->in_req_fifo)) {
+		struct usb_request *req =
+			midi_alloc_ep_req(midi->in_ep, midi->buflen);
+
+		if (req == NULL)
+			return -ENOMEM;
+
+		req->length = 0;
+		req->complete = f_midi_complete;
+
+		kfifo_put(&midi->in_req_fifo, req);
+	}
+
+	/* allocate a bunch of read buffers and queue them all at once. */
+	for (i = 0; i < midi->qlen && err == 0; i++) {
+		struct usb_request *req =
+			midi_alloc_ep_req(midi->out_ep, midi->buflen);
+
+		if (req == NULL)
+			return -ENOMEM;
+
+		req->complete = f_midi_complete;
+		err = usb_ep_queue(midi->out_ep, req, GFP_ATOMIC);
+		if (err) {
+			ERROR(midi, "%s: couldn't enqueue request: %d\n",
+				    midi->out_ep->name, err);
+			if (req->buf != NULL)
+				free_ep_req(midi->out_ep, req);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void f_midi_disable(struct usb_function *f)
+{
+	struct f_midi *midi = func_to_midi(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request *req = NULL;
+
+	DBG(cdev, "disable\n");
+
+	/*
+	 * just disable endpoints, forcing completion of pending i/o.
+	 * all our completion handlers free their requests in this case.
+	 */
+	usb_ep_disable(midi->in_ep);
+	usb_ep_disable(midi->out_ep);
+
+	/* release IN requests */
+	while (kfifo_get(&midi->in_req_fifo, &req))
+		free_ep_req(midi->in_ep, req);
+
+	f_midi_drop_out_substreams(midi);
+}
+
+static int f_midi_snd_free(struct snd_device *device)
+{
+	return 0;
+}
+
+/*
+ * Converts MIDI commands to USB MIDI packets.
+ */
+static void f_midi_transmit_byte(struct usb_request *req,
+				 struct gmidi_in_port *port, uint8_t b)
+{
+	uint8_t p[4] = { port->cable << 4, 0, 0, 0 };
+	uint8_t next_state = STATE_INITIAL;
+
+	switch (b) {
+	case 0xf8 ... 0xff:
+		/* System Real-Time Messages */
+		p[0] |= 0x0f;
+		p[1] = b;
+		next_state = port->state;
+		port->state = STATE_REAL_TIME;
+		break;
+
+	case 0xf7:
+		/* End of SysEx */
+		switch (port->state) {
+		case STATE_SYSEX_0:
+			p[0] |= 0x05;
+			p[1] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		case STATE_SYSEX_1:
+			p[0] |= 0x06;
+			p[1] = port->data[0];
+			p[2] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		case STATE_SYSEX_2:
+			p[0] |= 0x07;
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = 0xf7;
+			next_state = STATE_FINISHED;
+			break;
+		default:
+			/* Ignore byte */
+			next_state = port->state;
+			port->state = STATE_INITIAL;
+		}
+		break;
+
+	case 0xf0 ... 0xf6:
+		/* System Common Messages */
+		port->data[0] = port->data[1] = 0;
+		port->state = STATE_INITIAL;
+		switch (b) {
+		case 0xf0:
+			port->data[0] = b;
+			port->data[1] = 0;
+			next_state = STATE_SYSEX_1;
+			break;
+		case 0xf1:
+		case 0xf3:
+			port->data[0] = b;
+			next_state = STATE_1PARAM;
+			break;
+		case 0xf2:
+			port->data[0] = b;
+			next_state = STATE_2PARAM_1;
+			break;
+		case 0xf4:
+		case 0xf5:
+			next_state = STATE_INITIAL;
+			break;
+		case 0xf6:
+			p[0] |= 0x05;
+			p[1] = 0xf6;
+			next_state = STATE_FINISHED;
+			break;
+		}
+		break;
+
+	case 0x80 ... 0xef:
+		/*
+		 * Channel Voice Messages, Channel Mode Messages
+		 * and Control Change Messages.
+		 */
+		port->data[0] = b;
+		port->data[1] = 0;
+		port->state = STATE_INITIAL;
+		if (b >= 0xc0 && b <= 0xdf)
+			next_state = STATE_1PARAM;
+		else
+			next_state = STATE_2PARAM_1;
+		break;
+
+	case 0x00 ... 0x7f:
+		/* Message parameters */
+		switch (port->state) {
+		case STATE_1PARAM:
+			if (port->data[0] < 0xf0)
+				p[0] |= port->data[0] >> 4;
+			else
+				p[0] |= 0x02;
+
+			p[1] = port->data[0];
+			p[2] = b;
+			/* This is to allow Running State Messages */
+			next_state = STATE_1PARAM;
+			break;
+		case STATE_2PARAM_1:
+			port->data[1] = b;
+			next_state = STATE_2PARAM_2;
+			break;
+		case STATE_2PARAM_2:
+			if (port->data[0] < 0xf0)
+				p[0] |= port->data[0] >> 4;
+			else
+				p[0] |= 0x03;
+
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = b;
+			/* This is to allow Running State Messages */
+			next_state = STATE_2PARAM_1;
+			break;
+		case STATE_SYSEX_0:
+			port->data[0] = b;
+			next_state = STATE_SYSEX_1;
+			break;
+		case STATE_SYSEX_1:
+			port->data[1] = b;
+			next_state = STATE_SYSEX_2;
+			break;
+		case STATE_SYSEX_2:
+			p[0] |= 0x04;
+			p[1] = port->data[0];
+			p[2] = port->data[1];
+			p[3] = b;
+			next_state = STATE_SYSEX_0;
+			break;
+		}
+		break;
+	}
+
+	/* States where we have to write into the USB request */
+	if (next_state == STATE_FINISHED ||
+	    port->state == STATE_SYSEX_2 ||
+	    port->state == STATE_1PARAM ||
+	    port->state == STATE_2PARAM_2 ||
+	    port->state == STATE_REAL_TIME) {
+
+		unsigned int length = req->length;
+		u8 *buf = (u8 *)req->buf + length;
+
+		memcpy(buf, p, sizeof(p));
+		req->length = length + sizeof(p);
+
+		if (next_state == STATE_FINISHED) {
+			next_state = STATE_INITIAL;
+			port->data[0] = port->data[1] = 0;
+		}
+	}
+
+	port->state = next_state;
+}
+
+static int f_midi_do_transmit(struct f_midi *midi, struct usb_ep *ep)
+{
+	struct usb_request *req = NULL;
+	unsigned int len, i;
+	bool active = false;
+	int err;
+
+	/*
+	 * We peek the request in order to reuse it if it fails to enqueue on
+	 * its endpoint
+	 */
+	len = kfifo_peek(&midi->in_req_fifo, &req);
+	if (len != 1) {
+		ERROR(midi, "%s: Couldn't get usb request\n", __func__);
+		return -1;
+	}
+
+	/*
+	 * If buffer overrun, then we ignore this transmission.
+	 * IMPORTANT: This will cause the user-space rawmidi device to block
+	 * until a) usb requests have been completed or b) snd_rawmidi_write()
+	 * times out.
+	 */
+	if (req->length > 0)
+		return 0;
+
+	for (i = midi->in_last_port; i < midi->in_ports; ++i) {
+		struct gmidi_in_port *port = midi->in_ports_array + i;
+		struct snd_rawmidi_substream *substream = port->substream;
+
+		if (!port->active || !substream)
+			continue;
+
+		while (req->length + 3 < midi->buflen) {
+			uint8_t b;
+
+			if (snd_rawmidi_transmit(substream, &b, 1) != 1) {
+				port->active = 0;
+				break;
+			}
+			f_midi_transmit_byte(req, port, b);
+		}
+
+		active = !!port->active;
+		if (active)
+			break;
+	}
+	midi->in_last_port = active ? i : 0;
+
+	if (req->length <= 0)
+		goto done;
+
+	err = usb_ep_queue(ep, req, GFP_ATOMIC);
+	if (err < 0) {
+		ERROR(midi, "%s failed to queue req: %d\n",
+		      midi->in_ep->name, err);
+		req->length = 0; /* Re-use request next time. */
+	} else {
+		/* Upon success, put request at the back of the queue. */
+		kfifo_skip(&midi->in_req_fifo);
+		kfifo_put(&midi->in_req_fifo, req);
+	}
+
+done:
+	return active;
+}
+
+static void f_midi_transmit(struct f_midi *midi)
+{
+	struct usb_ep *ep = midi->in_ep;
+	int ret;
+	unsigned long flags;
+
+	/* We only care about USB requests if IN endpoint is enabled */
+	if (!ep || !ep->enabled)
+		goto drop_out;
+
+	spin_lock_irqsave(&midi->transmit_lock, flags);
+
+	do {
+		ret = f_midi_do_transmit(midi, ep);
+		if (ret < 0) {
+			spin_unlock_irqrestore(&midi->transmit_lock, flags);
+			goto drop_out;
+		}
+	} while (ret);
+
+	spin_unlock_irqrestore(&midi->transmit_lock, flags);
+
+	return;
+
+drop_out:
+	f_midi_drop_out_substreams(midi);
+}
+
+static void f_midi_in_tasklet(unsigned long data)
+{
+	struct f_midi *midi = (struct f_midi *) data;
+	f_midi_transmit(midi);
+}
+
+static int f_midi_in_open(struct snd_rawmidi_substream *substream)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+	struct gmidi_in_port *port;
+
+	if (substream->number >= midi->in_ports)
+		return -EINVAL;
+
+	VDBG(midi, "%s()\n", __func__);
+	port = midi->in_ports_array + substream->number;
+	port->substream = substream;
+	port->state = STATE_INITIAL;
+	return 0;
+}
+
+static int f_midi_in_close(struct snd_rawmidi_substream *substream)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+
+	VDBG(midi, "%s()\n", __func__);
+	return 0;
+}
+
+static void f_midi_in_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+
+	if (substream->number >= midi->in_ports)
+		return;
+
+	VDBG(midi, "%s() %d\n", __func__, up);
+	midi->in_ports_array[substream->number].active = up;
+	if (up)
+		tasklet_hi_schedule(&midi->tasklet);
+}
+
+static int f_midi_out_open(struct snd_rawmidi_substream *substream)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+
+	if (substream->number >= MAX_PORTS)
+		return -EINVAL;
+
+	VDBG(midi, "%s()\n", __func__);
+	midi->out_substream[substream->number] = substream;
+	return 0;
+}
+
+static int f_midi_out_close(struct snd_rawmidi_substream *substream)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+
+	VDBG(midi, "%s()\n", __func__);
+	return 0;
+}
+
+static void f_midi_out_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct f_midi *midi = substream->rmidi->private_data;
+
+	VDBG(midi, "%s()\n", __func__);
+
+	if (up)
+		set_bit(substream->number, &midi->out_triggered);
+	else
+		clear_bit(substream->number, &midi->out_triggered);
+}
+
+static const struct snd_rawmidi_ops gmidi_in_ops = {
+	.open = f_midi_in_open,
+	.close = f_midi_in_close,
+	.trigger = f_midi_in_trigger,
+};
+
+static const struct snd_rawmidi_ops gmidi_out_ops = {
+	.open = f_midi_out_open,
+	.close = f_midi_out_close,
+	.trigger = f_midi_out_trigger
+};
+
+static inline void f_midi_unregister_card(struct f_midi *midi)
+{
+	if (midi->card) {
+		snd_card_free(midi->card);
+		midi->card = NULL;
+	}
+}
+
+/* register as a sound "card" */
+static int f_midi_register_card(struct f_midi *midi)
+{
+	struct snd_card *card;
+	struct snd_rawmidi *rmidi;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = f_midi_snd_free,
+	};
+
+	err = snd_card_new(&midi->gadget->dev, midi->index, midi->id,
+			   THIS_MODULE, 0, &card);
+	if (err < 0) {
+		ERROR(midi, "snd_card_new() failed\n");
+		goto fail;
+	}
+	midi->card = card;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, midi, &ops);
+	if (err < 0) {
+		ERROR(midi, "snd_device_new() failed: error %d\n", err);
+		goto fail;
+	}
+
+	strcpy(card->driver, f_midi_longname);
+	strcpy(card->longname, f_midi_longname);
+	strcpy(card->shortname, f_midi_shortname);
+
+	/* Set up rawmidi */
+	snd_component_add(card, "MIDI");
+	err = snd_rawmidi_new(card, card->longname, 0,
+			      midi->out_ports, midi->in_ports, &rmidi);
+	if (err < 0) {
+		ERROR(midi, "snd_rawmidi_new() failed: error %d\n", err);
+		goto fail;
+	}
+	midi->rmidi = rmidi;
+	midi->in_last_port = 0;
+	strcpy(rmidi->name, card->shortname);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			    SNDRV_RAWMIDI_INFO_INPUT |
+			    SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = midi;
+	rmidi->private_free = f_midi_rmidi_free;
+	midi->free_ref++;
+
+	/*
+	 * Yes, rawmidi OUTPUT = USB IN, and rawmidi INPUT = USB OUT.
+	 * It's an upside-down world being a gadget.
+	 */
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &gmidi_in_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &gmidi_out_ops);
+
+	/* register it - we're ready to go */
+	err = snd_card_register(card);
+	if (err < 0) {
+		ERROR(midi, "snd_card_register() failed\n");
+		goto fail;
+	}
+
+	VDBG(midi, "%s() finished ok\n", __func__);
+	return 0;
+
+fail:
+	f_midi_unregister_card(midi);
+	return err;
+}
+
+/* MIDI function driver setup/binding */
+
+static int f_midi_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_descriptor_header **midi_function;
+	struct usb_midi_in_jack_descriptor jack_in_ext_desc[MAX_PORTS];
+	struct usb_midi_in_jack_descriptor jack_in_emb_desc[MAX_PORTS];
+	struct usb_midi_out_jack_descriptor_1 jack_out_ext_desc[MAX_PORTS];
+	struct usb_midi_out_jack_descriptor_1 jack_out_emb_desc[MAX_PORTS];
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_midi *midi = func_to_midi(f);
+	struct usb_string *us;
+	int status, n, jack = 1, i = 0, endpoint_descriptor_index = 0;
+
+	midi->gadget = cdev->gadget;
+	tasklet_init(&midi->tasklet, f_midi_in_tasklet, (unsigned long) midi);
+	status = f_midi_register_card(midi);
+	if (status < 0)
+		goto fail_register;
+
+	/* maybe allocate device-global string ID */
+	us = usb_gstrings_attach(c->cdev, midi_strings,
+				 ARRAY_SIZE(midi_string_defs));
+	if (IS_ERR(us)) {
+		status = PTR_ERR(us);
+		goto fail;
+	}
+	ac_interface_desc.iInterface = us[STRING_FUNC_IDX].id;
+
+	/* We have two interfaces, AudioControl and MIDIStreaming */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ac_interface_desc.bInterfaceNumber = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ms_interface_desc.bInterfaceNumber = status;
+	ac_header_desc.baInterfaceNr[0] = status;
+	midi->ms_id = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	midi->in_ep = usb_ep_autoconfig(cdev->gadget, &bulk_in_desc);
+	if (!midi->in_ep)
+		goto fail;
+
+	midi->out_ep = usb_ep_autoconfig(cdev->gadget, &bulk_out_desc);
+	if (!midi->out_ep)
+		goto fail;
+
+	/* allocate temporary function list */
+	midi_function = kcalloc((MAX_PORTS * 4) + 11, sizeof(*midi_function),
+				GFP_KERNEL);
+	if (!midi_function) {
+		status = -ENOMEM;
+		goto fail;
+	}
+
+	/*
+	 * construct the function's descriptor set. As the number of
+	 * input and output MIDI ports is configurable, we have to do
+	 * it that way.
+	 */
+
+	/* add the headers - these are always the same */
+	midi_function[i++] = (struct usb_descriptor_header *) &ac_interface_desc;
+	midi_function[i++] = (struct usb_descriptor_header *) &ac_header_desc;
+	midi_function[i++] = (struct usb_descriptor_header *) &ms_interface_desc;
+
+	/* calculate the header's wTotalLength */
+	n = USB_DT_MS_HEADER_SIZE
+		+ (midi->in_ports + midi->out_ports) *
+			(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
+	ms_header_desc.wTotalLength = cpu_to_le16(n);
+
+	midi_function[i++] = (struct usb_descriptor_header *) &ms_header_desc;
+
+	/* configure the external IN jacks, each linked to an embedded OUT jack */
+	for (n = 0; n < midi->in_ports; n++) {
+		struct usb_midi_in_jack_descriptor *in_ext = &jack_in_ext_desc[n];
+		struct usb_midi_out_jack_descriptor_1 *out_emb = &jack_out_emb_desc[n];
+
+		in_ext->bLength			= USB_DT_MIDI_IN_SIZE;
+		in_ext->bDescriptorType		= USB_DT_CS_INTERFACE;
+		in_ext->bDescriptorSubtype	= USB_MS_MIDI_IN_JACK;
+		in_ext->bJackType		= USB_MS_EXTERNAL;
+		in_ext->bJackID			= jack++;
+		in_ext->iJack			= 0;
+		midi_function[i++] = (struct usb_descriptor_header *) in_ext;
+
+		out_emb->bLength		= USB_DT_MIDI_OUT_SIZE(1);
+		out_emb->bDescriptorType	= USB_DT_CS_INTERFACE;
+		out_emb->bDescriptorSubtype	= USB_MS_MIDI_OUT_JACK;
+		out_emb->bJackType		= USB_MS_EMBEDDED;
+		out_emb->bJackID		= jack++;
+		out_emb->bNrInputPins		= 1;
+		out_emb->pins[0].baSourcePin	= 1;
+		out_emb->pins[0].baSourceID	= in_ext->bJackID;
+		out_emb->iJack			= 0;
+		midi_function[i++] = (struct usb_descriptor_header *) out_emb;
+
+		/* link it to the endpoint */
+		ms_in_desc.baAssocJackID[n] = out_emb->bJackID;
+	}
+
+	/* configure the external OUT jacks, each linked to an embedded IN jack */
+	for (n = 0; n < midi->out_ports; n++) {
+		struct usb_midi_in_jack_descriptor *in_emb = &jack_in_emb_desc[n];
+		struct usb_midi_out_jack_descriptor_1 *out_ext = &jack_out_ext_desc[n];
+
+		in_emb->bLength			= USB_DT_MIDI_IN_SIZE;
+		in_emb->bDescriptorType		= USB_DT_CS_INTERFACE;
+		in_emb->bDescriptorSubtype	= USB_MS_MIDI_IN_JACK;
+		in_emb->bJackType		= USB_MS_EMBEDDED;
+		in_emb->bJackID			= jack++;
+		in_emb->iJack			= 0;
+		midi_function[i++] = (struct usb_descriptor_header *) in_emb;
+
+		out_ext->bLength =		USB_DT_MIDI_OUT_SIZE(1);
+		out_ext->bDescriptorType =	USB_DT_CS_INTERFACE;
+		out_ext->bDescriptorSubtype =	USB_MS_MIDI_OUT_JACK;
+		out_ext->bJackType =		USB_MS_EXTERNAL;
+		out_ext->bJackID =		jack++;
+		out_ext->bNrInputPins =		1;
+		out_ext->iJack =		0;
+		out_ext->pins[0].baSourceID =	in_emb->bJackID;
+		out_ext->pins[0].baSourcePin =	1;
+		midi_function[i++] = (struct usb_descriptor_header *) out_ext;
+
+		/* link it to the endpoint */
+		ms_out_desc.baAssocJackID[n] = in_emb->bJackID;
+	}
+
+	/* configure the endpoint descriptors ... */
+	ms_out_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports);
+	ms_out_desc.bNumEmbMIDIJack = midi->in_ports;
+
+	ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->out_ports);
+	ms_in_desc.bNumEmbMIDIJack = midi->out_ports;
+
+	/* ... and add them to the list */
+	endpoint_descriptor_index = i;
+	midi_function[i++] = (struct usb_descriptor_header *) &bulk_out_desc;
+	midi_function[i++] = (struct usb_descriptor_header *) &ms_out_desc;
+	midi_function[i++] = (struct usb_descriptor_header *) &bulk_in_desc;
+	midi_function[i++] = (struct usb_descriptor_header *) &ms_in_desc;
+	midi_function[i++] = NULL;
+
+	/*
+	 * support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	/* copy descriptors, and track endpoint copies */
+	f->fs_descriptors = usb_copy_descriptors(midi_function);
+	if (!f->fs_descriptors)
+		goto fail_f_midi;
+
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		bulk_in_desc.wMaxPacketSize = cpu_to_le16(512);
+		bulk_out_desc.wMaxPacketSize = cpu_to_le16(512);
+		f->hs_descriptors = usb_copy_descriptors(midi_function);
+		if (!f->hs_descriptors)
+			goto fail_f_midi;
+	}
+
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		bulk_in_desc.wMaxPacketSize = cpu_to_le16(1024);
+		bulk_out_desc.wMaxPacketSize = cpu_to_le16(1024);
+		i = endpoint_descriptor_index;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &bulk_out_desc;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &bulk_out_ss_comp_desc;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &ms_out_desc;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &bulk_in_desc;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &bulk_in_ss_comp_desc;
+		midi_function[i++] = (struct usb_descriptor_header *)
+				     &ms_in_desc;
+		f->ss_descriptors = usb_copy_descriptors(midi_function);
+		if (!f->ss_descriptors)
+			goto fail_f_midi;
+
+		if (gadget_is_superspeed_plus(c->cdev->gadget)) {
+			f->ssp_descriptors = usb_copy_descriptors(midi_function);
+			if (!f->ssp_descriptors)
+				goto fail_f_midi;
+		}
+	}
+
+	kfree(midi_function);
+
+	return 0;
+
+fail_f_midi:
+	kfree(midi_function);
+	usb_free_all_descriptors(f);
+fail:
+	f_midi_unregister_card(midi);
+fail_register:
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static inline struct f_midi_opts *to_f_midi_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_midi_opts,
+			    func_inst.group);
+}
+
+static void midi_attr_release(struct config_item *item)
+{
+	struct f_midi_opts *opts = to_f_midi_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations midi_item_ops = {
+	.release	= midi_attr_release,
+};
+
+#define F_MIDI_OPT(name, test_limit, limit)				\
+static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \
+{									\
+	struct f_midi_opts *opts = to_f_midi_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%d\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_midi_opts_##name##_store(struct config_item *item,	\
+					 const char *page, size_t len)	\
+{									\
+	struct f_midi_opts *opts = to_f_midi_opts(item);		\
+	int ret;							\
+	u32 num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt > 1) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou32(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	if (test_limit && num > limit) {				\
+		ret = -EINVAL;						\
+		goto end;						\
+	}								\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_midi_opts_, name);
+
+F_MIDI_OPT(index, true, SNDRV_CARDS);
+F_MIDI_OPT(buflen, false, 0);
+F_MIDI_OPT(qlen, false, 0);
+F_MIDI_OPT(in_ports, true, MAX_PORTS);
+F_MIDI_OPT(out_ports, true, MAX_PORTS);
+
+static ssize_t f_midi_opts_id_show(struct config_item *item, char *page)
+{
+	struct f_midi_opts *opts = to_f_midi_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	if (opts->id) {
+		result = strlcpy(page, opts->id, PAGE_SIZE);
+	} else {
+		page[0] = 0;
+		result = 0;
+	}
+
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_midi_opts_id_store(struct config_item *item,
+				    const char *page, size_t len)
+{
+	struct f_midi_opts *opts = to_f_midi_opts(item);
+	int ret;
+	char *c;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt > 1) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	c = kstrndup(page, len, GFP_KERNEL);
+	if (!c) {
+		ret = -ENOMEM;
+		goto end;
+	}
+	if (opts->id_allocated)
+		kfree(opts->id);
+	opts->id = c;
+	opts->id_allocated = true;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_midi_opts_, id);
+
+static struct configfs_attribute *midi_attrs[] = {
+	&f_midi_opts_attr_index,
+	&f_midi_opts_attr_buflen,
+	&f_midi_opts_attr_qlen,
+	&f_midi_opts_attr_in_ports,
+	&f_midi_opts_attr_out_ports,
+	&f_midi_opts_attr_id,
+	NULL,
+};
+
+static const struct config_item_type midi_func_type = {
+	.ct_item_ops	= &midi_item_ops,
+	.ct_attrs	= midi_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_midi_free_inst(struct usb_function_instance *f)
+{
+	struct f_midi_opts *opts;
+	bool free = false;
+
+	opts = container_of(f, struct f_midi_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	if (!--opts->refcnt) {
+		free = true;
+	}
+	mutex_unlock(&opts->lock);
+
+	if (free) {
+		if (opts->id_allocated)
+			kfree(opts->id);
+		kfree(opts);
+	}
+}
+
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+extern struct device *create_function_device(char *name);
+static ssize_t alsa_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct usb_function_instance *fi_midi = dev_get_drvdata(dev);
+	struct f_midi *midi;
+
+	if (!fi_midi->f)
+		dev_warn(dev, "f_midi: function not set\n");
+
+	if (fi_midi && fi_midi->f) {
+		midi = func_to_midi(fi_midi->f);
+		if (midi->rmidi && midi->rmidi->card)
+			return sprintf(buf, "%d %d\n",
+			midi->rmidi->card->number, midi->rmidi->device);
+	}
+
+	/* print PCM card and device numbers */
+	return sprintf(buf, "%d %d\n", -1, -1);
+}
+
+static DEVICE_ATTR(alsa, S_IRUGO, alsa_show, NULL);
+
+static struct device_attribute *alsa_function_attributes[] = {
+	&dev_attr_alsa,
+	NULL
+};
+
+static int create_alsa_device(struct usb_function_instance *fi)
+{
+	struct device *dev;
+	struct device_attribute **attrs;
+	struct device_attribute *attr;
+	int err = 0;
+
+	dev = create_function_device("f_midi");
+	if (IS_ERR(dev))
+		return PTR_ERR(dev);
+
+	attrs = alsa_function_attributes;
+	if (attrs) {
+		while ((attr = *attrs++) && !err)
+			err = device_create_file(dev, attr);
+		if (err) {
+			device_destroy(dev->class, dev->devt);
+			return -EINVAL;
+		}
+	}
+	dev_set_drvdata(dev, fi);
+	return 0;
+}
+#else
+static int create_alsa_device(struct usb_function_instance *fi)
+{
+	return 0;
+}
+#endif
+
+static struct usb_function_instance *f_midi_alloc_inst(void)
+{
+	struct f_midi_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_midi_free_inst;
+	opts->index = SNDRV_DEFAULT_IDX1;
+	opts->id = SNDRV_DEFAULT_STR1;
+	opts->buflen = 512;
+	opts->qlen = 32;
+	opts->in_ports = 1;
+	opts->out_ports = 1;
+	opts->refcnt = 1;
+
+	if (create_alsa_device(&opts->func_inst)) {
+		kfree(opts);
+		return ERR_PTR(-ENODEV);
+	}
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &midi_func_type);
+
+	return &opts->func_inst;
+}
+
+static void f_midi_free(struct usb_function *f)
+{
+	struct f_midi *midi;
+	struct f_midi_opts *opts;
+	bool free = false;
+
+	midi = func_to_midi(f);
+	opts = container_of(f->fi, struct f_midi_opts, func_inst);
+	mutex_lock(&opts->lock);
+	if (!--midi->free_ref) {
+		kfree(midi->id);
+		kfifo_free(&midi->in_req_fifo);
+		kfree(midi);
+		free = true;
+		opts->func_inst.f = NULL;
+	}
+	mutex_unlock(&opts->lock);
+
+	if (free)
+		f_midi_free_inst(&opts->func_inst);
+}
+
+static void f_midi_rmidi_free(struct snd_rawmidi *rmidi)
+{
+	f_midi_free(rmidi->private_data);
+}
+
+static void f_midi_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_midi *midi = func_to_midi(f);
+	struct snd_card *card;
+
+	DBG(cdev, "unbind\n");
+
+	/* just to be sure */
+	f_midi_disable(f);
+
+	card = midi->card;
+	midi->card = NULL;
+	if (card)
+		snd_card_free_when_closed(card);
+
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
+{
+	struct f_midi *midi = NULL;
+	struct f_midi_opts *opts;
+	int status, i;
+
+	opts = container_of(fi, struct f_midi_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	/* sanity check */
+	if (opts->in_ports > MAX_PORTS || opts->out_ports > MAX_PORTS) {
+		status = -EINVAL;
+		goto setup_fail;
+	}
+
+	/* allocate and initialize one new instance */
+	midi = kzalloc(struct_size(midi, in_ports_array, opts->in_ports),
+		       GFP_KERNEL);
+	if (!midi) {
+		status = -ENOMEM;
+		goto setup_fail;
+	}
+
+	for (i = 0; i < opts->in_ports; i++)
+		midi->in_ports_array[i].cable = i;
+
+	/* set up ALSA midi devices */
+	midi->id = kstrdup(opts->id, GFP_KERNEL);
+	if (opts->id && !midi->id) {
+		status = -ENOMEM;
+		goto midi_free;
+	}
+	midi->in_ports = opts->in_ports;
+	midi->out_ports = opts->out_ports;
+	midi->index = opts->index;
+	midi->buflen = opts->buflen;
+	midi->qlen = opts->qlen;
+	midi->in_last_port = 0;
+	midi->free_ref = 1;
+
+	status = kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL);
+	if (status)
+		goto midi_free;
+
+	spin_lock_init(&midi->transmit_lock);
+
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	midi->func.name		= "gmidi function";
+	midi->func.bind		= f_midi_bind;
+	midi->func.unbind	= f_midi_unbind;
+	midi->func.set_alt	= f_midi_set_alt;
+	midi->func.disable	= f_midi_disable;
+	midi->func.free_func	= f_midi_free;
+
+	fi->f = &midi->func;
+	return &midi->func;
+
+midi_free:
+	if (midi)
+		kfree(midi->id);
+	kfree(midi);
+setup_fail:
+	mutex_unlock(&opts->lock);
+
+	return ERR_PTR(status);
+}
+
+DECLARE_USB_FUNCTION_INIT(midi, f_midi_alloc_inst, f_midi_alloc);
diff --git a/marvell/linux/drivers/usb/gadget/function/f_ncm.c b/marvell/linux/drivers/usb/gadget/function/f_ncm.c
new file mode 100644
index 0000000..ca50257
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_ncm.c
@@ -0,0 +1,1765 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_ncm.c -- USB CDC Network (NCM) link function driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Contact: Yauheni Kaliuta <yauheni.kaliuta@nokia.com>
+ *
+ * The driver borrows from f_ecm.c which is:
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ */
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/crc32.h>
+
+#include <linux/usb/cdc.h>
+
+#include "u_ether.h"
+#include "u_ether_configfs.h"
+#include "u_ncm.h"
+#include "configfs.h"
+
+/*
+ * This function is a "CDC Network Control Model" (CDC NCM) Ethernet link.
+ * NCM is intended to be used with high-speed network attachments.
+ *
+ * Note that NCM requires the use of "alternate settings" for its data
+ * interface.  This means that the set_alt() method has real work to do,
+ * and also means that a get_alt() method is required.
+ */
+
+/* to trigger crc/non-crc ndp signature */
+
+#define NCM_NDP_HDR_CRC		0x01000000
+
+enum ncm_notify_state {
+	NCM_NOTIFY_NONE,		/* don't notify */
+	NCM_NOTIFY_CONNECT,		/* issue CONNECT next */
+	NCM_NOTIFY_SPEED,		/* issue SPEED_CHANGE next */
+};
+
+struct f_ncm {
+	struct gether			port;
+	u8				ctrl_id, data_id;
+
+	char				ethaddr[14];
+
+	struct usb_ep			*notify;
+	struct usb_request		*notify_req;
+	u8				notify_state;
+	atomic_t			notify_count;
+	bool				is_open;
+
+	const struct ndp_parser_opts	*parser_opts;
+	bool				is_crc;
+	u32				ndp_sign;
+
+	/*
+	 * for notification, it is accessed from both
+	 * callback and ethernet open/close
+	 */
+	spinlock_t			lock;
+
+	struct net_device		*netdev;
+
+	/* For multi-frame NDP TX */
+	struct sk_buff			*skb_tx_data;
+	struct sk_buff			*skb_tx_ndp;
+	u16				ndp_dgram_count;
+	bool				timer_force_tx;
+	struct hrtimer			task_timer;
+	bool				timer_stopping;
+};
+
+static inline struct f_ncm *func_to_ncm(struct usb_function *f)
+{
+	return container_of(f, struct f_ncm, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static inline unsigned ncm_bitrate(struct usb_gadget *g)
+{
+	if (!g)
+		return 0;
+	else if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS)
+		return 4250000000U;
+	else if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+		return 3750000000U;
+	else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return 13 * 512 * 8 * 1000 * 8;
+	else
+		return 19 *  64 * 1 * 1000 * 8;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * We cannot group frames so use just the minimal size which ok to put
+ * one max-size ethernet frame.
+ * If the host can group frames, allow it to do that, 16K is selected,
+ * because it's used by default by the current linux host driver
+ */
+#define NTB_DEFAULT_IN_SIZE	16384
+#define NTB_OUT_SIZE		16384
+
+/* Allocation for storing the NDP, 32 should suffice for a
+ * 16k packet. This allows a maximum of 32 * 507 Byte packets to
+ * be transmitted in a single 16kB skb, though when sending full size
+ * packets this limit will be plenty.
+ * Smaller packets are not likely to be trying to maximize the
+ * throughput and will be mstly sending smaller infrequent frames.
+ */
+#define TX_MAX_NUM_DPE		32
+
+/* Delay for the transmit to wait before sending an unfilled NTB frame. */
+#define TX_TIMEOUT_NSECS	300000
+
+#define FORMATS_SUPPORTED	(USB_CDC_NCM_NTB16_SUPPORTED |	\
+				 USB_CDC_NCM_NTB32_SUPPORTED)
+
+static struct usb_cdc_ncm_ntb_parameters ntb_parameters = {
+	.wLength = cpu_to_le16(sizeof(ntb_parameters)),
+	.bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED),
+	.dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE),
+	.wNdpInDivisor = cpu_to_le16(4),
+	.wNdpInPayloadRemainder = cpu_to_le16(0),
+	.wNdpInAlignment = cpu_to_le16(4),
+
+	.dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE),
+	.wNdpOutDivisor = cpu_to_le16(4),
+	.wNdpOutPayloadRemainder = cpu_to_le16(0),
+	.wNdpOutAlignment = cpu_to_le16(4),
+};
+
+/*
+ * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
+ * packet, to simplify cancellation; and a big transfer interval, to
+ * waste less bandwidth.
+ */
+
+#define NCM_STATUS_INTERVAL_MS		32
+#define NCM_STATUS_BYTECOUNT		16	/* 8 byte header + data */
+
+static struct usb_interface_assoc_descriptor ncm_iad_desc = {
+	.bLength =		sizeof ncm_iad_desc,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,	/* control + data */
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_NCM,
+	.bFunctionProtocol =	USB_CDC_PROTO_NONE,
+	/* .iFunction =		DYNAMIC */
+};
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor ncm_control_intf = {
+	.bLength =		sizeof ncm_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_NCM,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc ncm_header_desc = {
+	.bLength =		sizeof ncm_header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_union_desc ncm_union_desc = {
+	.bLength =		sizeof(ncm_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+static struct usb_cdc_ether_desc ecm_desc = {
+	.bLength =		sizeof ecm_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ETHERNET_TYPE,
+
+	/* this descriptor actually adds value, surprise! */
+	/* .iMACAddress = DYNAMIC */
+	.bmEthernetStatistics =	cpu_to_le32(0), /* no statistics */
+	.wMaxSegmentSize =	cpu_to_le16(ETH_FRAME_LEN),
+	.wNumberMCFilters =	cpu_to_le16(0),
+	.bNumberPowerFilters =	0,
+};
+
+#define NCAPS	(USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE)
+
+static struct usb_cdc_ncm_desc ncm_desc = {
+	.bLength =		sizeof ncm_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_NCM_TYPE,
+
+	.bcdNcmVersion =	cpu_to_le16(0x0100),
+	/* can process SetEthernetPacketFilter */
+	.bmNetworkCapabilities = NCAPS,
+};
+
+/* the default data interface has no endpoints ... */
+
+static struct usb_interface_descriptor ncm_data_nop_intf = {
+	.bLength =		sizeof ncm_data_nop_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_NCM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+/* ... but the "real" data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor ncm_data_intf = {
+	.bLength =		sizeof ncm_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	USB_CDC_NCM_PROTO_NTB,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_ncm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		NCM_STATUS_INTERVAL_MS,
+};
+
+static struct usb_endpoint_descriptor fs_ncm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_ncm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *ncm_fs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &ncm_header_desc,
+	(struct usb_descriptor_header *) &ncm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &fs_ncm_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &fs_ncm_in_desc,
+	(struct usb_descriptor_header *) &fs_ncm_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_ncm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(NCM_STATUS_INTERVAL_MS),
+};
+static struct usb_endpoint_descriptor hs_ncm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_ncm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *ncm_hs_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &ncm_header_desc,
+	(struct usb_descriptor_header *) &ncm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &hs_ncm_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &hs_ncm_in_desc,
+	(struct usb_descriptor_header *) &hs_ncm_out_desc,
+	NULL,
+};
+
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_ncm_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(NCM_STATUS_INTERVAL_MS)
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ncm_notify_comp_desc = {
+	.bLength =		sizeof(ss_ncm_notify_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(NCM_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ss_ncm_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_ncm_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ncm_bulk_comp_desc = {
+	.bLength =		sizeof(ss_ncm_bulk_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *ncm_ss_function[] = {
+	(struct usb_descriptor_header *) &ncm_iad_desc,
+	/* CDC NCM control descriptors */
+	(struct usb_descriptor_header *) &ncm_control_intf,
+	(struct usb_descriptor_header *) &ncm_header_desc,
+	(struct usb_descriptor_header *) &ncm_union_desc,
+	(struct usb_descriptor_header *) &ecm_desc,
+	(struct usb_descriptor_header *) &ncm_desc,
+	(struct usb_descriptor_header *) &ss_ncm_notify_desc,
+	(struct usb_descriptor_header *) &ss_ncm_notify_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ncm_data_nop_intf,
+	(struct usb_descriptor_header *) &ncm_data_intf,
+	(struct usb_descriptor_header *) &ss_ncm_in_desc,
+	(struct usb_descriptor_header *) &ss_ncm_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_ncm_out_desc,
+	(struct usb_descriptor_header *) &ss_ncm_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+#define STRING_CTRL_IDX	0
+#define STRING_MAC_IDX	1
+#define STRING_DATA_IDX	2
+#define STRING_IAD_IDX	3
+
+static struct usb_string ncm_string_defs[] = {
+	[STRING_CTRL_IDX].s = "CDC Network Control Model (NCM)",
+	[STRING_MAC_IDX].s = "",
+	[STRING_DATA_IDX].s = "CDC Network Data",
+	[STRING_IAD_IDX].s = "CDC NCM",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings ncm_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		ncm_string_defs,
+};
+
+static struct usb_gadget_strings *ncm_strings[] = {
+	&ncm_string_table,
+	NULL,
+};
+
+/*
+ * Here are options for NCM Datagram Pointer table (NDP) parser.
+ * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3),
+ * in NDP16 offsets and sizes fields are 1 16bit word wide,
+ * in NDP32 -- 2 16bit words wide. Also signatures are different.
+ * To make the parser code the same, put the differences in the structure,
+ * and switch pointers to the structures when the format is changed.
+ */
+
+struct ndp_parser_opts {
+	u32		nth_sign;
+	u32		ndp_sign;
+	unsigned	nth_size;
+	unsigned	ndp_size;
+	unsigned	dpe_size;
+	unsigned	ndplen_align;
+	/* sizes in u16 units */
+	unsigned	dgram_item_len; /* index or length */
+	unsigned	block_length;
+	unsigned	ndp_index;
+	unsigned	reserved1;
+	unsigned	reserved2;
+	unsigned	next_ndp_index;
+};
+
+#define INIT_NDP16_OPTS {					\
+		.nth_sign = USB_CDC_NCM_NTH16_SIGN,		\
+		.ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN,	\
+		.nth_size = sizeof(struct usb_cdc_ncm_nth16),	\
+		.ndp_size = sizeof(struct usb_cdc_ncm_ndp16),	\
+		.dpe_size = sizeof(struct usb_cdc_ncm_dpe16),	\
+		.ndplen_align = 4,				\
+		.dgram_item_len = 1,				\
+		.block_length = 1,				\
+		.ndp_index = 1,					\
+		.reserved1 = 0,					\
+		.reserved2 = 0,					\
+		.next_ndp_index = 1,				\
+	}
+
+
+#define INIT_NDP32_OPTS {					\
+		.nth_sign = USB_CDC_NCM_NTH32_SIGN,		\
+		.ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN,	\
+		.nth_size = sizeof(struct usb_cdc_ncm_nth32),	\
+		.ndp_size = sizeof(struct usb_cdc_ncm_ndp32),	\
+		.dpe_size = sizeof(struct usb_cdc_ncm_dpe32),	\
+		.ndplen_align = 8,				\
+		.dgram_item_len = 2,				\
+		.block_length = 2,				\
+		.ndp_index = 2,					\
+		.reserved1 = 1,					\
+		.reserved2 = 2,					\
+		.next_ndp_index = 2,				\
+	}
+
+static const struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
+static const struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS;
+
+static inline void put_ncm(__le16 **p, unsigned size, unsigned val)
+{
+	switch (size) {
+	case 1:
+		put_unaligned_le16((u16)val, *p);
+		break;
+	case 2:
+		put_unaligned_le32((u32)val, *p);
+
+		break;
+	default:
+		BUG();
+	}
+
+	*p += size;
+}
+
+static inline unsigned get_ncm(__le16 **p, unsigned size)
+{
+	unsigned tmp;
+
+	switch (size) {
+	case 1:
+		tmp = get_unaligned_le16(*p);
+		break;
+	case 2:
+		tmp = get_unaligned_le32(*p);
+		break;
+	default:
+		BUG();
+	}
+
+	*p += size;
+	return tmp;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static inline void ncm_reset_values(struct f_ncm *ncm)
+{
+	ncm->parser_opts = &ndp16_opts;
+	ncm->is_crc = false;
+	ncm->ndp_sign = ncm->parser_opts->ndp_sign;
+	ncm->port.cdc_filter = DEFAULT_FILTER;
+
+	/* doesn't make sense for ncm, fixed size used */
+	ncm->port.header_len = 0;
+
+	ncm->port.fixed_out_len = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+	ncm->port.fixed_in_len = NTB_DEFAULT_IN_SIZE;
+}
+
+/*
+ * Context: ncm->lock held
+ */
+static void ncm_do_notify(struct f_ncm *ncm)
+{
+	struct usb_request		*req = ncm->notify_req;
+	struct usb_cdc_notification	*event;
+	struct usb_composite_dev	*cdev = ncm->port.func.config->cdev;
+	__le32				*data;
+	int				status;
+
+	/* notification already in flight? */
+	if (atomic_read(&ncm->notify_count))
+		return;
+
+	event = req->buf;
+	switch (ncm->notify_state) {
+	case NCM_NOTIFY_NONE:
+		return;
+
+	case NCM_NOTIFY_CONNECT:
+		event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+		if (ncm->is_open)
+			event->wValue = cpu_to_le16(1);
+		else
+			event->wValue = cpu_to_le16(0);
+		event->wLength = 0;
+		req->length = sizeof *event;
+
+		DBG(cdev, "notify connect %s\n",
+				ncm->is_open ? "true" : "false");
+		ncm->notify_state = NCM_NOTIFY_NONE;
+		break;
+
+	case NCM_NOTIFY_SPEED:
+		event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(8);
+		req->length = NCM_STATUS_BYTECOUNT;
+
+		/* SPEED_CHANGE data is up/down speeds in bits/sec */
+		data = req->buf + sizeof *event;
+		data[0] = cpu_to_le32(ncm_bitrate(cdev->gadget));
+		data[1] = data[0];
+
+		DBG(cdev, "notify speed %u\n", ncm_bitrate(cdev->gadget));
+		ncm->notify_state = NCM_NOTIFY_CONNECT;
+		break;
+	}
+	event->bmRequestType = 0xA1;
+	event->wIndex = cpu_to_le16(ncm->ctrl_id);
+
+	atomic_inc(&ncm->notify_count);
+
+	/*
+	 * In double buffering if there is a space in FIFO,
+	 * completion callback can be called right after the call,
+	 * so unlocking
+	 */
+	spin_unlock(&ncm->lock);
+	status = usb_ep_queue(ncm->notify, req, GFP_ATOMIC);
+	spin_lock(&ncm->lock);
+	if (status < 0) {
+		atomic_dec(&ncm->notify_count);
+		DBG(cdev, "notify --> %d\n", status);
+	}
+}
+
+/*
+ * Context: ncm->lock held
+ */
+static void ncm_notify(struct f_ncm *ncm)
+{
+	/*
+	 * NOTE on most versions of Linux, host side cdc-ethernet
+	 * won't listen for notifications until its netdevice opens.
+	 * The first notification then sits in the FIFO for a long
+	 * time, and the second one is queued.
+	 *
+	 * If ncm_notify() is called before the second (CONNECT)
+	 * notification is sent, then it will reset to send the SPEED
+	 * notificaion again (and again, and again), but it's not a problem
+	 */
+	ncm->notify_state = NCM_NOTIFY_SPEED;
+	ncm_do_notify(ncm);
+}
+
+static void ncm_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_ncm			*ncm = req->context;
+	struct usb_composite_dev	*cdev = ncm->port.func.config->cdev;
+	struct usb_cdc_notification	*event = req->buf;
+
+	spin_lock(&ncm->lock);
+	switch (req->status) {
+	case 0:
+		VDBG(cdev, "Notification %02x sent\n",
+		     event->bNotificationType);
+		atomic_dec(&ncm->notify_count);
+		break;
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		atomic_set(&ncm->notify_count, 0);
+		ncm->notify_state = NCM_NOTIFY_NONE;
+		break;
+	default:
+		DBG(cdev, "event %02x --> %d\n",
+			event->bNotificationType, req->status);
+		atomic_dec(&ncm->notify_count);
+		break;
+	}
+	ncm_do_notify(ncm);
+	spin_unlock(&ncm->lock);
+}
+
+static void ncm_ep0out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	/* now for SET_NTB_INPUT_SIZE only */
+	unsigned		in_size;
+	struct usb_function	*f = req->context;
+	struct f_ncm		*ncm = func_to_ncm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	req->context = NULL;
+	if (req->status || req->actual != req->length) {
+		DBG(cdev, "Bad control-OUT transfer\n");
+		goto invalid;
+	}
+
+	in_size = get_unaligned_le32(req->buf);
+	if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+	    in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) {
+		DBG(cdev, "Got wrong INPUT SIZE (%d) from host\n", in_size);
+		goto invalid;
+	}
+
+	ncm->port.fixed_in_len = in_size;
+	VDBG(cdev, "Set NTB INPUT SIZE %d\n", in_size);
+	return;
+
+invalid:
+	usb_ep_set_halt(ep);
+	return;
+}
+
+static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_ncm		*ncm = func_to_ncm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/*
+	 * composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SET_ETHERNET_PACKET_FILTER:
+		/*
+		 * see 6.2.30: no data, wIndex = interface,
+		 * wValue = packet filter bitmap
+		 */
+		if (w_length != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		DBG(cdev, "packet filter %02x\n", w_value);
+		/*
+		 * REVISIT locking of cdc_filter.  This assumes the UDC
+		 * driver won't have a concurrent packet TX irq running on
+		 * another CPU; or that if it does, this write is atomic...
+		 */
+		ncm->port.cdc_filter = w_value;
+		value = 0;
+		break;
+	/*
+	 * and optionally:
+	 * case USB_CDC_SEND_ENCAPSULATED_COMMAND:
+	 * case USB_CDC_GET_ENCAPSULATED_RESPONSE:
+	 * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
+	 * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
+	 * case USB_CDC_GET_ETHERNET_STATISTIC:
+	 */
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_PARAMETERS:
+
+		if (w_length == 0 || w_value != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		value = w_length > sizeof ntb_parameters ?
+			sizeof ntb_parameters : w_length;
+		memcpy(req->buf, &ntb_parameters, value);
+		VDBG(cdev, "Host asked NTB parameters\n");
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_INPUT_SIZE:
+
+		if (w_length < 4 || w_value != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		put_unaligned_le32(ncm->port.fixed_in_len, req->buf);
+		value = 4;
+		VDBG(cdev, "Host asked INPUT SIZE, sending %d\n",
+		     ncm->port.fixed_in_len);
+		break;
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_NTB_INPUT_SIZE:
+	{
+		if (w_length != 4 || w_value != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		req->complete = ncm_ep0out_complete;
+		req->length = w_length;
+		req->context = f;
+
+		value = req->length;
+		break;
+	}
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_FORMAT:
+	{
+		uint16_t format;
+
+		if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		format = (ncm->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001;
+		put_unaligned_le16(format, req->buf);
+		value = 2;
+		VDBG(cdev, "Host asked NTB FORMAT, sending %d\n", format);
+		break;
+	}
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_NTB_FORMAT:
+	{
+		if (w_length != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		switch (w_value) {
+		case 0x0000:
+			ncm->parser_opts = &ndp16_opts;
+			DBG(cdev, "NCM16 selected\n");
+			break;
+		case 0x0001:
+			ncm->parser_opts = &ndp32_opts;
+			DBG(cdev, "NCM32 selected\n");
+			break;
+		default:
+			goto invalid;
+		}
+		value = 0;
+		break;
+	}
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_CRC_MODE:
+	{
+		uint16_t is_crc;
+
+		if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		is_crc = ncm->is_crc ? 0x0001 : 0x0000;
+		put_unaligned_le16(is_crc, req->buf);
+		value = 2;
+		VDBG(cdev, "Host asked CRC MODE, sending %d\n", is_crc);
+		break;
+	}
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_CRC_MODE:
+	{
+		if (w_length != 0 || w_index != ncm->ctrl_id)
+			goto invalid;
+		switch (w_value) {
+		case 0x0000:
+			ncm->is_crc = false;
+			DBG(cdev, "non-CRC mode selected\n");
+			break;
+		case 0x0001:
+			ncm->is_crc = true;
+			DBG(cdev, "CRC mode selected\n");
+			break;
+		default:
+			goto invalid;
+		}
+		value = 0;
+		break;
+	}
+
+	/* and disabled in ncm descriptor: */
+	/* case USB_CDC_GET_NET_ADDRESS: */
+	/* case USB_CDC_SET_NET_ADDRESS: */
+	/* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */
+	/* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */
+
+	default:
+invalid:
+		DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+	ncm->ndp_sign = ncm->parser_opts->ndp_sign |
+		(ncm->is_crc ? NCM_NDP_HDR_CRC : 0);
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "ncm req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "ncm req %02x.%02x response err %d\n",
+					ctrl->bRequestType, ctrl->bRequest,
+					value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+
+static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_ncm		*ncm = func_to_ncm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* Control interface has only altsetting 0 */
+	if (intf == ncm->ctrl_id) {
+		if (alt != 0)
+			goto fail;
+
+		DBG(cdev, "reset ncm control %d\n", intf);
+		usb_ep_disable(ncm->notify);
+
+		if (!(ncm->notify->desc)) {
+			DBG(cdev, "init ncm ctrl %d\n", intf);
+			if (config_ep_by_speed(cdev->gadget, f, ncm->notify))
+				goto fail;
+		}
+		usb_ep_enable(ncm->notify);
+
+	/* Data interface has two altsettings, 0 and 1 */
+	} else if (intf == ncm->data_id) {
+		if (alt > 1)
+			goto fail;
+
+		if (ncm->port.in_ep->enabled) {
+			DBG(cdev, "reset ncm\n");
+			ncm->timer_stopping = true;
+			ncm->netdev = NULL;
+			gether_disconnect(&ncm->port);
+			ncm_reset_values(ncm);
+		}
+
+		/*
+		 * CDC Network only sends data in non-default altsettings.
+		 * Changing altsettings resets filters, statistics, etc.
+		 */
+		if (alt == 1) {
+			struct net_device	*net;
+
+			if (!ncm->port.in_ep->desc ||
+			    !ncm->port.out_ep->desc) {
+				DBG(cdev, "init ncm\n");
+				if (config_ep_by_speed(cdev->gadget, f,
+						       ncm->port.in_ep) ||
+				    config_ep_by_speed(cdev->gadget, f,
+						       ncm->port.out_ep)) {
+					ncm->port.in_ep->desc = NULL;
+					ncm->port.out_ep->desc = NULL;
+					goto fail;
+				}
+			}
+
+			/* TODO */
+			/* Enable zlps by default for NCM conformance;
+			 * override for musb_hdrc (avoids txdma ovhead)
+			 */
+			ncm->port.is_zlp_ok =
+				gadget_is_zlp_supported(cdev->gadget);
+			ncm->port.cdc_filter = DEFAULT_FILTER;
+			DBG(cdev, "activate ncm\n");
+			net = gether_connect(&ncm->port);
+			if (IS_ERR(net))
+				return PTR_ERR(net);
+			ncm->netdev = net;
+			ncm->timer_stopping = false;
+		}
+
+		spin_lock(&ncm->lock);
+		ncm_notify(ncm);
+		spin_unlock(&ncm->lock);
+	} else
+		goto fail;
+
+	return 0;
+fail:
+	return -EINVAL;
+}
+
+/*
+ * Because the data interface supports multiple altsettings,
+ * this NCM function *MUST* implement a get_alt() method.
+ */
+static int ncm_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_ncm		*ncm = func_to_ncm(f);
+
+	if (intf == ncm->ctrl_id)
+		return 0;
+	return ncm->port.in_ep->enabled ? 1 : 0;
+}
+
+static struct sk_buff *package_for_tx(struct f_ncm *ncm)
+{
+	__le16		*ntb_iter;
+	struct sk_buff	*skb2 = NULL;
+	unsigned	ndp_pad;
+	unsigned	ndp_index;
+	unsigned	new_len;
+
+	const struct ndp_parser_opts *opts = ncm->parser_opts;
+	const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
+	const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
+
+	/* Stop the timer */
+	hrtimer_try_to_cancel(&ncm->task_timer);
+
+	ndp_pad = ALIGN(ncm->skb_tx_data->len, ndp_align) -
+			ncm->skb_tx_data->len;
+	ndp_index = ncm->skb_tx_data->len + ndp_pad;
+	new_len = ndp_index + dgram_idx_len + ncm->skb_tx_ndp->len;
+
+	/* Set the final BlockLength and wNdpIndex */
+	ntb_iter = (void *) ncm->skb_tx_data->data;
+	/* Increment pointer to BlockLength */
+	ntb_iter += 2 + 1 + 1;
+	put_ncm(&ntb_iter, opts->block_length, new_len);
+	put_ncm(&ntb_iter, opts->ndp_index, ndp_index);
+
+	/* Set the final NDP wLength */
+	new_len = opts->ndp_size +
+			(ncm->ndp_dgram_count * dgram_idx_len);
+	ncm->ndp_dgram_count = 0;
+	/* Increment from start to wLength */
+	ntb_iter = (void *) ncm->skb_tx_ndp->data;
+	ntb_iter += 2;
+	put_unaligned_le16(new_len, ntb_iter);
+
+	/* Merge the skbs */
+	swap(skb2, ncm->skb_tx_data);
+	if (ncm->skb_tx_data) {
+		dev_consume_skb_any(ncm->skb_tx_data);
+		ncm->skb_tx_data = NULL;
+	}
+
+	/* Insert NDP alignment. */
+	skb_put_zero(skb2, ndp_pad);
+
+	/* Copy NTB across. */
+	skb_put_data(skb2, ncm->skb_tx_ndp->data, ncm->skb_tx_ndp->len);
+	dev_consume_skb_any(ncm->skb_tx_ndp);
+	ncm->skb_tx_ndp = NULL;
+
+	/* Insert zero'd datagram. */
+	skb_put_zero(skb2, dgram_idx_len);
+
+	return skb2;
+}
+
+static struct sk_buff *ncm_wrap_ntb(struct gether *port,
+				    struct sk_buff *skb)
+{
+	struct f_ncm	*ncm = func_to_ncm(&port->func);
+	struct sk_buff	*skb2 = NULL;
+	int		ncb_len = 0;
+	__le16		*ntb_data;
+	__le16		*ntb_ndp;
+	int		dgram_pad;
+
+	unsigned	max_size = ncm->port.fixed_in_len;
+	const struct ndp_parser_opts *opts = ncm->parser_opts;
+	const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
+	const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor);
+	const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder);
+	const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
+
+	if (!skb && !ncm->skb_tx_data)
+		return NULL;
+
+	if (skb) {
+		/* Add the CRC if required up front */
+		if (ncm->is_crc) {
+			uint32_t	crc;
+			__le16		*crc_pos;
+
+			crc = ~crc32_le(~0,
+					skb->data,
+					skb->len);
+			crc_pos = skb_put(skb, sizeof(uint32_t));
+			put_unaligned_le32(crc, crc_pos);
+		}
+
+		/* If the new skb is too big for the current NCM NTB then
+		 * set the current stored skb to be sent now and clear it
+		 * ready for new data.
+		 * NOTE: Assume maximum align for speed of calculation.
+		 */
+		if (ncm->skb_tx_data
+		    && (ncm->ndp_dgram_count >= TX_MAX_NUM_DPE
+		    || (ncm->skb_tx_data->len +
+		    div + rem + skb->len +
+		    ncm->skb_tx_ndp->len + ndp_align + (2 * dgram_idx_len))
+		    > max_size)) {
+			skb2 = package_for_tx(ncm);
+			if (!skb2)
+				goto err;
+		}
+
+		if (!ncm->skb_tx_data) {
+			ncb_len = opts->nth_size;
+			dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len;
+			ncb_len += dgram_pad;
+
+			/* Create a new skb for the NTH and datagrams. */
+			ncm->skb_tx_data = alloc_skb(max_size, GFP_ATOMIC);
+			if (!ncm->skb_tx_data)
+				goto err;
+
+			ncm->skb_tx_data->dev = ncm->netdev;
+			ntb_data = skb_put_zero(ncm->skb_tx_data, ncb_len);
+			/* dwSignature */
+			put_unaligned_le32(opts->nth_sign, ntb_data);
+			ntb_data += 2;
+			/* wHeaderLength */
+			put_unaligned_le16(opts->nth_size, ntb_data++);
+
+			/* Allocate an skb for storing the NDP,
+			 * TX_MAX_NUM_DPE should easily suffice for a
+			 * 16k packet.
+			 */
+			ncm->skb_tx_ndp = alloc_skb((int)(opts->ndp_size
+						    + opts->dpe_size
+						    * TX_MAX_NUM_DPE),
+						    GFP_ATOMIC);
+			if (!ncm->skb_tx_ndp)
+				goto err;
+
+			ncm->skb_tx_ndp->dev = ncm->netdev;
+			ntb_ndp = skb_put(ncm->skb_tx_ndp, opts->ndp_size);
+			memset(ntb_ndp, 0, ncb_len);
+			/* dwSignature */
+			put_unaligned_le32(ncm->ndp_sign, ntb_ndp);
+			ntb_ndp += 2;
+
+			/* There is always a zeroed entry */
+			ncm->ndp_dgram_count = 1;
+
+			/* Note: we skip opts->next_ndp_index */
+
+			/* Start the timer. */
+			hrtimer_start(&ncm->task_timer, TX_TIMEOUT_NSECS,
+				      HRTIMER_MODE_REL_SOFT);
+		}
+
+		/* Add the datagram position entries */
+		ntb_ndp = skb_put_zero(ncm->skb_tx_ndp, dgram_idx_len);
+
+		ncb_len = ncm->skb_tx_data->len;
+		dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len;
+		ncb_len += dgram_pad;
+
+		/* (d)wDatagramIndex */
+		put_ncm(&ntb_ndp, opts->dgram_item_len, ncb_len);
+		/* (d)wDatagramLength */
+		put_ncm(&ntb_ndp, opts->dgram_item_len, skb->len);
+		ncm->ndp_dgram_count++;
+
+		/* Add the new data to the skb */
+		skb_put_zero(ncm->skb_tx_data, dgram_pad);
+		skb_put_data(ncm->skb_tx_data, skb->data, skb->len);
+		dev_consume_skb_any(skb);
+		skb = NULL;
+
+	} else if (ncm->skb_tx_data && ncm->timer_force_tx) {
+		/* If the tx was requested because of a timeout then send */
+		skb2 = package_for_tx(ncm);
+		if (!skb2)
+			goto err;
+	}
+
+	return skb2;
+
+err:
+	ncm->netdev->stats.tx_dropped++;
+
+	if (skb)
+		dev_kfree_skb_any(skb);
+	if (ncm->skb_tx_data)
+		dev_kfree_skb_any(ncm->skb_tx_data);
+	if (ncm->skb_tx_ndp)
+		dev_kfree_skb_any(ncm->skb_tx_ndp);
+
+	return NULL;
+}
+
+/*
+ * The transmit should only be run if no skb data has been sent
+ * for a certain duration.
+ */
+static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data)
+{
+	struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer);
+
+	/* Only send if data is available. */
+	if (!ncm->timer_stopping && ncm->skb_tx_data) {
+		ncm->timer_force_tx = true;
+
+		/* XXX This allowance of a NULL skb argument to ndo_start_xmit
+		 * XXX is not sane.  The gadget layer should be redesigned so
+		 * XXX that the dev->wrap() invocations to build SKBs is transparent
+		 * XXX and performed in some way outside of the ndo_start_xmit
+		 * XXX interface.
+		 */
+		ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev);
+
+		ncm->timer_force_tx = false;
+	}
+	return HRTIMER_NORESTART;
+}
+
+static int ncm_unwrap_ntb(struct gether *port,
+			  struct sk_buff *skb,
+			  struct sk_buff_head *list)
+{
+	struct f_ncm	*ncm = func_to_ncm(&port->func);
+	unsigned char	*ntb_ptr = skb->data;
+	__le16		*tmp;
+	unsigned	index, index2;
+	int		ndp_index;
+	unsigned	dg_len, dg_len2;
+	unsigned	ndp_len;
+	unsigned	block_len;
+	struct sk_buff	*skb2;
+	int		ret = -EINVAL;
+	unsigned	ntb_max = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+	unsigned	frame_max = le16_to_cpu(ecm_desc.wMaxSegmentSize);
+	const struct ndp_parser_opts *opts = ncm->parser_opts;
+	unsigned	crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
+	int		dgram_counter;
+	int		to_process = skb->len;
+
+parse_ntb:
+	tmp = (__le16 *)ntb_ptr;
+
+	/* dwSignature */
+	if (get_unaligned_le32(tmp) != opts->nth_sign) {
+		INFO(port->func.config->cdev, "Wrong NTH SIGN, skblen %d\n",
+			skb->len);
+		print_hex_dump(KERN_INFO, "HEAD:", DUMP_PREFIX_ADDRESS, 32, 1,
+			       skb->data, 32, false);
+
+		goto err;
+	}
+	tmp += 2;
+	/* wHeaderLength */
+	if (get_unaligned_le16(tmp++) != opts->nth_size) {
+		INFO(port->func.config->cdev, "Wrong NTB headersize\n");
+		goto err;
+	}
+	tmp++; /* skip wSequence */
+
+	block_len = get_ncm(&tmp, opts->block_length);
+	/* (d)wBlockLength */
+	if (block_len > ntb_max) {
+		INFO(port->func.config->cdev, "OUT size exceeded\n");
+		goto err;
+	}
+
+	ndp_index = get_ncm(&tmp, opts->ndp_index);
+
+	/* Run through all the NDP's in the NTB */
+	do {
+		/*
+		 * NCM 3.2
+		 * dwNdpIndex
+		 */
+		if (((ndp_index % 4) != 0) ||
+				(ndp_index < opts->nth_size) ||
+				(ndp_index > (block_len -
+					      opts->ndp_size))) {
+			INFO(port->func.config->cdev, "Bad index: %#X\n",
+			     ndp_index);
+			goto err;
+		}
+
+		/*
+		 * walk through NDP
+		 * dwSignature
+		 */
+		tmp = (__le16 *)(ntb_ptr + ndp_index);
+		if (get_unaligned_le32(tmp) != ncm->ndp_sign) {
+			INFO(port->func.config->cdev, "Wrong NDP SIGN\n");
+			goto err;
+		}
+		tmp += 2;
+
+		ndp_len = get_unaligned_le16(tmp++);
+		/*
+		 * NCM 3.3.1
+		 * wLength
+		 * entry is 2 items
+		 * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+		 * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry
+		 * Each entry is a dgram index and a dgram length.
+		 */
+		if ((ndp_len < opts->ndp_size
+				+ 2 * 2 * (opts->dgram_item_len * 2)) ||
+				(ndp_len % opts->ndplen_align != 0)) {
+			INFO(port->func.config->cdev, "Bad NDP length: %#X\n",
+			     ndp_len);
+			goto err;
+		}
+		tmp += opts->reserved1;
+		/* Check for another NDP (d)wNextNdpIndex */
+		ndp_index = get_ncm(&tmp, opts->next_ndp_index);
+		tmp += opts->reserved2;
+
+		ndp_len -= opts->ndp_size;
+		index2 = get_ncm(&tmp, opts->dgram_item_len);
+		dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+		dgram_counter = 0;
+
+		do {
+			index = index2;
+			/* wDatagramIndex[0] */
+			if ((index < opts->nth_size) ||
+					(index > block_len - opts->dpe_size)) {
+				INFO(port->func.config->cdev,
+				     "Bad index: %#X\n", index);
+				goto err;
+			}
+
+			dg_len = dg_len2;
+			/*
+			 * wDatagramLength[0]
+			 * ethernet hdr + crc or larger than max frame size
+			 */
+			if ((dg_len < 14 + crc_len) ||
+					(dg_len > frame_max)) {
+				INFO(port->func.config->cdev,
+				     "Bad dgram length: %#X\n", dg_len);
+				goto err;
+			}
+			if (ncm->is_crc) {
+				uint32_t crc, crc2;
+
+				crc = get_unaligned_le32(ntb_ptr +
+							 index + dg_len -
+							 crc_len);
+				crc2 = ~crc32_le(~0,
+						 ntb_ptr + index,
+						 dg_len - crc_len);
+				if (crc != crc2) {
+					INFO(port->func.config->cdev,
+					     "Bad CRC\n");
+					goto err;
+				}
+			}
+
+			index2 = get_ncm(&tmp, opts->dgram_item_len);
+			dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+
+			/* wDatagramIndex[1] */
+			if (index2 > block_len - opts->dpe_size) {
+				INFO(port->func.config->cdev,
+				     "Bad index: %#X\n", index2);
+				goto err;
+			}
+
+			/*
+			 * Copy the data into a new skb.
+			 * This ensures the truesize is correct
+			 */
+			skb2 = netdev_alloc_skb_ip_align(ncm->netdev,
+							 dg_len - crc_len);
+			if (skb2 == NULL)
+				goto err;
+			skb_put_data(skb2, ntb_ptr + index,
+				     dg_len - crc_len);
+
+			skb_queue_tail(list, skb2);
+
+			ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+			dgram_counter++;
+			if (index2 == 0 || dg_len2 == 0)
+				break;
+		} while (ndp_len > 2 * (opts->dgram_item_len * 2));
+	} while (ndp_index);
+
+	VDBG(port->func.config->cdev,
+	     "Parsed NTB with %d frames\n", dgram_counter);
+
+	to_process -= block_len;
+
+	/*
+	 * Windows NCM driver avoids USB ZLPs by adding a 1-byte
+	 * zero pad as needed.
+	 */
+	if (to_process == 1 &&
+	    (*(unsigned char *)(ntb_ptr + block_len) == 0x00)) {
+		to_process--;
+	} else if ((to_process > 0) && (block_len != 0)) {
+		ntb_ptr = (unsigned char *)(ntb_ptr + block_len);
+		goto parse_ntb;
+	}
+
+	dev_consume_skb_any(skb);
+
+	return 0;
+err:
+	skb_queue_purge(list);
+	dev_kfree_skb_any(skb);
+	return ret;
+}
+
+static void ncm_disable(struct usb_function *f)
+{
+	struct f_ncm		*ncm = func_to_ncm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "ncm deactivated\n");
+
+	if (ncm->port.in_ep->enabled) {
+		ncm->timer_stopping = true;
+		ncm->netdev = NULL;
+		gether_disconnect(&ncm->port);
+	}
+
+	if (ncm->notify->enabled) {
+		usb_ep_disable(ncm->notify);
+		ncm->notify->desc = NULL;
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Callbacks let us notify the host about connect/disconnect when the
+ * net device is opened or closed.
+ *
+ * For testing, note that link states on this side include both opened
+ * and closed variants of:
+ *
+ *   - disconnected/unconfigured
+ *   - configured but inactive (data alt 0)
+ *   - configured and active (data alt 1)
+ *
+ * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
+ * SET_INTERFACE (altsetting).  Remember also that "configured" doesn't
+ * imply the host is actually polling the notification endpoint, and
+ * likewise that "active" doesn't imply it's actually using the data
+ * endpoints for traffic.
+ */
+
+static void ncm_open(struct gether *geth)
+{
+	struct f_ncm		*ncm = func_to_ncm(&geth->func);
+
+	DBG(ncm->port.func.config->cdev, "%s\n", __func__);
+
+	spin_lock(&ncm->lock);
+	ncm->is_open = true;
+	ncm_notify(ncm);
+	spin_unlock(&ncm->lock);
+}
+
+static void ncm_close(struct gether *geth)
+{
+	struct f_ncm		*ncm = func_to_ncm(&geth->func);
+
+	DBG(ncm->port.func.config->cdev, "%s\n", __func__);
+
+	spin_lock(&ncm->lock);
+	ncm->is_open = false;
+	ncm_notify(ncm);
+	spin_unlock(&ncm->lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_ncm		*ncm = func_to_ncm(f);
+	struct usb_string	*us;
+	int			status = 0;
+	struct usb_ep		*ep;
+	struct f_ncm_opts	*ncm_opts;
+
+	if (!can_support_ecm(cdev->gadget))
+		return -EINVAL;
+
+	ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst);
+
+	if (cdev->use_os_string) {
+		f->os_desc_table = kzalloc(sizeof(*f->os_desc_table),
+					   GFP_KERNEL);
+		if (!f->os_desc_table)
+			return -ENOMEM;
+		f->os_desc_n = 1;
+		f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc;
+	}
+
+	mutex_lock(&ncm_opts->lock);
+	gether_set_gadget(ncm_opts->net, cdev->gadget);
+	if (!ncm_opts->bound)
+		status = gether_register_netdev(ncm_opts->net);
+	mutex_unlock(&ncm_opts->lock);
+
+	if (status)
+		goto fail;
+
+	ncm_opts->bound = true;
+
+	us = usb_gstrings_attach(cdev, ncm_strings,
+				 ARRAY_SIZE(ncm_string_defs));
+	if (IS_ERR(us)) {
+		status = PTR_ERR(us);
+		goto fail;
+	}
+	ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id;
+	ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id;
+	ncm_data_intf.iInterface = us[STRING_DATA_IDX].id;
+	ecm_desc.iMACAddress = us[STRING_MAC_IDX].id;
+	ncm_iad_desc.iFunction = us[STRING_IAD_IDX].id;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ncm->ctrl_id = status;
+	ncm_iad_desc.bFirstInterface = status;
+
+	ncm_control_intf.bInterfaceNumber = status;
+	ncm_union_desc.bMasterInterface0 = status;
+
+	if (cdev->use_os_string)
+		f->os_desc_table[0].if_id =
+			ncm_iad_desc.bFirstInterface;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ncm->data_id = status;
+
+	ncm_data_nop_intf.bInterfaceNumber = status;
+	ncm_data_intf.bInterfaceNumber = status;
+	ncm_union_desc.bSlaveInterface0 = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc);
+	if (!ep)
+		goto fail;
+	ncm->port.in_ep = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc);
+	if (!ep)
+		goto fail;
+	ncm->port.out_ep = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc);
+	if (!ep)
+		goto fail;
+	ncm->notify = ep;
+
+	status = -ENOMEM;
+
+	/* allocate notification request and buffer */
+	ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!ncm->notify_req)
+		goto fail;
+	ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL);
+	if (!ncm->notify_req->buf)
+		goto fail;
+	ncm->notify_req->context = ncm;
+	ncm->notify_req->complete = ncm_notify_complete;
+
+	/*
+	 * support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	hs_ncm_in_desc.bEndpointAddress = fs_ncm_in_desc.bEndpointAddress;
+	hs_ncm_out_desc.bEndpointAddress = fs_ncm_out_desc.bEndpointAddress;
+	hs_ncm_notify_desc.bEndpointAddress =
+		fs_ncm_notify_desc.bEndpointAddress;
+
+	ss_ncm_in_desc.bEndpointAddress = fs_ncm_in_desc.bEndpointAddress;
+	ss_ncm_out_desc.bEndpointAddress = fs_ncm_out_desc.bEndpointAddress;
+	ss_ncm_notify_desc.bEndpointAddress =
+		fs_ncm_notify_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function,
+			ncm_ss_function, ncm_ss_function);
+	if (status)
+		goto fail;
+
+	/*
+	 * NOTE:  all that is done without knowing or caring about
+	 * the network link ... which is unavailable to this code
+	 * until we're activated via set_alt().
+	 */
+
+	ncm->port.open = ncm_open;
+	ncm->port.close = ncm_close;
+
+	hrtimer_init(&ncm->task_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT);
+	ncm->task_timer.function = ncm_tx_timeout;
+
+	DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			ncm->port.in_ep->name, ncm->port.out_ep->name,
+			ncm->notify->name);
+	return 0;
+
+fail:
+	kfree(f->os_desc_table);
+	f->os_desc_n = 0;
+
+	if (ncm->notify_req) {
+		kfree(ncm->notify_req->buf);
+		usb_ep_free_request(ncm->notify, ncm->notify_req);
+	}
+
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_ncm_opts,
+			    func_inst.group);
+}
+
+/* f_ncm_item_ops */
+USB_ETHERNET_CONFIGFS_ITEM(ncm);
+
+/* f_ncm_opts_dev_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm);
+
+/* f_ncm_opts_host_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm);
+
+/* f_ncm_opts_qmult */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm);
+
+/* f_ncm_opts_ifname */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm);
+
+static struct configfs_attribute *ncm_attrs[] = {
+	&ncm_opts_attr_dev_addr,
+	&ncm_opts_attr_host_addr,
+	&ncm_opts_attr_qmult,
+	&ncm_opts_attr_ifname,
+	NULL,
+};
+
+static const struct config_item_type ncm_func_type = {
+	.ct_item_ops	= &ncm_item_ops,
+	.ct_attrs	= ncm_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void ncm_free_inst(struct usb_function_instance *f)
+{
+	struct f_ncm_opts *opts;
+
+	opts = container_of(f, struct f_ncm_opts, func_inst);
+	if (opts->bound)
+		gether_cleanup(netdev_priv(opts->net));
+	else
+		free_netdev(opts->net);
+	kfree(opts->ncm_interf_group);
+	kfree(opts);
+}
+
+static struct usb_function_instance *ncm_alloc_inst(void)
+{
+	struct f_ncm_opts *opts;
+	struct usb_os_desc *descs[1];
+	char *names[1];
+	struct config_group *ncm_interf_group;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	opts->ncm_os_desc.ext_compat_id = opts->ncm_ext_compat_id;
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = ncm_free_inst;
+	opts->net = gether_setup_default();
+	if (IS_ERR(opts->net)) {
+		struct net_device *net = opts->net;
+		kfree(opts);
+		return ERR_CAST(net);
+	}
+	INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop);
+
+	descs[0] = &opts->ncm_os_desc;
+	names[0] = "ncm";
+
+	config_group_init_type_name(&opts->func_inst.group, "", &ncm_func_type);
+	ncm_interf_group =
+		usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs,
+					       names, THIS_MODULE);
+	if (IS_ERR(ncm_interf_group)) {
+		ncm_free_inst(&opts->func_inst);
+		return ERR_CAST(ncm_interf_group);
+	}
+	opts->ncm_interf_group = ncm_interf_group;
+
+	return &opts->func_inst;
+}
+
+static void ncm_free(struct usb_function *f)
+{
+	struct f_ncm *ncm;
+	struct f_ncm_opts *opts;
+
+	ncm = func_to_ncm(f);
+	opts = container_of(f->fi, struct f_ncm_opts, func_inst);
+	kfree(ncm);
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+}
+
+static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_ncm *ncm = func_to_ncm(f);
+
+	DBG(c->cdev, "ncm unbind\n");
+
+	hrtimer_cancel(&ncm->task_timer);
+
+	kfree(f->os_desc_table);
+	f->os_desc_n = 0;
+
+	ncm_string_defs[0].id = 0;
+	usb_free_all_descriptors(f);
+
+	if (atomic_read(&ncm->notify_count)) {
+		usb_ep_dequeue(ncm->notify, ncm->notify_req);
+		atomic_set(&ncm->notify_count, 0);
+	}
+
+	kfree(ncm->notify_req->buf);
+	usb_ep_free_request(ncm->notify, ncm->notify_req);
+}
+
+static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
+{
+	struct f_ncm		*ncm;
+	struct f_ncm_opts	*opts;
+	int status;
+
+	/* allocate and initialize one new instance */
+	ncm = kzalloc(sizeof(*ncm), GFP_KERNEL);
+	if (!ncm)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_ncm_opts, func_inst);
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+
+	/* export host's Ethernet address in CDC format */
+	status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr,
+				      sizeof(ncm->ethaddr));
+	if (status < 12) { /* strlen("01234567890a") */
+		kfree(ncm);
+		mutex_unlock(&opts->lock);
+		return ERR_PTR(-EINVAL);
+	}
+	ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr;
+
+	spin_lock_init(&ncm->lock);
+	ncm_reset_values(ncm);
+	ncm->port.ioport = netdev_priv(opts->net);
+	mutex_unlock(&opts->lock);
+	ncm->port.is_fixed = true;
+	ncm->port.supports_multi_frame = true;
+
+	ncm->port.func.name = "cdc_network";
+	/* descriptors are per-instance copies */
+	ncm->port.func.bind = ncm_bind;
+	ncm->port.func.unbind = ncm_unbind;
+	ncm->port.func.set_alt = ncm_set_alt;
+	ncm->port.func.get_alt = ncm_get_alt;
+	ncm->port.func.setup = ncm_setup;
+	ncm->port.func.disable = ncm_disable;
+	ncm->port.func.free_func = ncm_free;
+
+	ncm->port.wrap = ncm_wrap_ntb;
+	ncm->port.unwrap = ncm_unwrap_ntb;
+
+	return &ncm->port.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(ncm, ncm_alloc_inst, ncm_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yauheni Kaliuta");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_obex.c b/marvell/linux/drivers/usb/gadget/function/f_obex.c
new file mode 100644
index 0000000..55b7f57
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_obex.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_obex.c -- USB CDC OBEX function driver
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Felipe Balbi <felipe.balbi@nokia.com>
+ *
+ * Based on f_acm.c by Al Borchers and David Brownell.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include "u_serial.h"
+
+
+/*
+ * This CDC OBEX function support just packages a TTY-ish byte stream.
+ * A user mode server will put it into "raw" mode and handle all the
+ * relevant protocol details ... this is just a kernel passthrough.
+ * When possible, we prevent gadget enumeration until that server is
+ * ready to handle the commands.
+ */
+
+struct f_obex {
+	struct gserial			port;
+	u8				ctrl_id;
+	u8				data_id;
+	u8				cur_alt;
+	u8				port_num;
+};
+
+static inline struct f_obex *func_to_obex(struct usb_function *f)
+{
+	return container_of(f, struct f_obex, port.func);
+}
+
+static inline struct f_obex *port_to_obex(struct gserial *p)
+{
+	return container_of(p, struct f_obex, port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+#define OBEX_CTRL_IDX	0
+#define OBEX_DATA_IDX	1
+
+static struct usb_string obex_string_defs[] = {
+	[OBEX_CTRL_IDX].s	= "CDC Object Exchange (OBEX)",
+	[OBEX_DATA_IDX].s	= "CDC OBEX Data",
+	{  },	/* end of list */
+};
+
+static struct usb_gadget_strings obex_string_table = {
+	.language		= 0x0409,	/* en-US */
+	.strings		= obex_string_defs,
+};
+
+static struct usb_gadget_strings *obex_strings[] = {
+	&obex_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_interface_descriptor obex_control_intf = {
+	.bLength		= sizeof(obex_control_intf),
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= 0,
+
+	.bAlternateSetting	= 0,
+	.bNumEndpoints		= 0,
+	.bInterfaceClass	= USB_CLASS_COMM,
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_OBEX,
+};
+
+static struct usb_interface_descriptor obex_data_nop_intf = {
+	.bLength		= sizeof(obex_data_nop_intf),
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= 1,
+
+	.bAlternateSetting	= 0,
+	.bNumEndpoints		= 0,
+	.bInterfaceClass	= USB_CLASS_CDC_DATA,
+};
+
+static struct usb_interface_descriptor obex_data_intf = {
+	.bLength		= sizeof(obex_data_intf),
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= 2,
+
+	.bAlternateSetting	= 1,
+	.bNumEndpoints		= 2,
+	.bInterfaceClass	= USB_CLASS_CDC_DATA,
+};
+
+static struct usb_cdc_header_desc obex_cdc_header_desc = {
+	.bLength		= sizeof(obex_cdc_header_desc),
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubType	= USB_CDC_HEADER_TYPE,
+	.bcdCDC			= cpu_to_le16(0x0120),
+};
+
+static struct usb_cdc_union_desc obex_cdc_union_desc = {
+	.bLength		= sizeof(obex_cdc_union_desc),
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubType	= USB_CDC_UNION_TYPE,
+	.bMasterInterface0	= 1,
+	.bSlaveInterface0	= 2,
+};
+
+static struct usb_cdc_obex_desc obex_desc = {
+	.bLength		= sizeof(obex_desc),
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubType	= USB_CDC_OBEX_TYPE,
+	.bcdVersion		= cpu_to_le16(0x0100),
+};
+
+/* High-Speed Support */
+
+static struct usb_endpoint_descriptor obex_hs_ep_out_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize		= cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor obex_hs_ep_in_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize		= cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *hs_function[] = {
+	(struct usb_descriptor_header *) &obex_control_intf,
+	(struct usb_descriptor_header *) &obex_cdc_header_desc,
+	(struct usb_descriptor_header *) &obex_desc,
+	(struct usb_descriptor_header *) &obex_cdc_union_desc,
+
+	(struct usb_descriptor_header *) &obex_data_nop_intf,
+	(struct usb_descriptor_header *) &obex_data_intf,
+	(struct usb_descriptor_header *) &obex_hs_ep_in_desc,
+	(struct usb_descriptor_header *) &obex_hs_ep_out_desc,
+	NULL,
+};
+
+/* Full-Speed Support */
+
+static struct usb_endpoint_descriptor obex_fs_ep_in_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor obex_fs_ep_out_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_function[] = {
+	(struct usb_descriptor_header *) &obex_control_intf,
+	(struct usb_descriptor_header *) &obex_cdc_header_desc,
+	(struct usb_descriptor_header *) &obex_desc,
+	(struct usb_descriptor_header *) &obex_cdc_union_desc,
+
+	(struct usb_descriptor_header *) &obex_data_nop_intf,
+	(struct usb_descriptor_header *) &obex_data_intf,
+	(struct usb_descriptor_header *) &obex_fs_ep_in_desc,
+	(struct usb_descriptor_header *) &obex_fs_ep_out_desc,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_obex		*obex = func_to_obex(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	if (intf == obex->ctrl_id) {
+		if (alt != 0)
+			goto fail;
+		/* NOP */
+		dev_dbg(&cdev->gadget->dev,
+			"reset obex ttyGS%d control\n", obex->port_num);
+
+	} else if (intf == obex->data_id) {
+		if (alt > 1)
+			goto fail;
+
+		if (obex->port.in->enabled) {
+			dev_dbg(&cdev->gadget->dev,
+				"reset obex ttyGS%d\n", obex->port_num);
+			gserial_disconnect(&obex->port);
+		}
+
+		if (!obex->port.in->desc || !obex->port.out->desc) {
+			dev_dbg(&cdev->gadget->dev,
+				"init obex ttyGS%d\n", obex->port_num);
+			if (config_ep_by_speed(cdev->gadget, f,
+					       obex->port.in) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       obex->port.out)) {
+				obex->port.out->desc = NULL;
+				obex->port.in->desc = NULL;
+				goto fail;
+			}
+		}
+
+		if (alt == 1) {
+			dev_dbg(&cdev->gadget->dev,
+				"activate obex ttyGS%d\n", obex->port_num);
+			gserial_connect(&obex->port, obex->port_num);
+		}
+
+	} else
+		goto fail;
+
+	obex->cur_alt = alt;
+
+	return 0;
+
+fail:
+	return -EINVAL;
+}
+
+static int obex_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_obex		*obex = func_to_obex(f);
+
+	return obex->cur_alt;
+}
+
+static void obex_disable(struct usb_function *f)
+{
+	struct f_obex	*obex = func_to_obex(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	dev_dbg(&cdev->gadget->dev, "obex ttyGS%d disable\n", obex->port_num);
+	gserial_disconnect(&obex->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void obex_connect(struct gserial *g)
+{
+	struct f_obex		*obex = port_to_obex(g);
+	struct usb_composite_dev *cdev = g->func.config->cdev;
+	int			status;
+
+	status = usb_function_activate(&g->func);
+	if (status)
+		dev_dbg(&cdev->gadget->dev,
+			"obex ttyGS%d function activate --> %d\n",
+			obex->port_num, status);
+}
+
+static void obex_disconnect(struct gserial *g)
+{
+	struct f_obex		*obex = port_to_obex(g);
+	struct usb_composite_dev *cdev = g->func.config->cdev;
+	int			status;
+
+	status = usb_function_deactivate(&g->func);
+	if (status)
+		dev_dbg(&cdev->gadget->dev,
+			"obex ttyGS%d function deactivate --> %d\n",
+			obex->port_num, status);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Some controllers can't support CDC OBEX ... */
+static inline bool can_support_obex(struct usb_configuration *c)
+{
+	/* Since the first interface is a NOP, we can ignore the
+	 * issue of multi-interface support on most controllers.
+	 *
+	 * Altsettings are mandatory, however...
+	 */
+	if (!gadget_is_altset_supported(c->cdev->gadget))
+		return false;
+
+	/* everything else is *probably* fine ... */
+	return true;
+}
+
+static int obex_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_obex		*obex = func_to_obex(f);
+	struct usb_string	*us;
+	int			status;
+	struct usb_ep		*ep;
+
+	if (!can_support_obex(c))
+		return -EINVAL;
+
+	us = usb_gstrings_attach(cdev, obex_strings,
+				 ARRAY_SIZE(obex_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	obex_control_intf.iInterface = us[OBEX_CTRL_IDX].id;
+	obex_data_nop_intf.iInterface = us[OBEX_DATA_IDX].id;
+	obex_data_intf.iInterface = us[OBEX_DATA_IDX].id;
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	obex->ctrl_id = status;
+
+	obex_control_intf.bInterfaceNumber = status;
+	obex_cdc_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	obex->data_id = status;
+
+	obex_data_nop_intf.bInterfaceNumber = status;
+	obex_data_intf.bInterfaceNumber = status;
+	obex_cdc_union_desc.bSlaveInterface0 = status;
+
+	/* allocate instance-specific endpoints */
+
+	status = -ENODEV;
+	ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_in_desc);
+	if (!ep)
+		goto fail;
+	obex->port.in = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc);
+	if (!ep)
+		goto fail;
+	obex->port.out = ep;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+
+	obex_hs_ep_in_desc.bEndpointAddress =
+		obex_fs_ep_in_desc.bEndpointAddress;
+	obex_hs_ep_out_desc.bEndpointAddress =
+		obex_fs_ep_out_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, fs_function, hs_function, NULL,
+					NULL);
+	if (status)
+		goto fail;
+
+	dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
+		obex->port_num,
+		gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+		obex->port.in->name, obex->port.out->name);
+
+	return 0;
+
+fail:
+	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+	return status;
+}
+
+static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_serial_opts,
+			    func_inst.group);
+}
+
+static void obex_attr_release(struct config_item *item)
+{
+	struct f_serial_opts *opts = to_f_serial_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations obex_item_ops = {
+	.release	= obex_attr_release,
+};
+
+static ssize_t f_obex_port_num_show(struct config_item *item, char *page)
+{
+	return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
+}
+
+CONFIGFS_ATTR_RO(f_obex_, port_num);
+
+static struct configfs_attribute *acm_attrs[] = {
+	&f_obex_attr_port_num,
+	NULL,
+};
+
+static const struct config_item_type obex_func_type = {
+	.ct_item_ops	= &obex_item_ops,
+	.ct_attrs	= acm_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void obex_free_inst(struct usb_function_instance *f)
+{
+	struct f_serial_opts *opts;
+
+	opts = container_of(f, struct f_serial_opts, func_inst);
+	gserial_free_line(opts->port_num);
+	kfree(opts);
+}
+
+static struct usb_function_instance *obex_alloc_inst(void)
+{
+	struct f_serial_opts *opts;
+	int ret;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	opts->func_inst.free_func_inst = obex_free_inst;
+	ret = gserial_alloc_line(&opts->port_num);
+	if (ret) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &obex_func_type);
+
+	return &opts->func_inst;
+}
+
+static void obex_free(struct usb_function *f)
+{
+	struct f_obex *obex;
+
+	obex = func_to_obex(f);
+	kfree(obex);
+}
+
+static void obex_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *obex_alloc(struct usb_function_instance *fi)
+{
+	struct f_obex	*obex;
+	struct f_serial_opts *opts;
+
+	/* allocate and initialize one new instance */
+	obex = kzalloc(sizeof(*obex), GFP_KERNEL);
+	if (!obex)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_serial_opts, func_inst);
+
+	obex->port_num = opts->port_num;
+
+	obex->port.connect = obex_connect;
+	obex->port.disconnect = obex_disconnect;
+
+	obex->port.func.name = "obex";
+	/* descriptors are per-instance copies */
+	obex->port.func.bind = obex_bind;
+	obex->port.func.unbind = obex_unbind;
+	obex->port.func.set_alt = obex_set_alt;
+	obex->port.func.get_alt = obex_get_alt;
+	obex->port.func.disable = obex_disable;
+	obex->port.func.free_func = obex_free;
+	obex->port.func.bind_deactivated = true;
+
+	return &obex->port.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc);
+MODULE_AUTHOR("Felipe Balbi");
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_phonet.c b/marvell/linux/drivers/usb/gadget/function/f_phonet.c
new file mode 100644
index 0000000..8b72b19
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_phonet.c
@@ -0,0 +1,731 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * f_phonet.c -- USB CDC Phonet function
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ *
+ * Author: Rémi Denis-Courmont
+ */
+
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_ether.h>
+#include <linux/if_phonet.h>
+#include <linux/if_arp.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/composite.h>
+
+#include "u_phonet.h"
+#include "u_ether.h"
+
+#define PN_MEDIA_USB	0x1B
+#define MAXPACKET	512
+#if (PAGE_SIZE % MAXPACKET)
+#error MAXPACKET must divide PAGE_SIZE!
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+struct phonet_port {
+	struct f_phonet			*usb;
+	spinlock_t			lock;
+};
+
+struct f_phonet {
+	struct usb_function		function;
+	struct {
+		struct sk_buff		*skb;
+		spinlock_t		lock;
+	} rx;
+	struct net_device		*dev;
+	struct usb_ep			*in_ep, *out_ep;
+
+	struct usb_request		*in_req;
+	struct usb_request		*out_reqv[0];
+};
+
+static int phonet_rxq_size = 17;
+
+static inline struct f_phonet *func_to_pn(struct usb_function *f)
+{
+	return container_of(f, struct f_phonet, function);
+}
+
+/*-------------------------------------------------------------------------*/
+
+#define USB_CDC_SUBCLASS_PHONET	0xfe
+#define USB_CDC_PHONET_TYPE	0xab
+
+static struct usb_interface_descriptor
+pn_control_intf_desc = {
+	.bLength =		sizeof pn_control_intf_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber =	DYNAMIC, */
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_PHONET,
+};
+
+static const struct usb_cdc_header_desc
+pn_header_desc = {
+	.bLength =		sizeof pn_header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static const struct usb_cdc_header_desc
+pn_phonet_desc = {
+	.bLength =		sizeof pn_phonet_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_PHONET_TYPE,
+	.bcdCDC =		cpu_to_le16(0x1505), /* ??? */
+};
+
+static struct usb_cdc_union_desc
+pn_union_desc = {
+	.bLength =		sizeof pn_union_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+
+	/* .bMasterInterface0 =	DYNAMIC, */
+	/* .bSlaveInterface0 =	DYNAMIC, */
+};
+
+static struct usb_interface_descriptor
+pn_data_nop_intf_desc = {
+	.bLength =		sizeof pn_data_nop_intf_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber =	DYNAMIC, */
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+};
+
+static struct usb_interface_descriptor
+pn_data_intf_desc = {
+	.bLength =		sizeof pn_data_intf_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber =	DYNAMIC, */
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+};
+
+static struct usb_endpoint_descriptor
+pn_fs_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor
+pn_hs_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(MAXPACKET),
+};
+
+static struct usb_endpoint_descriptor
+pn_fs_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor
+pn_hs_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *fs_pn_function[] = {
+	(struct usb_descriptor_header *) &pn_control_intf_desc,
+	(struct usb_descriptor_header *) &pn_header_desc,
+	(struct usb_descriptor_header *) &pn_phonet_desc,
+	(struct usb_descriptor_header *) &pn_union_desc,
+	(struct usb_descriptor_header *) &pn_data_nop_intf_desc,
+	(struct usb_descriptor_header *) &pn_data_intf_desc,
+	(struct usb_descriptor_header *) &pn_fs_sink_desc,
+	(struct usb_descriptor_header *) &pn_fs_source_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_pn_function[] = {
+	(struct usb_descriptor_header *) &pn_control_intf_desc,
+	(struct usb_descriptor_header *) &pn_header_desc,
+	(struct usb_descriptor_header *) &pn_phonet_desc,
+	(struct usb_descriptor_header *) &pn_union_desc,
+	(struct usb_descriptor_header *) &pn_data_nop_intf_desc,
+	(struct usb_descriptor_header *) &pn_data_intf_desc,
+	(struct usb_descriptor_header *) &pn_hs_sink_desc,
+	(struct usb_descriptor_header *) &pn_hs_source_desc,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int pn_net_open(struct net_device *dev)
+{
+	netif_wake_queue(dev);
+	return 0;
+}
+
+static int pn_net_close(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+	return 0;
+}
+
+static void pn_tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_phonet *fp = ep->driver_data;
+	struct net_device *dev = fp->dev;
+	struct sk_buff *skb = req->context;
+
+	switch (req->status) {
+	case 0:
+		dev->stats.tx_packets++;
+		dev->stats.tx_bytes += skb->len;
+		break;
+
+	case -ESHUTDOWN: /* disconnected */
+	case -ECONNRESET: /* disabled */
+		dev->stats.tx_aborted_errors++;
+		/* fall through */
+	default:
+		dev->stats.tx_errors++;
+	}
+
+	dev_kfree_skb_any(skb);
+	netif_wake_queue(dev);
+}
+
+static netdev_tx_t pn_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct phonet_port *port = netdev_priv(dev);
+	struct f_phonet *fp;
+	struct usb_request *req;
+	unsigned long flags;
+
+	if (skb->protocol != htons(ETH_P_PHONET))
+		goto out;
+
+	spin_lock_irqsave(&port->lock, flags);
+	fp = port->usb;
+	if (unlikely(!fp)) /* race with carrier loss */
+		goto out_unlock;
+
+	req = fp->in_req;
+	req->buf = skb->data;
+	req->length = skb->len;
+	req->complete = pn_tx_complete;
+	req->zero = 1;
+	req->context = skb;
+
+	if (unlikely(usb_ep_queue(fp->in_ep, req, GFP_ATOMIC)))
+		goto out_unlock;
+
+	netif_stop_queue(dev);
+	skb = NULL;
+
+out_unlock:
+	spin_unlock_irqrestore(&port->lock, flags);
+out:
+	if (unlikely(skb)) {
+		dev_kfree_skb(skb);
+		dev->stats.tx_dropped++;
+	}
+	return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops pn_netdev_ops = {
+	.ndo_open	= pn_net_open,
+	.ndo_stop	= pn_net_close,
+	.ndo_start_xmit	= pn_net_xmit,
+};
+
+static void pn_net_setup(struct net_device *dev)
+{
+	dev->features		= 0;
+	dev->type		= ARPHRD_PHONET;
+	dev->flags		= IFF_POINTOPOINT | IFF_NOARP;
+	dev->mtu		= PHONET_DEV_MTU;
+	dev->min_mtu		= PHONET_MIN_MTU;
+	dev->max_mtu		= PHONET_MAX_MTU;
+	dev->hard_header_len	= 1;
+	dev->dev_addr[0]	= PN_MEDIA_USB;
+	dev->addr_len		= 1;
+	dev->tx_queue_len	= 1;
+
+	dev->netdev_ops		= &pn_netdev_ops;
+	dev->needs_free_netdev	= true;
+	dev->header_ops		= &phonet_header_ops;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Queue buffer for data from the host
+ */
+static int
+pn_rx_submit(struct f_phonet *fp, struct usb_request *req, gfp_t gfp_flags)
+{
+	struct page *page;
+	int err;
+
+	page = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC);
+	if (!page)
+		return -ENOMEM;
+
+	req->buf = page_address(page);
+	req->length = PAGE_SIZE;
+	req->context = page;
+
+	err = usb_ep_queue(fp->out_ep, req, gfp_flags);
+	if (unlikely(err))
+		put_page(page);
+	return err;
+}
+
+static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_phonet *fp = ep->driver_data;
+	struct net_device *dev = fp->dev;
+	struct page *page = req->context;
+	struct sk_buff *skb;
+	unsigned long flags;
+	int status = req->status;
+
+	switch (status) {
+	case 0:
+		spin_lock_irqsave(&fp->rx.lock, flags);
+		skb = fp->rx.skb;
+		if (!skb)
+			skb = fp->rx.skb = netdev_alloc_skb(dev, 12);
+		if (req->actual < req->length) /* Last fragment */
+			fp->rx.skb = NULL;
+		spin_unlock_irqrestore(&fp->rx.lock, flags);
+
+		if (unlikely(!skb))
+			break;
+
+		if (skb->len == 0) { /* First fragment */
+			skb->protocol = htons(ETH_P_PHONET);
+			skb_reset_mac_header(skb);
+			/* Can't use pskb_pull() on page in IRQ */
+			skb_put_data(skb, page_address(page), 1);
+		}
+
+		skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
+				skb->len <= 1, req->actual, PAGE_SIZE);
+		page = NULL;
+
+		if (req->actual < req->length) { /* Last fragment */
+			skb->dev = dev;
+			dev->stats.rx_packets++;
+			dev->stats.rx_bytes += skb->len;
+
+			netif_rx(skb);
+		}
+		break;
+
+	/* Do not resubmit in these cases: */
+	case -ESHUTDOWN: /* disconnect */
+	case -ECONNABORTED: /* hw reset */
+	case -ECONNRESET: /* dequeued (unlink or netif down) */
+		req = NULL;
+		break;
+
+	/* Do resubmit in these cases: */
+	case -EOVERFLOW: /* request buffer overflow */
+		dev->stats.rx_over_errors++;
+		/* fall through */
+	default:
+		dev->stats.rx_errors++;
+		break;
+	}
+
+	if (page)
+		put_page(page);
+	if (req)
+		pn_rx_submit(fp, req, GFP_ATOMIC);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void __pn_reset(struct usb_function *f)
+{
+	struct f_phonet *fp = func_to_pn(f);
+	struct net_device *dev = fp->dev;
+	struct phonet_port *port = netdev_priv(dev);
+
+	netif_carrier_off(dev);
+	port->usb = NULL;
+
+	usb_ep_disable(fp->out_ep);
+	usb_ep_disable(fp->in_ep);
+	if (fp->rx.skb) {
+		dev_kfree_skb_irq(fp->rx.skb);
+		fp->rx.skb = NULL;
+	}
+}
+
+static int pn_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_phonet *fp = func_to_pn(f);
+	struct usb_gadget *gadget = fp->function.config->cdev->gadget;
+
+	if (intf == pn_control_intf_desc.bInterfaceNumber)
+		/* control interface, no altsetting */
+		return (alt > 0) ? -EINVAL : 0;
+
+	if (intf == pn_data_intf_desc.bInterfaceNumber) {
+		struct net_device *dev = fp->dev;
+		struct phonet_port *port = netdev_priv(dev);
+
+		/* data intf (0: inactive, 1: active) */
+		if (alt > 1)
+			return -EINVAL;
+
+		spin_lock(&port->lock);
+
+		if (fp->in_ep->enabled)
+			__pn_reset(f);
+
+		if (alt == 1) {
+			int i;
+
+			if (config_ep_by_speed(gadget, f, fp->in_ep) ||
+			    config_ep_by_speed(gadget, f, fp->out_ep)) {
+				fp->in_ep->desc = NULL;
+				fp->out_ep->desc = NULL;
+				spin_unlock(&port->lock);
+				return -EINVAL;
+			}
+			usb_ep_enable(fp->out_ep);
+			usb_ep_enable(fp->in_ep);
+
+			port->usb = fp;
+			fp->out_ep->driver_data = fp;
+			fp->in_ep->driver_data = fp;
+
+			netif_carrier_on(dev);
+			for (i = 0; i < phonet_rxq_size; i++)
+				pn_rx_submit(fp, fp->out_reqv[i], GFP_ATOMIC);
+		}
+		spin_unlock(&port->lock);
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int pn_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_phonet *fp = func_to_pn(f);
+
+	if (intf == pn_control_intf_desc.bInterfaceNumber)
+		return 0;
+
+	if (intf == pn_data_intf_desc.bInterfaceNumber) {
+		struct phonet_port *port = netdev_priv(fp->dev);
+		u8 alt;
+
+		spin_lock(&port->lock);
+		alt = port->usb != NULL;
+		spin_unlock(&port->lock);
+		return alt;
+	}
+
+	return -EINVAL;
+}
+
+static void pn_disconnect(struct usb_function *f)
+{
+	struct f_phonet *fp = func_to_pn(f);
+	struct phonet_port *port = netdev_priv(fp->dev);
+	unsigned long flags;
+
+	/* remain disabled until set_alt */
+	spin_lock_irqsave(&port->lock, flags);
+	__pn_reset(f);
+	spin_unlock_irqrestore(&port->lock, flags);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int pn_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct f_phonet *fp = func_to_pn(f);
+	struct usb_ep *ep;
+	int status, i;
+
+	struct f_phonet_opts *phonet_opts;
+
+	phonet_opts = container_of(f->fi, struct f_phonet_opts, func_inst);
+
+	/*
+	 * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+	 * configurations are bound in sequence with list_for_each_entry,
+	 * in each configuration its functions are bound in sequence
+	 * with list_for_each_entry, so we assume no race condition
+	 * with regard to phonet_opts->bound access
+	 */
+	if (!phonet_opts->bound) {
+		gphonet_set_gadget(phonet_opts->net, gadget);
+		status = gphonet_register_netdev(phonet_opts->net);
+		if (status)
+			return status;
+		phonet_opts->bound = true;
+	}
+
+	/* Reserve interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto err;
+	pn_control_intf_desc.bInterfaceNumber = status;
+	pn_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto err;
+	pn_data_nop_intf_desc.bInterfaceNumber = status;
+	pn_data_intf_desc.bInterfaceNumber = status;
+	pn_union_desc.bSlaveInterface0 = status;
+
+	/* Reserve endpoints */
+	status = -ENODEV;
+	ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
+	if (!ep)
+		goto err;
+	fp->out_ep = ep;
+
+	ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
+	if (!ep)
+		goto err;
+	fp->in_ep = ep;
+
+	pn_hs_sink_desc.bEndpointAddress = pn_fs_sink_desc.bEndpointAddress;
+	pn_hs_source_desc.bEndpointAddress = pn_fs_source_desc.bEndpointAddress;
+
+	/* Do not try to bind Phonet twice... */
+	status = usb_assign_descriptors(f, fs_pn_function, hs_pn_function,
+			NULL, NULL);
+	if (status)
+		goto err;
+
+	/* Incoming USB requests */
+	status = -ENOMEM;
+	for (i = 0; i < phonet_rxq_size; i++) {
+		struct usb_request *req;
+
+		req = usb_ep_alloc_request(fp->out_ep, GFP_KERNEL);
+		if (!req)
+			goto err_req;
+
+		req->complete = pn_rx_complete;
+		fp->out_reqv[i] = req;
+	}
+
+	/* Outgoing USB requests */
+	fp->in_req = usb_ep_alloc_request(fp->in_ep, GFP_KERNEL);
+	if (!fp->in_req)
+		goto err_req;
+
+	INFO(cdev, "USB CDC Phonet function\n");
+	INFO(cdev, "using %s, OUT %s, IN %s\n", cdev->gadget->name,
+		fp->out_ep->name, fp->in_ep->name);
+	return 0;
+
+err_req:
+	for (i = 0; i < phonet_rxq_size && fp->out_reqv[i]; i++)
+		usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
+	usb_free_all_descriptors(f);
+err:
+	ERROR(cdev, "USB CDC Phonet: cannot autoconfigure\n");
+	return status;
+}
+
+static inline struct f_phonet_opts *to_f_phonet_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_phonet_opts,
+			func_inst.group);
+}
+
+static void phonet_attr_release(struct config_item *item)
+{
+	struct f_phonet_opts *opts = to_f_phonet_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations phonet_item_ops = {
+	.release		= phonet_attr_release,
+};
+
+static ssize_t f_phonet_ifname_show(struct config_item *item, char *page)
+{
+	return gether_get_ifname(to_f_phonet_opts(item)->net, page, PAGE_SIZE);
+}
+
+CONFIGFS_ATTR_RO(f_phonet_, ifname);
+
+static struct configfs_attribute *phonet_attrs[] = {
+	&f_phonet_attr_ifname,
+	NULL,
+};
+
+static const struct config_item_type phonet_func_type = {
+	.ct_item_ops	= &phonet_item_ops,
+	.ct_attrs	= phonet_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void phonet_free_inst(struct usb_function_instance *f)
+{
+	struct f_phonet_opts *opts;
+
+	opts = container_of(f, struct f_phonet_opts, func_inst);
+	if (opts->bound)
+		gphonet_cleanup(opts->net);
+	else
+		free_netdev(opts->net);
+	kfree(opts);
+}
+
+static struct usb_function_instance *phonet_alloc_inst(void)
+{
+	struct f_phonet_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	opts->func_inst.free_func_inst = phonet_free_inst;
+	opts->net = gphonet_setup_default();
+	if (IS_ERR(opts->net)) {
+		struct net_device *net = opts->net;
+		kfree(opts);
+		return ERR_CAST(net);
+	}
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+			&phonet_func_type);
+
+	return &opts->func_inst;
+}
+
+static void phonet_free(struct usb_function *f)
+{
+	struct f_phonet *phonet;
+
+	phonet = func_to_pn(f);
+	kfree(phonet);
+}
+
+static void pn_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_phonet *fp = func_to_pn(f);
+	int i;
+
+	/* We are already disconnected */
+	if (fp->in_req)
+		usb_ep_free_request(fp->in_ep, fp->in_req);
+	for (i = 0; i < phonet_rxq_size; i++)
+		if (fp->out_reqv[i])
+			usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
+
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
+{
+	struct f_phonet *fp;
+	struct f_phonet_opts *opts;
+	int size;
+
+	size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *));
+	fp = kzalloc(size, GFP_KERNEL);
+	if (!fp)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_phonet_opts, func_inst);
+
+	fp->dev = opts->net;
+	fp->function.name = "phonet";
+	fp->function.bind = pn_bind;
+	fp->function.unbind = pn_unbind;
+	fp->function.set_alt = pn_set_alt;
+	fp->function.get_alt = pn_get_alt;
+	fp->function.disable = pn_disconnect;
+	fp->function.free_func = phonet_free;
+	spin_lock_init(&fp->rx.lock);
+
+	return &fp->function;
+}
+
+struct net_device *gphonet_setup_default(void)
+{
+	struct net_device *dev;
+	struct phonet_port *port;
+
+	/* Create net device */
+	dev = alloc_netdev(sizeof(*port), "upnlink%d", NET_NAME_UNKNOWN,
+			   pn_net_setup);
+	if (!dev)
+		return ERR_PTR(-ENOMEM);
+
+	port = netdev_priv(dev);
+	spin_lock_init(&port->lock);
+	netif_carrier_off(dev);
+
+	return dev;
+}
+
+void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g)
+{
+	SET_NETDEV_DEV(net, &g->dev);
+}
+
+int gphonet_register_netdev(struct net_device *net)
+{
+	int status;
+
+	status = register_netdev(net);
+	if (status)
+		free_netdev(net);
+
+	return status;
+}
+
+void gphonet_cleanup(struct net_device *dev)
+{
+	unregister_netdev(dev);
+}
+
+DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc);
+MODULE_AUTHOR("Rémi Denis-Courmont");
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_printer.c b/marvell/linux/drivers/usb/gadget/function/f_printer.c
new file mode 100644
index 0000000..1e9ac31
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_printer.c
@@ -0,0 +1,1513 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_printer.c - USB printer function driver
+ *
+ * Copied from drivers/usb/gadget/legacy/printer.c,
+ * which was:
+ *
+ * printer.c -- Printer gadget driver
+ *
+ * Copyright (C) 2003-2005 David Brownell
+ * Copyright (C) 2006 Craig W. Nadler
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/idr.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/types.h>
+#include <linux/ctype.h>
+#include <linux/cdev.h>
+#include <linux/kref.h>
+
+#include <asm/byteorder.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/g_printer.h>
+
+#include "u_printer.h"
+
+#define PRINTER_MINORS		4
+#define GET_DEVICE_ID		0
+#define GET_PORT_STATUS		1
+#define SOFT_RESET		2
+
+static int major, minors;
+static struct class *usb_gadget_class;
+static DEFINE_IDA(printer_ida);
+static DEFINE_MUTEX(printer_ida_lock); /* protects access do printer_ida */
+
+/*-------------------------------------------------------------------------*/
+
+struct printer_dev {
+	spinlock_t		lock;		/* lock this structure */
+	/* lock buffer lists during read/write calls */
+	struct mutex		lock_printer_io;
+	struct usb_gadget	*gadget;
+	s8			interface;
+	struct usb_ep		*in_ep, *out_ep;
+	struct kref             kref;
+	struct list_head	rx_reqs;	/* List of free RX structs */
+	struct list_head	rx_reqs_active;	/* List of Active RX xfers */
+	struct list_head	rx_buffers;	/* List of completed xfers */
+	/* wait until there is data to be read. */
+	wait_queue_head_t	rx_wait;
+	struct list_head	tx_reqs;	/* List of free TX structs */
+	struct list_head	tx_reqs_active; /* List of Active TX xfers */
+	/* Wait until there are write buffers available to use. */
+	wait_queue_head_t	tx_wait;
+	/* Wait until all write buffers have been sent. */
+	wait_queue_head_t	tx_flush_wait;
+	struct usb_request	*current_rx_req;
+	size_t			current_rx_bytes;
+	u8			*current_rx_buf;
+	u8			printer_status;
+	u8			reset_printer;
+	int			minor;
+	struct cdev		printer_cdev;
+	u8			printer_cdev_open;
+	wait_queue_head_t	wait;
+	unsigned		q_len;
+	char			**pnp_string;	/* We don't own memory! */
+	struct usb_function	function;
+};
+
+static inline struct printer_dev *func_to_printer(struct usb_function *f)
+{
+	return container_of(f, struct printer_dev, function);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * DESCRIPTORS ... most are static, but strings and (full) configuration
+ * descriptors are built on demand.
+ */
+
+/* holds our biggest descriptor */
+#define USB_DESC_BUFSIZE		256
+#define USB_BUFSIZE			8192
+
+static struct usb_interface_descriptor intf_desc = {
+	.bLength =		sizeof(intf_desc),
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_PRINTER,
+	.bInterfaceSubClass =	1,	/* Printer Sub-Class */
+	.bInterfaceProtocol =	2,	/* Bi-Directional */
+	.iInterface =		0
+};
+
+static struct usb_endpoint_descriptor fs_ep_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK
+};
+
+static struct usb_endpoint_descriptor fs_ep_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK
+};
+
+static struct usb_descriptor_header *fs_printer_function[] = {
+	(struct usb_descriptor_header *) &intf_desc,
+	(struct usb_descriptor_header *) &fs_ep_in_desc,
+	(struct usb_descriptor_header *) &fs_ep_out_desc,
+	NULL
+};
+
+/*
+ * usb 2.0 devices need to expose both high speed and full speed
+ * descriptors, unless they only run at full speed.
+ */
+
+static struct usb_endpoint_descriptor hs_ep_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512)
+};
+
+static struct usb_endpoint_descriptor hs_ep_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512)
+};
+
+static struct usb_descriptor_header *hs_printer_function[] = {
+	(struct usb_descriptor_header *) &intf_desc,
+	(struct usb_descriptor_header *) &hs_ep_in_desc,
+	(struct usb_descriptor_header *) &hs_ep_out_desc,
+	NULL
+};
+
+/*
+ * Added endpoint descriptors for 3.0 devices
+ */
+
+static struct usb_endpoint_descriptor ss_ep_in_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =       cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ep_in_comp_desc = {
+	.bLength =              sizeof(ss_ep_in_comp_desc),
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_endpoint_descriptor ss_ep_out_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =       cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ep_out_comp_desc = {
+	.bLength =              sizeof(ss_ep_out_comp_desc),
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *ss_printer_function[] = {
+	(struct usb_descriptor_header *) &intf_desc,
+	(struct usb_descriptor_header *) &ss_ep_in_desc,
+	(struct usb_descriptor_header *) &ss_ep_in_comp_desc,
+	(struct usb_descriptor_header *) &ss_ep_out_desc,
+	(struct usb_descriptor_header *) &ss_ep_out_comp_desc,
+	NULL
+};
+
+/* maxpacket and other transfer characteristics vary by speed. */
+static inline struct usb_endpoint_descriptor *ep_desc(struct usb_gadget *gadget,
+					struct usb_endpoint_descriptor *fs,
+					struct usb_endpoint_descriptor *hs,
+					struct usb_endpoint_descriptor *ss)
+{
+	switch (gadget->speed) {
+	case USB_SPEED_SUPER_PLUS:
+	case USB_SPEED_SUPER:
+		return ss;
+	case USB_SPEED_HIGH:
+		return hs;
+	default:
+		return fs;
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void printer_dev_free(struct kref *kref)
+{
+	struct printer_dev *dev = container_of(kref, struct printer_dev, kref);
+
+	kfree(dev);
+}
+
+static struct usb_request *
+printer_req_alloc(struct usb_ep *ep, unsigned len, gfp_t gfp_flags)
+{
+	struct usb_request	*req;
+
+	req = usb_ep_alloc_request(ep, gfp_flags);
+
+	if (req != NULL) {
+		req->length = len;
+		req->buf = kmalloc(len, gfp_flags);
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			return NULL;
+		}
+	}
+
+	return req;
+}
+
+static void
+printer_req_free(struct usb_ep *ep, struct usb_request *req)
+{
+	if (ep != NULL && req != NULL) {
+		kfree(req->buf);
+		usb_ep_free_request(ep, req);
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct printer_dev	*dev = ep->driver_data;
+	int			status = req->status;
+	unsigned long		flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	list_del_init(&req->list);	/* Remode from Active List */
+
+	switch (status) {
+
+	/* normal completion */
+	case 0:
+		if (req->actual > 0) {
+			list_add_tail(&req->list, &dev->rx_buffers);
+			DBG(dev, "G_Printer : rx length %d\n", req->actual);
+		} else {
+			list_add(&req->list, &dev->rx_reqs);
+		}
+		break;
+
+	/* software-driven interface shutdown */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		VDBG(dev, "rx shutdown, code %d\n", status);
+		list_add(&req->list, &dev->rx_reqs);
+		break;
+
+	/* for hardware automagic (such as pxa) */
+	case -ECONNABORTED:		/* endpoint reset */
+		DBG(dev, "rx %s reset\n", ep->name);
+		list_add(&req->list, &dev->rx_reqs);
+		break;
+
+	/* data overrun */
+	case -EOVERFLOW:
+		/* FALLTHROUGH */
+
+	default:
+		DBG(dev, "rx status %d\n", status);
+		list_add(&req->list, &dev->rx_reqs);
+		break;
+	}
+
+	wake_up_interruptible(&dev->rx_wait);
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct printer_dev	*dev = ep->driver_data;
+
+	switch (req->status) {
+	default:
+		VDBG(dev, "tx err %d\n", req->status);
+		/* FALLTHROUGH */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		break;
+	case 0:
+		break;
+	}
+
+	spin_lock(&dev->lock);
+	/* Take the request struct off the active list and put it on the
+	 * free list.
+	 */
+	list_del_init(&req->list);
+	list_add(&req->list, &dev->tx_reqs);
+	wake_up_interruptible(&dev->tx_wait);
+	if (likely(list_empty(&dev->tx_reqs_active)))
+		wake_up_interruptible(&dev->tx_flush_wait);
+
+	spin_unlock(&dev->lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int
+printer_open(struct inode *inode, struct file *fd)
+{
+	struct printer_dev	*dev;
+	unsigned long		flags;
+	int			ret = -EBUSY;
+
+	dev = container_of(inode->i_cdev, struct printer_dev, printer_cdev);
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	if (!dev->printer_cdev_open) {
+		dev->printer_cdev_open = 1;
+		fd->private_data = dev;
+		ret = 0;
+		/* Change the printer status to show that it's on-line. */
+		dev->printer_status |= PRINTER_SELECTED;
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	kref_get(&dev->kref);
+	DBG(dev, "printer_open returned %x\n", ret);
+	return ret;
+}
+
+static int
+printer_close(struct inode *inode, struct file *fd)
+{
+	struct printer_dev	*dev = fd->private_data;
+	unsigned long		flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->printer_cdev_open = 0;
+	fd->private_data = NULL;
+	/* Change printer status to show that the printer is off-line. */
+	dev->printer_status &= ~PRINTER_SELECTED;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	kref_put(&dev->kref, printer_dev_free);
+	DBG(dev, "printer_close\n");
+
+	return 0;
+}
+
+/* This function must be called with interrupts turned off. */
+static void
+setup_rx_reqs(struct printer_dev *dev)
+{
+	struct usb_request              *req;
+
+	while (likely(!list_empty(&dev->rx_reqs))) {
+		int error;
+
+		req = container_of(dev->rx_reqs.next,
+				struct usb_request, list);
+		list_del_init(&req->list);
+
+		/* The USB Host sends us whatever amount of data it wants to
+		 * so we always set the length field to the full USB_BUFSIZE.
+		 * If the amount of data is more than the read() caller asked
+		 * for it will be stored in the request buffer until it is
+		 * asked for by read().
+		 */
+		req->length = USB_BUFSIZE;
+		req->complete = rx_complete;
+
+		/* here, we unlock, and only unlock, to avoid deadlock. */
+		spin_unlock(&dev->lock);
+		error = usb_ep_queue(dev->out_ep, req, GFP_ATOMIC);
+		spin_lock(&dev->lock);
+		if (error) {
+			DBG(dev, "rx submit --> %d\n", error);
+			list_add(&req->list, &dev->rx_reqs);
+			break;
+		}
+		/* if the req is empty, then add it into dev->rx_reqs_active. */
+		else if (list_empty(&req->list))
+			list_add(&req->list, &dev->rx_reqs_active);
+	}
+}
+
+static ssize_t
+printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr)
+{
+	struct printer_dev		*dev = fd->private_data;
+	unsigned long			flags;
+	size_t				size;
+	size_t				bytes_copied;
+	struct usb_request		*req;
+	/* This is a pointer to the current USB rx request. */
+	struct usb_request		*current_rx_req;
+	/* This is the number of bytes in the current rx buffer. */
+	size_t				current_rx_bytes;
+	/* This is a pointer to the current rx buffer. */
+	u8				*current_rx_buf;
+
+	if (len == 0)
+		return -EINVAL;
+
+	DBG(dev, "printer_read trying to read %d bytes\n", (int)len);
+
+	mutex_lock(&dev->lock_printer_io);
+	spin_lock_irqsave(&dev->lock, flags);
+
+	/* We will use this flag later to check if a printer reset happened
+	 * after we turn interrupts back on.
+	 */
+	dev->reset_printer = 0;
+
+	setup_rx_reqs(dev);
+
+	bytes_copied = 0;
+	current_rx_req = dev->current_rx_req;
+	current_rx_bytes = dev->current_rx_bytes;
+	current_rx_buf = dev->current_rx_buf;
+	dev->current_rx_req = NULL;
+	dev->current_rx_bytes = 0;
+	dev->current_rx_buf = NULL;
+
+	/* Check if there is any data in the read buffers. Please note that
+	 * current_rx_bytes is the number of bytes in the current rx buffer.
+	 * If it is zero then check if there are any other rx_buffers that
+	 * are on the completed list. We are only out of data if all rx
+	 * buffers are empty.
+	 */
+	if ((current_rx_bytes == 0) &&
+			(likely(list_empty(&dev->rx_buffers)))) {
+		/* Turn interrupts back on before sleeping. */
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		/*
+		 * If no data is available check if this is a NON-Blocking
+		 * call or not.
+		 */
+		if (fd->f_flags & (O_NONBLOCK|O_NDELAY)) {
+			mutex_unlock(&dev->lock_printer_io);
+			return -EAGAIN;
+		}
+
+		/* Sleep until data is available */
+		wait_event_interruptible(dev->rx_wait,
+				(likely(!list_empty(&dev->rx_buffers))));
+		spin_lock_irqsave(&dev->lock, flags);
+	}
+
+	/* We have data to return then copy it to the caller's buffer.*/
+	while ((current_rx_bytes || likely(!list_empty(&dev->rx_buffers)))
+			&& len) {
+		if (current_rx_bytes == 0) {
+			req = container_of(dev->rx_buffers.next,
+					struct usb_request, list);
+			list_del_init(&req->list);
+
+			if (req->actual && req->buf) {
+				current_rx_req = req;
+				current_rx_bytes = req->actual;
+				current_rx_buf = req->buf;
+			} else {
+				list_add(&req->list, &dev->rx_reqs);
+				continue;
+			}
+		}
+
+		/* Don't leave irqs off while doing memory copies */
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		if (len > current_rx_bytes)
+			size = current_rx_bytes;
+		else
+			size = len;
+
+		size -= copy_to_user(buf, current_rx_buf, size);
+		bytes_copied += size;
+		len -= size;
+		buf += size;
+
+		spin_lock_irqsave(&dev->lock, flags);
+
+		/* We've disconnected or reset so return. */
+		if (dev->reset_printer) {
+			list_add(&current_rx_req->list, &dev->rx_reqs);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			mutex_unlock(&dev->lock_printer_io);
+			return -EAGAIN;
+		}
+
+		/* If we not returning all the data left in this RX request
+		 * buffer then adjust the amount of data left in the buffer.
+		 * Othewise if we are done with this RX request buffer then
+		 * requeue it to get any incoming data from the USB host.
+		 */
+		if (size < current_rx_bytes) {
+			current_rx_bytes -= size;
+			current_rx_buf += size;
+		} else {
+			list_add(&current_rx_req->list, &dev->rx_reqs);
+			current_rx_bytes = 0;
+			current_rx_buf = NULL;
+			current_rx_req = NULL;
+		}
+	}
+
+	dev->current_rx_req = current_rx_req;
+	dev->current_rx_bytes = current_rx_bytes;
+	dev->current_rx_buf = current_rx_buf;
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock_printer_io);
+
+	DBG(dev, "printer_read returned %d bytes\n", (int)bytes_copied);
+
+	if (bytes_copied)
+		return bytes_copied;
+	else
+		return -EAGAIN;
+}
+
+static ssize_t
+printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
+{
+	struct printer_dev	*dev = fd->private_data;
+	unsigned long		flags;
+	size_t			size;	/* Amount of data in a TX request. */
+	size_t			bytes_copied = 0;
+	struct usb_request	*req;
+	int			value;
+
+	DBG(dev, "printer_write trying to send %d bytes\n", (int)len);
+
+	if (len == 0)
+		return -EINVAL;
+
+	mutex_lock(&dev->lock_printer_io);
+	spin_lock_irqsave(&dev->lock, flags);
+
+	/* Check if a printer reset happens while we have interrupts on */
+	dev->reset_printer = 0;
+
+	/* Check if there is any available write buffers */
+	if (likely(list_empty(&dev->tx_reqs))) {
+		/* Turn interrupts back on before sleeping. */
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		/*
+		 * If write buffers are available check if this is
+		 * a NON-Blocking call or not.
+		 */
+		if (fd->f_flags & (O_NONBLOCK|O_NDELAY)) {
+			mutex_unlock(&dev->lock_printer_io);
+			return -EAGAIN;
+		}
+
+		/* Sleep until a write buffer is available */
+		wait_event_interruptible(dev->tx_wait,
+				(likely(!list_empty(&dev->tx_reqs))));
+		spin_lock_irqsave(&dev->lock, flags);
+	}
+
+	while (likely(!list_empty(&dev->tx_reqs)) && len) {
+
+		if (len > USB_BUFSIZE)
+			size = USB_BUFSIZE;
+		else
+			size = len;
+
+		req = container_of(dev->tx_reqs.next, struct usb_request,
+				list);
+		list_del_init(&req->list);
+
+		req->complete = tx_complete;
+		req->length = size;
+
+		/* Check if we need to send a zero length packet. */
+		if (len > size)
+			/* They will be more TX requests so no yet. */
+			req->zero = 0;
+		else
+			/* If the data amount is not a multiple of the
+			 * maxpacket size then send a zero length packet.
+			 */
+			req->zero = ((len % dev->in_ep->maxpacket) == 0);
+
+		/* Don't leave irqs off while doing memory copies */
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		if (copy_from_user(req->buf, buf, size)) {
+			list_add(&req->list, &dev->tx_reqs);
+			mutex_unlock(&dev->lock_printer_io);
+			return bytes_copied;
+		}
+
+		bytes_copied += size;
+		len -= size;
+		buf += size;
+
+		spin_lock_irqsave(&dev->lock, flags);
+
+		/* We've disconnected or reset so free the req and buffer */
+		if (dev->reset_printer) {
+			list_add(&req->list, &dev->tx_reqs);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			mutex_unlock(&dev->lock_printer_io);
+			return -EAGAIN;
+		}
+
+		list_add(&req->list, &dev->tx_reqs_active);
+
+		/* here, we unlock, and only unlock, to avoid deadlock. */
+		spin_unlock(&dev->lock);
+		value = usb_ep_queue(dev->in_ep, req, GFP_ATOMIC);
+		spin_lock(&dev->lock);
+		if (value) {
+			list_del(&req->list);
+			list_add(&req->list, &dev->tx_reqs);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			mutex_unlock(&dev->lock_printer_io);
+			return -EAGAIN;
+		}
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock_printer_io);
+
+	DBG(dev, "printer_write sent %d bytes\n", (int)bytes_copied);
+
+	if (bytes_copied)
+		return bytes_copied;
+	else
+		return -EAGAIN;
+}
+
+static int
+printer_fsync(struct file *fd, loff_t start, loff_t end, int datasync)
+{
+	struct printer_dev	*dev = fd->private_data;
+	struct inode *inode = file_inode(fd);
+	unsigned long		flags;
+	int			tx_list_empty;
+
+	inode_lock(inode);
+	spin_lock_irqsave(&dev->lock, flags);
+	tx_list_empty = (likely(list_empty(&dev->tx_reqs)));
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (!tx_list_empty) {
+		/* Sleep until all data has been sent */
+		wait_event_interruptible(dev->tx_flush_wait,
+				(likely(list_empty(&dev->tx_reqs_active))));
+	}
+	inode_unlock(inode);
+
+	return 0;
+}
+
+static __poll_t
+printer_poll(struct file *fd, poll_table *wait)
+{
+	struct printer_dev	*dev = fd->private_data;
+	unsigned long		flags;
+	__poll_t		status = 0;
+
+	mutex_lock(&dev->lock_printer_io);
+	spin_lock_irqsave(&dev->lock, flags);
+	setup_rx_reqs(dev);
+	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock_printer_io);
+
+	poll_wait(fd, &dev->rx_wait, wait);
+	poll_wait(fd, &dev->tx_wait, wait);
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (likely(!list_empty(&dev->tx_reqs)))
+		status |= EPOLLOUT | EPOLLWRNORM;
+
+	if (likely(dev->current_rx_bytes) ||
+			likely(!list_empty(&dev->rx_buffers)))
+		status |= EPOLLIN | EPOLLRDNORM;
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return status;
+}
+
+static long
+printer_ioctl(struct file *fd, unsigned int code, unsigned long arg)
+{
+	struct printer_dev	*dev = fd->private_data;
+	unsigned long		flags;
+	int			status = 0;
+
+	DBG(dev, "printer_ioctl: cmd=0x%4.4x, arg=%lu\n", code, arg);
+
+	/* handle ioctls */
+
+	spin_lock_irqsave(&dev->lock, flags);
+
+	switch (code) {
+	case GADGET_GET_PRINTER_STATUS:
+		status = (int)dev->printer_status;
+		break;
+	case GADGET_SET_PRINTER_STATUS:
+		dev->printer_status = (u8)arg;
+		break;
+	default:
+		/* could not handle ioctl */
+		DBG(dev, "printer_ioctl: ERROR cmd=0x%4.4xis not supported\n",
+				code);
+		status = -ENOTTY;
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return status;
+}
+
+/* used after endpoint configuration */
+static const struct file_operations printer_io_operations = {
+	.owner =	THIS_MODULE,
+	.open =		printer_open,
+	.read =		printer_read,
+	.write =	printer_write,
+	.fsync =	printer_fsync,
+	.poll =		printer_poll,
+	.unlocked_ioctl = printer_ioctl,
+	.release =	printer_close,
+	.llseek =	noop_llseek,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int
+set_printer_interface(struct printer_dev *dev)
+{
+	int			result = 0;
+
+	dev->in_ep->desc = ep_desc(dev->gadget, &fs_ep_in_desc, &hs_ep_in_desc,
+				&ss_ep_in_desc);
+	dev->in_ep->driver_data = dev;
+
+	dev->out_ep->desc = ep_desc(dev->gadget, &fs_ep_out_desc,
+				    &hs_ep_out_desc, &ss_ep_out_desc);
+	dev->out_ep->driver_data = dev;
+
+	result = usb_ep_enable(dev->in_ep);
+	if (result != 0) {
+		DBG(dev, "enable %s --> %d\n", dev->in_ep->name, result);
+		goto done;
+	}
+
+	result = usb_ep_enable(dev->out_ep);
+	if (result != 0) {
+		DBG(dev, "enable %s --> %d\n", dev->in_ep->name, result);
+		goto done;
+	}
+
+done:
+	/* on error, disable any endpoints  */
+	if (result != 0) {
+		(void) usb_ep_disable(dev->in_ep);
+		(void) usb_ep_disable(dev->out_ep);
+		dev->in_ep->desc = NULL;
+		dev->out_ep->desc = NULL;
+	}
+
+	/* caller is responsible for cleanup on error */
+	return result;
+}
+
+static void printer_reset_interface(struct printer_dev *dev)
+{
+	unsigned long	flags;
+
+	if (dev->interface < 0)
+		return;
+
+	DBG(dev, "%s\n", __func__);
+
+	if (dev->in_ep->desc)
+		usb_ep_disable(dev->in_ep);
+
+	if (dev->out_ep->desc)
+		usb_ep_disable(dev->out_ep);
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->in_ep->desc = NULL;
+	dev->out_ep->desc = NULL;
+	dev->interface = -1;
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* Change our operational Interface. */
+static int set_interface(struct printer_dev *dev, unsigned number)
+{
+	int			result = 0;
+
+	/* Free the current interface */
+	printer_reset_interface(dev);
+
+	result = set_printer_interface(dev);
+	if (result)
+		printer_reset_interface(dev);
+	else
+		dev->interface = number;
+
+	if (!result)
+		INFO(dev, "Using interface %x\n", number);
+
+	return result;
+}
+
+static void printer_soft_reset(struct printer_dev *dev)
+{
+	struct usb_request	*req;
+
+	INFO(dev, "Received Printer Reset Request\n");
+
+	if (usb_ep_disable(dev->in_ep))
+		DBG(dev, "Failed to disable USB in_ep\n");
+	if (usb_ep_disable(dev->out_ep))
+		DBG(dev, "Failed to disable USB out_ep\n");
+
+	if (dev->current_rx_req != NULL) {
+		list_add(&dev->current_rx_req->list, &dev->rx_reqs);
+		dev->current_rx_req = NULL;
+	}
+	dev->current_rx_bytes = 0;
+	dev->current_rx_buf = NULL;
+	dev->reset_printer = 1;
+
+	while (likely(!(list_empty(&dev->rx_buffers)))) {
+		req = container_of(dev->rx_buffers.next, struct usb_request,
+				list);
+		list_del_init(&req->list);
+		list_add(&req->list, &dev->rx_reqs);
+	}
+
+	while (likely(!(list_empty(&dev->rx_reqs_active)))) {
+		req = container_of(dev->rx_buffers.next, struct usb_request,
+				list);
+		list_del_init(&req->list);
+		list_add(&req->list, &dev->rx_reqs);
+	}
+
+	while (likely(!(list_empty(&dev->tx_reqs_active)))) {
+		req = container_of(dev->tx_reqs_active.next,
+				struct usb_request, list);
+		list_del_init(&req->list);
+		list_add(&req->list, &dev->tx_reqs);
+	}
+
+	if (usb_ep_enable(dev->in_ep))
+		DBG(dev, "Failed to enable USB in_ep\n");
+	if (usb_ep_enable(dev->out_ep))
+		DBG(dev, "Failed to enable USB out_ep\n");
+
+	wake_up_interruptible(&dev->rx_wait);
+	wake_up_interruptible(&dev->tx_wait);
+	wake_up_interruptible(&dev->tx_flush_wait);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static bool gprinter_req_match(struct usb_function *f,
+			       const struct usb_ctrlrequest *ctrl,
+			       bool config0)
+{
+	struct printer_dev	*dev = func_to_printer(f);
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	if (config0)
+		return false;
+
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE ||
+	    (ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
+		return false;
+
+	switch (ctrl->bRequest) {
+	case GET_DEVICE_ID:
+		w_index >>= 8;
+		if (USB_DIR_IN & ctrl->bRequestType)
+			break;
+		return false;
+	case GET_PORT_STATUS:
+		if (!w_value && w_length == 1 &&
+		    (USB_DIR_IN & ctrl->bRequestType))
+			break;
+		return false;
+	case SOFT_RESET:
+		if (!w_value && !w_length &&
+		   !(USB_DIR_IN & ctrl->bRequestType))
+			break;
+		/* fall through */
+	default:
+		return false;
+	}
+	return w_index == dev->interface;
+}
+
+/*
+ * The setup() callback implements all the ep0 functionality that's not
+ * handled lower down.
+ */
+static int printer_func_setup(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct printer_dev *dev = func_to_printer(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	u8			*buf = req->buf;
+	int			value = -EOPNOTSUPP;
+	u16			wIndex = le16_to_cpu(ctrl->wIndex);
+	u16			wValue = le16_to_cpu(ctrl->wValue);
+	u16			wLength = le16_to_cpu(ctrl->wLength);
+
+	DBG(dev, "ctrl req%02x.%02x v%04x i%04x l%d\n",
+		ctrl->bRequestType, ctrl->bRequest, wValue, wIndex, wLength);
+
+	switch (ctrl->bRequestType&USB_TYPE_MASK) {
+	case USB_TYPE_CLASS:
+		switch (ctrl->bRequest) {
+		case GET_DEVICE_ID: /* Get the IEEE-1284 PNP String */
+			/* Only one printer interface is supported. */
+			if ((wIndex>>8) != dev->interface)
+				break;
+
+			if (!*dev->pnp_string) {
+				value = 0;
+				break;
+			}
+			value = strlen(*dev->pnp_string);
+			buf[0] = (value >> 8) & 0xFF;
+			buf[1] = value & 0xFF;
+			memcpy(buf + 2, *dev->pnp_string, value);
+			DBG(dev, "1284 PNP String: %x %s\n", value,
+			    *dev->pnp_string);
+			break;
+
+		case GET_PORT_STATUS: /* Get Port Status */
+			/* Only one printer interface is supported. */
+			if (wIndex != dev->interface)
+				break;
+
+			buf[0] = dev->printer_status;
+			value = min_t(u16, wLength, 1);
+			break;
+
+		case SOFT_RESET: /* Soft Reset */
+			/* Only one printer interface is supported. */
+			if (wIndex != dev->interface)
+				break;
+
+			printer_soft_reset(dev);
+
+			value = 0;
+			break;
+
+		default:
+			goto unknown;
+		}
+		break;
+
+	default:
+unknown:
+		VDBG(dev,
+			"unknown ctrl req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			wValue, wIndex, wLength);
+		break;
+	}
+	/* host either stalls (value < 0) or reports success */
+	if (value >= 0) {
+		req->length = value;
+		req->zero = value < wLength;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			ERROR(dev, "%s:%d Error!\n", __func__, __LINE__);
+			req->status = 0;
+		}
+	}
+	return value;
+}
+
+static int printer_func_bind(struct usb_configuration *c,
+		struct usb_function *f)
+{
+	struct usb_gadget *gadget = c->cdev->gadget;
+	struct printer_dev *dev = func_to_printer(f);
+	struct device *pdev;
+	struct usb_composite_dev *cdev = c->cdev;
+	struct usb_ep *in_ep;
+	struct usb_ep *out_ep = NULL;
+	struct usb_request *req;
+	dev_t devt;
+	int id;
+	int ret;
+	u32 i;
+
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	intf_desc.bInterfaceNumber = id;
+
+	/* finish hookup to lower layer ... */
+	dev->gadget = gadget;
+
+	/* all we really need is bulk IN/OUT */
+	in_ep = usb_ep_autoconfig(cdev->gadget, &fs_ep_in_desc);
+	if (!in_ep) {
+autoconf_fail:
+		dev_err(&cdev->gadget->dev, "can't autoconfigure on %s\n",
+			cdev->gadget->name);
+		return -ENODEV;
+	}
+
+	out_ep = usb_ep_autoconfig(cdev->gadget, &fs_ep_out_desc);
+	if (!out_ep)
+		goto autoconf_fail;
+
+	/* assumes that all endpoints are dual-speed */
+	hs_ep_in_desc.bEndpointAddress = fs_ep_in_desc.bEndpointAddress;
+	hs_ep_out_desc.bEndpointAddress = fs_ep_out_desc.bEndpointAddress;
+	ss_ep_in_desc.bEndpointAddress = fs_ep_in_desc.bEndpointAddress;
+	ss_ep_out_desc.bEndpointAddress = fs_ep_out_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, fs_printer_function,
+			hs_printer_function, ss_printer_function,
+			ss_printer_function);
+	if (ret)
+		return ret;
+
+	dev->in_ep = in_ep;
+	dev->out_ep = out_ep;
+
+	ret = -ENOMEM;
+	for (i = 0; i < dev->q_len; i++) {
+		req = printer_req_alloc(dev->in_ep, USB_BUFSIZE, GFP_KERNEL);
+		if (!req)
+			goto fail_tx_reqs;
+		list_add(&req->list, &dev->tx_reqs);
+	}
+
+	for (i = 0; i < dev->q_len; i++) {
+		req = printer_req_alloc(dev->out_ep, USB_BUFSIZE, GFP_KERNEL);
+		if (!req)
+			goto fail_rx_reqs;
+		list_add(&req->list, &dev->rx_reqs);
+	}
+
+	/* Setup the sysfs files for the printer gadget. */
+	devt = MKDEV(major, dev->minor);
+	pdev = device_create(usb_gadget_class, NULL, devt,
+				  NULL, "g_printer%d", dev->minor);
+	if (IS_ERR(pdev)) {
+		ERROR(dev, "Failed to create device: g_printer\n");
+		ret = PTR_ERR(pdev);
+		goto fail_rx_reqs;
+	}
+
+	/*
+	 * Register a character device as an interface to a user mode
+	 * program that handles the printer specific functionality.
+	 */
+	cdev_init(&dev->printer_cdev, &printer_io_operations);
+	dev->printer_cdev.owner = THIS_MODULE;
+	ret = cdev_add(&dev->printer_cdev, devt, 1);
+	if (ret) {
+		ERROR(dev, "Failed to open char device\n");
+		goto fail_cdev_add;
+	}
+
+	return 0;
+
+fail_cdev_add:
+	device_destroy(usb_gadget_class, devt);
+
+fail_rx_reqs:
+	while (!list_empty(&dev->rx_reqs)) {
+		req = container_of(dev->rx_reqs.next, struct usb_request, list);
+		list_del(&req->list);
+		printer_req_free(dev->out_ep, req);
+	}
+
+fail_tx_reqs:
+	while (!list_empty(&dev->tx_reqs)) {
+		req = container_of(dev->tx_reqs.next, struct usb_request, list);
+		list_del(&req->list);
+		printer_req_free(dev->in_ep, req);
+	}
+
+	usb_free_all_descriptors(f);
+	return ret;
+
+}
+
+static int printer_func_set_alt(struct usb_function *f,
+		unsigned intf, unsigned alt)
+{
+	struct printer_dev *dev = func_to_printer(f);
+	int ret = -ENOTSUPP;
+
+	if (!alt)
+		ret = set_interface(dev, intf);
+
+	return ret;
+}
+
+static void printer_func_disable(struct usb_function *f)
+{
+	struct printer_dev *dev = func_to_printer(f);
+
+	DBG(dev, "%s\n", __func__);
+
+	printer_reset_interface(dev);
+}
+
+static inline struct f_printer_opts
+*to_f_printer_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_printer_opts,
+			    func_inst.group);
+}
+
+static void printer_attr_release(struct config_item *item)
+{
+	struct f_printer_opts *opts = to_f_printer_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations printer_item_ops = {
+	.release	= printer_attr_release,
+};
+
+static ssize_t f_printer_opts_pnp_string_show(struct config_item *item,
+					      char *page)
+{
+	struct f_printer_opts *opts = to_f_printer_opts(item);
+	int result = 0;
+
+	mutex_lock(&opts->lock);
+	if (!opts->pnp_string)
+		goto unlock;
+
+	result = strlcpy(page, opts->pnp_string, PAGE_SIZE);
+	if (result >= PAGE_SIZE) {
+		result = PAGE_SIZE;
+	} else if (page[result - 1] != '\n' && result + 1 < PAGE_SIZE) {
+		page[result++] = '\n';
+		page[result] = '\0';
+	}
+
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_printer_opts_pnp_string_store(struct config_item *item,
+					       const char *page, size_t len)
+{
+	struct f_printer_opts *opts = to_f_printer_opts(item);
+	char *new_pnp;
+	int result;
+
+	mutex_lock(&opts->lock);
+
+	new_pnp = kstrndup(page, len, GFP_KERNEL);
+	if (!new_pnp) {
+		result = -ENOMEM;
+		goto unlock;
+	}
+
+	if (opts->pnp_string_allocated)
+		kfree(opts->pnp_string);
+
+	opts->pnp_string_allocated = true;
+	opts->pnp_string = new_pnp;
+	result = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+CONFIGFS_ATTR(f_printer_opts_, pnp_string);
+
+static ssize_t f_printer_opts_q_len_show(struct config_item *item,
+					 char *page)
+{
+	struct f_printer_opts *opts = to_f_printer_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%d\n", opts->q_len);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_printer_opts_q_len_store(struct config_item *item,
+					  const char *page, size_t len)
+{
+	struct f_printer_opts *opts = to_f_printer_opts(item);
+	int ret;
+	u16 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou16(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->q_len = (unsigned)num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_printer_opts_, q_len);
+
+static struct configfs_attribute *printer_attrs[] = {
+	&f_printer_opts_attr_pnp_string,
+	&f_printer_opts_attr_q_len,
+	NULL,
+};
+
+static const struct config_item_type printer_func_type = {
+	.ct_item_ops	= &printer_item_ops,
+	.ct_attrs	= printer_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static inline int gprinter_get_minor(void)
+{
+	int ret;
+
+	ret = ida_simple_get(&printer_ida, 0, 0, GFP_KERNEL);
+	if (ret >= PRINTER_MINORS) {
+		ida_simple_remove(&printer_ida, ret);
+		ret = -ENODEV;
+	}
+
+	return ret;
+}
+
+static inline void gprinter_put_minor(int minor)
+{
+	ida_simple_remove(&printer_ida, minor);
+}
+
+static int gprinter_setup(int);
+static void gprinter_cleanup(void);
+
+static void gprinter_free_inst(struct usb_function_instance *f)
+{
+	struct f_printer_opts *opts;
+
+	opts = container_of(f, struct f_printer_opts, func_inst);
+
+	mutex_lock(&printer_ida_lock);
+
+	gprinter_put_minor(opts->minor);
+	if (ida_is_empty(&printer_ida))
+		gprinter_cleanup();
+
+	mutex_unlock(&printer_ida_lock);
+
+	if (opts->pnp_string_allocated)
+		kfree(opts->pnp_string);
+	kfree(opts);
+}
+
+static struct usb_function_instance *gprinter_alloc_inst(void)
+{
+	struct f_printer_opts *opts;
+	struct usb_function_instance *ret;
+	int status = 0;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = gprinter_free_inst;
+	ret = &opts->func_inst;
+
+	mutex_lock(&printer_ida_lock);
+
+	if (ida_is_empty(&printer_ida)) {
+		status = gprinter_setup(PRINTER_MINORS);
+		if (status) {
+			ret = ERR_PTR(status);
+			kfree(opts);
+			goto unlock;
+		}
+	}
+
+	opts->minor = gprinter_get_minor();
+	if (opts->minor < 0) {
+		ret = ERR_PTR(opts->minor);
+		kfree(opts);
+		if (ida_is_empty(&printer_ida))
+			gprinter_cleanup();
+		goto unlock;
+	}
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &printer_func_type);
+
+unlock:
+	mutex_unlock(&printer_ida_lock);
+	return ret;
+}
+
+static void gprinter_free(struct usb_function *f)
+{
+	struct printer_dev *dev = func_to_printer(f);
+	struct f_printer_opts *opts;
+
+	opts = container_of(f->fi, struct f_printer_opts, func_inst);
+
+	kref_put(&dev->kref, printer_dev_free);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void printer_func_unbind(struct usb_configuration *c,
+		struct usb_function *f)
+{
+	struct printer_dev	*dev;
+	struct usb_request	*req;
+
+	dev = func_to_printer(f);
+
+	device_destroy(usb_gadget_class, MKDEV(major, dev->minor));
+
+	/* Remove Character Device */
+	cdev_del(&dev->printer_cdev);
+
+	/* we must already have been disconnected ... no i/o may be active */
+	WARN_ON(!list_empty(&dev->tx_reqs_active));
+	WARN_ON(!list_empty(&dev->rx_reqs_active));
+
+	/* Free all memory for this driver. */
+	while (!list_empty(&dev->tx_reqs)) {
+		req = container_of(dev->tx_reqs.next, struct usb_request,
+				list);
+		list_del(&req->list);
+		printer_req_free(dev->in_ep, req);
+	}
+
+	if (dev->current_rx_req != NULL)
+		printer_req_free(dev->out_ep, dev->current_rx_req);
+
+	while (!list_empty(&dev->rx_reqs)) {
+		req = container_of(dev->rx_reqs.next,
+				struct usb_request, list);
+		list_del(&req->list);
+		printer_req_free(dev->out_ep, req);
+	}
+
+	while (!list_empty(&dev->rx_buffers)) {
+		req = container_of(dev->rx_buffers.next,
+				struct usb_request, list);
+		list_del(&req->list);
+		printer_req_free(dev->out_ep, req);
+	}
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *gprinter_alloc(struct usb_function_instance *fi)
+{
+	struct printer_dev	*dev;
+	struct f_printer_opts	*opts;
+
+	opts = container_of(fi, struct f_printer_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	if (opts->minor >= minors) {
+		mutex_unlock(&opts->lock);
+		return ERR_PTR(-ENOENT);
+	}
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		mutex_unlock(&opts->lock);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	kref_init(&dev->kref);
+	++opts->refcnt;
+	dev->minor = opts->minor;
+	dev->pnp_string = &opts->pnp_string;
+	dev->q_len = opts->q_len;
+	mutex_unlock(&opts->lock);
+
+	dev->function.name = "printer";
+	dev->function.bind = printer_func_bind;
+	dev->function.setup = printer_func_setup;
+	dev->function.unbind = printer_func_unbind;
+	dev->function.set_alt = printer_func_set_alt;
+	dev->function.disable = printer_func_disable;
+	dev->function.req_match = gprinter_req_match;
+	dev->function.free_func = gprinter_free;
+
+	INIT_LIST_HEAD(&dev->tx_reqs);
+	INIT_LIST_HEAD(&dev->rx_reqs);
+	INIT_LIST_HEAD(&dev->rx_buffers);
+	INIT_LIST_HEAD(&dev->tx_reqs_active);
+	INIT_LIST_HEAD(&dev->rx_reqs_active);
+
+	spin_lock_init(&dev->lock);
+	mutex_init(&dev->lock_printer_io);
+	init_waitqueue_head(&dev->rx_wait);
+	init_waitqueue_head(&dev->tx_wait);
+	init_waitqueue_head(&dev->tx_flush_wait);
+
+	dev->interface = -1;
+	dev->printer_cdev_open = 0;
+	dev->printer_status = PRINTER_NOT_ERROR;
+	dev->current_rx_req = NULL;
+	dev->current_rx_bytes = 0;
+	dev->current_rx_buf = NULL;
+
+	return &dev->function;
+}
+
+DECLARE_USB_FUNCTION_INIT(printer, gprinter_alloc_inst, gprinter_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Craig Nadler");
+
+static int gprinter_setup(int count)
+{
+	int status;
+	dev_t devt;
+
+	usb_gadget_class = class_create(THIS_MODULE, "usb_printer_gadget");
+	if (IS_ERR(usb_gadget_class)) {
+		status = PTR_ERR(usb_gadget_class);
+		usb_gadget_class = NULL;
+		pr_err("unable to create usb_gadget class %d\n", status);
+		return status;
+	}
+
+	status = alloc_chrdev_region(&devt, 0, count, "USB printer gadget");
+	if (status) {
+		pr_err("alloc_chrdev_region %d\n", status);
+		class_destroy(usb_gadget_class);
+		usb_gadget_class = NULL;
+		return status;
+	}
+
+	major = MAJOR(devt);
+	minors = count;
+
+	return status;
+}
+
+static void gprinter_cleanup(void)
+{
+	if (major) {
+		unregister_chrdev_region(MKDEV(major, 0), minors);
+		major = minors = 0;
+	}
+	class_destroy(usb_gadget_class);
+	usb_gadget_class = NULL;
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_rndis.c b/marvell/linux/drivers/usb/gadget/function/f_rndis.c
new file mode 100644
index 0000000..f1ca436
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_rndis.c
@@ -0,0 +1,1149 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_rndis.c -- RNDIS link function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ * Copyright (C) 2009 Samsung Electronics
+ *                    Author: Michal Nazarewicz (mina86@mina86.com)
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/atomic.h>
+
+#include "u_ether.h"
+#include "rndis.h"
+
+#ifdef CONFIG_ASR_TOE
+#include <linux/toe.h>
+#endif
+
+/*
+ * This function is an RNDIS Ethernet port -- a Microsoft protocol that's
+ * been promoted instead of the standard CDC Ethernet.  The published RNDIS
+ * spec is ambiguous, incomplete, and needlessly complex.  Variants such as
+ * ActiveSync have even worse status in terms of specification.
+ *
+ * In short:  it's a protocol controlled by (and for) Microsoft, not for an
+ * Open ecosystem or markets.  Linux supports it *only* because Microsoft
+ * doesn't support the CDC Ethernet standard.
+ *
+ * The RNDIS data transfer model is complex, with multiple Ethernet packets
+ * per USB message, and out of band data.  The control model is built around
+ * what's essentially an "RNDIS RPC" protocol.  It's all wrapped in a CDC ACM
+ * (modem, not Ethernet) veneer, with those ACM descriptors being entirely
+ * useless (they're ignored).  RNDIS expects to be the only function in its
+ * configuration, so it's no real help if you need composite devices; and
+ * it expects to be the first configuration too.
+ *
+ * There is a single technical advantage of RNDIS over CDC Ethernet, if you
+ * discount the fluff that its RPC can be made to deliver: it doesn't need
+ * a NOP altsetting for the data interface.  That lets it work on some of the
+ * "so smart it's stupid" hardware which takes over configuration changes
+ * from the software, and adds restrictions like "no altsettings".
+ *
+ * Unfortunately MSFT's RNDIS drivers are buggy.  They hang or oops, and
+ * have all sorts of contrary-to-specification oddities that can prevent
+ * them from working sanely.  Since bugfixes (or accurate specs, letting
+ * Linux work around those bugs) are unlikely to ever come from MSFT, you
+ * may want to avoid using RNDIS on purely operational grounds.
+ *
+ * Omissions from the RNDIS 1.0 specification include:
+ *
+ *   - Power management ... references data that's scattered around lots
+ *     of other documentation, which is incorrect/incomplete there too.
+ *
+ *   - There are various undocumented protocol requirements, like the need
+ *     to send garbage in some control-OUT messages.
+ *
+ *   - MS-Windows drivers sometimes emit undocumented requests.
+ */
+static unsigned int rndis_dl_max_xfer = 0;
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_DWC3)
+static unsigned int rndis_ul_max_pkt_per_xfer = 2;
+#else
+static unsigned int rndis_ul_max_pkt_per_xfer = 1;
+#endif
+
+module_param(rndis_ul_max_pkt_per_xfer, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(rndis_ul_max_pkt_per_xfer,
+	"Maximum packets per transfer for UL aggregation");
+#ifdef CONFIG_CPU_ASR1903
+u32 nr_aggr_fail_padding;
+#endif
+
+struct f_rndis {
+	struct gether			port;
+	u8				ctrl_id, data_id;
+	u8				ethaddr[ETH_ALEN];
+	u32				vendorID;
+	const char			*manufacturer;
+	int				config;
+
+	struct usb_ep			*notify;
+	struct usb_request		*notify_req;
+	atomic_t			notify_count;
+};
+
+static inline struct f_rndis *func_to_rndis(struct usb_function *f)
+{
+	return container_of(f, struct f_rndis, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static unsigned int bitrate(struct usb_gadget *g)
+{
+	if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS)
+		return 4250000000U;
+	if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+		return 3750000000U;
+	else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return 13 * 512 * 8 * 1000 * 8;
+	else
+		return 19 * 64 * 1 * 1000 * 8;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ */
+
+#define RNDIS_STATUS_INTERVAL_MS	1  /*use small interval instead of 32 */
+#define STATUS_BYTECOUNT		8	/* 8 bytes data */
+
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor rndis_control_intf = {
+	.bLength =		sizeof rndis_control_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	/* status endpoint is optional; this could be patched later */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =   USB_CDC_SUBCLASS_ACM,
+	.bInterfaceProtocol =   USB_CDC_ACM_PROTO_VENDOR,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc header_desc = {
+	.bLength =		sizeof header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = {
+	.bLength =		sizeof call_mgmt_descriptor,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_CALL_MANAGEMENT_TYPE,
+
+	.bmCapabilities =	0x00,
+	.bDataInterface =	0x01,
+};
+
+static struct usb_cdc_acm_descriptor rndis_acm_descriptor = {
+	.bLength =		sizeof rndis_acm_descriptor,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ACM_TYPE,
+
+	.bmCapabilities =	0x00,
+};
+
+static struct usb_cdc_union_desc rndis_union_desc = {
+	.bLength =		sizeof(rndis_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+/* the data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor rndis_data_intf = {
+	.bLength =		sizeof rndis_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+
+static struct usb_interface_assoc_descriptor
+rndis_iad_descriptor = {
+	.bLength =		sizeof rndis_iad_descriptor,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	.bFirstInterface =	0, /* XXX, hardcoded */
+	.bInterfaceCount = 	2,	// control + data
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_CPU_ASR1901)
+	.bFunctionClass =	0xef,
+	.bFunctionSubClass =	0x04,
+	.bFunctionProtocol =	0x01,
+#else
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_ETHERNET,
+	.bFunctionProtocol =	USB_CDC_PROTO_NONE,
+#endif
+	/* .iFunction = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(STATUS_BYTECOUNT),
+	.bInterval =		RNDIS_STATUS_INTERVAL_MS,
+};
+
+static struct usb_endpoint_descriptor fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *eth_fs_function[] = {
+	(struct usb_descriptor_header *) &rndis_iad_descriptor,
+
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_control_intf,
+	(struct usb_descriptor_header *) &header_desc,
+	(struct usb_descriptor_header *) &call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_union_desc,
+	(struct usb_descriptor_header *) &fs_notify_desc,
+
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_data_intf,
+	(struct usb_descriptor_header *) &fs_in_desc,
+	(struct usb_descriptor_header *) &fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(RNDIS_STATUS_INTERVAL_MS)
+};
+
+static struct usb_endpoint_descriptor hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *eth_hs_function[] = {
+	(struct usb_descriptor_header *) &rndis_iad_descriptor,
+
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_control_intf,
+	(struct usb_descriptor_header *) &header_desc,
+	(struct usb_descriptor_header *) &call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_union_desc,
+	(struct usb_descriptor_header *) &hs_notify_desc,
+
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_data_intf,
+	(struct usb_descriptor_header *) &hs_in_desc,
+	(struct usb_descriptor_header *) &hs_out_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(STATUS_BYTECOUNT),
+	.bInterval =		USB_MS_TO_HS_INTERVAL(RNDIS_STATUS_INTERVAL_MS)
+};
+
+static struct usb_ss_ep_comp_descriptor ss_intr_comp_desc = {
+	.bLength =		sizeof ss_intr_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_bulk_comp_desc = {
+	.bLength =		sizeof ss_bulk_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+#ifdef CONFIG_ASR_TOE
+	.bMaxBurst =		15,
+#endif
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *eth_ss_function[] = {
+	(struct usb_descriptor_header *) &rndis_iad_descriptor,
+
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_control_intf,
+	(struct usb_descriptor_header *) &header_desc,
+	(struct usb_descriptor_header *) &call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_union_desc,
+	(struct usb_descriptor_header *) &ss_notify_desc,
+	(struct usb_descriptor_header *) &ss_intr_comp_desc,
+
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_data_intf,
+	(struct usb_descriptor_header *) &ss_in_desc,
+	(struct usb_descriptor_header *) &ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_out_desc,
+	(struct usb_descriptor_header *) &ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string rndis_string_defs[] = {
+	[0].s = "RNDIS Communications Control",
+	[1].s = "RNDIS Ethernet Data",
+	[2].s = "RNDIS",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings rndis_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		rndis_string_defs,
+};
+
+static struct usb_gadget_strings *rndis_strings[] = {
+	&rndis_string_table,
+	NULL,
+};
+
+#define RNDIS_OUT_BUFF_LEN	128
+#define RNDIS_IN_BUFF_LEN	128
+
+static u8 rndis_out_buff[RNDIS_OUT_BUFF_LEN];
+static u8 rndis_in_buff[RNDIS_IN_BUFF_LEN];
+/*-------------------------------------------------------------------------*/
+
+static struct sk_buff *rndis_add_header(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct sk_buff *skb2;
+
+	if (!skb_cloned(skb) && skb_headroom(skb) >= sizeof(struct rndis_packet_msg_type)) {
+		rndis_add_hdr(skb);
+		return skb;
+	}
+	skb2 = skb_realloc_headroom(skb,
+			sizeof(struct rndis_packet_msg_type));
+	if (skb2)
+		rndis_add_hdr(skb2);
+
+	dev_kfree_skb_any(skb);
+	return skb2;
+}
+
+#ifdef CONFIG_USB_G_RNDIS_MULT_PKT_SUPPORT
+#ifdef CONFIG_ASR_TOE
+static struct sk_buff *rndis_multpkt_add_header(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct sk_buff *prev_skb, *first_skb;
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list) && list_empty(&aggr_ctx->toe_list)) {
+		/* first packet in the usb request */
+		skb = rndis_add_header(port, skb, aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		aggr_ctx->is_toe = false;
+		return skb;
+	}
+
+	if (aggr_ctx->is_toe) {
+		aggr_ctx->pending_skb = skb;
+		first_skb = (struct sk_buff *)list_first_entry(&aggr_ctx->toe_list, struct toe_pkt_desc, list);	
+		return first_skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(!aggr_ctx->total_size);
+	aggr_ctx->pending_skb = skb;
+	return first_skb;
+}
+#else
+#ifndef CONFIG_USBNET_USE_SG
+static struct sk_buff *rndis_multpkt_add_header(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	struct	rndis_packet_msg_type *header;
+	int padding, aggregate_length;
+	struct sk_buff *prev_skb, *first_skb;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		/*first packet in the usb request*/
+		skb = rndis_add_header(port, skb, aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+		}
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+	prev_skb = skb_peek_tail(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!prev_skb);
+	BUG_ON(!aggr_ctx->total_size);
+
+	padding = (unsigned)skb->data - (unsigned)prev_skb->tail - sizeof(struct rndis_packet_msg_type);
+	aggregate_length = skb->tail - first_skb->data;
+
+	/*rndis aggregation condition*/
+	if (skb_cloned(skb) ||
+		   skb_headroom(skb) < sizeof(struct rndis_packet_msg_type) ||
+		   aggregate_length > rndis_dl_max_xfer ||
+		   padding > AGGR_MAX_PADDING  ||
+		   padding < 0) {
+		aggr_ctx->pending_skb = skb;
+#ifdef CONFIG_CPU_ASR1903
+		if (padding > AGGR_MAX_PADDING )
+			nr_aggr_fail_padding++;
+#endif
+		return first_skb;
+	}
+
+	/*new skb aggregation:*/
+	rndis_add_hdr(skb);
+	header = (struct rndis_packet_msg_type *)prev_skb->data;
+	header->MessageLength = cpu_to_le32(skb->data - prev_skb->data);
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+	aggr_ctx->total_size = skb->tail - first_skb->data;
+
+	return first_skb;
+}
+#else
+static struct sk_buff *rndis_multpkt_add_header(struct gether *port,
+				 struct sk_buff *skb, struct aggr_ctx *aggr_ctx)
+{
+	int aggregate_length;
+	struct sk_buff *first_skb;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!skb);
+
+	if (skb_queue_empty(&aggr_ctx->skb_list)) {
+		/*first packet in the usb request*/
+		skb = rndis_add_header(port, skb, aggr_ctx);
+		if (likely(skb)) {
+			aggr_ctx->total_size = skb->len;
+			skb_queue_tail(&aggr_ctx->skb_list, skb);
+			aggr_ctx->num_sgs = 1;
+		} else {
+			aggr_ctx->total_size = 0;
+			aggr_ctx->num_sgs = 0;
+			pr_err("error: %s 1st pkt failed\n", __func__);
+		}
+		return skb;
+	}
+
+	first_skb = skb_peek(&aggr_ctx->skb_list);
+
+	BUG_ON(!first_skb);
+	BUG_ON(!aggr_ctx->total_size);
+
+	aggregate_length = aggr_ctx->total_size + skb->len + sizeof(struct rndis_packet_msg_type);
+
+	/*rndis aggregation condition*/
+	if (skb_cloned(skb) ||
+		   skb_headroom(skb) < sizeof(struct rndis_packet_msg_type) ||
+		   aggregate_length > rndis_dl_max_xfer || aggr_ctx->num_sgs >= (USBNET_SG_NENTS - 1)) {
+		aggr_ctx->pending_skb = skb;
+		return first_skb;
+	}
+
+	/*new skb aggregation:*/
+	rndis_add_hdr(skb);
+	skb_queue_tail(&aggr_ctx->skb_list, skb);
+	aggr_ctx->total_size += skb->len;
+	aggr_ctx->num_sgs++;
+
+	return first_skb;
+}
+#endif
+#endif
+#endif
+static void rndis_response_available(void *_rndis)
+{
+	struct f_rndis			*rndis = _rndis;
+	struct usb_request		*req = rndis->notify_req;
+	struct usb_composite_dev	*cdev = rndis->port.func.config->cdev;
+	__le32				*data = req->buf;
+	int				status;
+
+	if (atomic_inc_return(&rndis->notify_count) != 1)
+		return;
+
+	/* Send RNDIS RESPONSE_AVAILABLE notification; a
+	 * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too
+	 *
+	 * This is the only notification defined by RNDIS.
+	 */
+	data[0] = cpu_to_le32(1);
+	data[1] = cpu_to_le32(0);
+
+	status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
+	if (status) {
+		atomic_dec(&rndis->notify_count);
+		ERROR(cdev, "notify/0 --> %d\n", status);
+	}
+}
+
+static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_rndis			*rndis = req->context;
+	struct usb_composite_dev	*cdev;
+	int				status = req->status;
+
+	if (!rndis->port.func.config || !rndis->port.func.config->cdev)
+		return;
+	else
+		cdev = rndis->port.func.config->cdev;
+
+	/* after TX:
+	 *  - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control)
+	 *  - RNDIS_RESPONSE_AVAILABLE (status/irq)
+	 */
+	switch (status) {
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		atomic_set(&rndis->notify_count, 0);
+		break;
+	default:
+		DBG(cdev, "RNDIS %s response error %d, %d/%d\n",
+			ep->name, status,
+			req->actual, req->length);
+		/* FALLTHROUGH */
+	case 0:
+		if (ep != rndis->notify)
+			break;
+
+		/* handle multiple pending RNDIS_RESPONSE_AVAILABLE
+		 * notifications by resending until we're done
+		 */
+		if (atomic_dec_and_test(&rndis->notify_count))
+			break;
+		status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
+		if (status) {
+			atomic_dec(&rndis->notify_count);
+			ERROR(cdev, "notify/1 --> %d\n", status);
+		}
+		break;
+	}
+}
+
+static void __rndis_dump_data(u8 *buf, int len)
+{
+	int i;
+
+	for (i = 0x0; i < len; i += 8) {
+		pr_info("0x%08x: %02x %02x %02x %02x %02x %02x %02x %02x\n", i,
+			buf[i + 0],
+			buf[i + 1],
+			buf[i + 2],
+			buf[i + 3],
+			buf[i + 4],
+			buf[i + 5],
+			buf[i + 6],
+			buf[i + 7]);
+	}
+}
+
+void rndis_dump_data(void)
+{
+	pr_err("rndis out buff:\n");
+	__rndis_dump_data(rndis_out_buff, RNDIS_OUT_BUFF_LEN);
+	pr_err("rndis in buff:\n");
+	__rndis_dump_data(rndis_in_buff, RNDIS_IN_BUFF_LEN);
+}
+
+static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_rndis			*rndis = req->context;
+	int				status;
+	rndis_init_msg_type		*buf;
+
+	if ((0 != req->status) && (0 == req->actual)) {
+		pr_err("!!!!!!%s req status: %d, actual:%d\n", __func__, req->status, req->actual);
+		return;
+	}
+
+	memset(rndis_out_buff, 0x0, RNDIS_OUT_BUFF_LEN);
+	if (req->actual > RNDIS_OUT_BUFF_LEN)
+		memcpy(rndis_out_buff, req->buf, RNDIS_OUT_BUFF_LEN);
+	else
+		memcpy(rndis_out_buff, req->buf, req->actual);
+
+	/* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */
+//	spin_lock(&dev->lock);
+	status = rndis_msg_parser(rndis->config, (u8 *) req->buf);
+	if (status < 0) {
+		pr_err("RNDIS command error %d, %d/%d\n",
+			status, req->actual, req->length);
+		rndis_dump_data();
+	}
+	buf = (rndis_init_msg_type *)req->buf;
+	if (buf->MessageType == RNDIS_MSG_INIT) {
+		rndis_dl_max_xfer = buf->MaxTransferSize;
+		pr_info("%s: MaxTransferSize: %d : max_pkt_per_xferr: %d\n",
+			__func__, buf->MaxTransferSize, buf->MaxTransferSize >> 11);
+	}
+
+//	spin_unlock(&dev->lock);
+}
+
+static int
+rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_rndis		*rndis = func_to_rndis(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+	/* RNDIS uses the CDC command encapsulation mechanism to implement
+	 * an RPC scheme, with much getting/setting of attributes by OID.
+	 */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SEND_ENCAPSULATED_COMMAND:
+		if (w_value || w_index != rndis->ctrl_id)
+			goto invalid;
+		/* read the request; process it later */
+		value = w_length;
+		req->complete = rndis_command_complete;
+		req->context = rndis;
+		/* later, rndis_response_available() sends a notification */
+		break;
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_GET_ENCAPSULATED_RESPONSE:
+		if (w_value || w_index != rndis->ctrl_id)
+			goto invalid;
+		else {
+			u8 *buf;
+			u32 n;
+
+			/* return the result */
+			buf = rndis_get_next_response(rndis->config, &n);
+			if (buf) {
+				memcpy(req->buf, buf, n);
+				req->complete = rndis_response_complete;
+				req->context = rndis;
+				rndis_free_response(rndis->config, buf);
+				value = n;
+
+				memset(rndis_in_buff, 0x0, RNDIS_IN_BUFF_LEN);
+				if (value > RNDIS_IN_BUFF_LEN)
+					memcpy(rndis_in_buff, req->buf, RNDIS_IN_BUFF_LEN);
+				else
+					memcpy(rndis_in_buff, req->buf, value);
+			} else {
+				INFO(cdev, "!!no NDIS resp for req%02x.%02x v%04x i%04x l%d\n",
+						ctrl->bRequestType, ctrl->bRequest,
+						w_value, w_index, w_length);
+				/* else stalls ... spec says to avoid that */
+			}
+		}
+		break;
+
+	/* for some specific usb host */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SET_ETHERNET_PACKET_FILTER:
+		INFO(cdev, "!!!rndis req%02x.%02x v%04x i%04x l%d\n",
+				ctrl->bRequestType, ctrl->bRequest,
+				w_value, w_index, w_length);
+		if (w_length == 0)
+			return 0;
+		else
+			break;
+
+	default:
+invalid:
+		ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = (value < w_length);
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "rndis response on err %d\n", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+
+static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_rndis		*rndis = func_to_rndis(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0 */
+
+	if (intf == rndis->ctrl_id) {
+		if (rndis->notify->driver_data) {
+			VDBG(cdev, "reset rndis control %d\n", intf);
+			usb_ep_disable(rndis->notify);
+		}
+		if (!rndis->notify->desc) {
+			VDBG(cdev, "init rndis ctrl %d\n", intf);
+			if (config_ep_by_speed(cdev->gadget, f, rndis->notify))
+				goto fail;
+		}
+		usb_ep_enable(rndis->notify);
+		rndis->notify->driver_data = rndis;
+
+	} else if (intf == rndis->data_id) {
+		struct net_device	*net;
+
+		if (rndis->port.in_ep->driver_data) {
+			INFO(cdev, "reset rndis\n");
+			gether_disconnect(&rndis->port);
+		}
+
+		if (!rndis->port.in_ep->desc || !rndis->port.out_ep->desc) {
+			INFO(cdev, "init rndis\n");
+			if (config_ep_by_speed(cdev->gadget, f,
+					       rndis->port.in_ep) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       rndis->port.out_ep)) {
+				rndis->port.in_ep->desc = NULL;
+				rndis->port.out_ep->desc = NULL;
+				goto fail;
+			}
+		}
+
+		/* Avoid ZLPs; they can be troublesome. */
+		rndis->port.is_zlp_ok = false;
+
+		/* RNDIS should be in the "RNDIS uninitialized" state,
+		 * either never activated or after rndis_uninit().
+		 *
+		 * We don't want data to flow here until a nonzero packet
+		 * filter is set, at which point it enters "RNDIS data
+		 * initialized" state ... but we do want the endpoints
+		 * to be activated.  It's a strange little state.
+		 *
+		 * REVISIT the RNDIS gadget code has done this wrong for a
+		 * very long time.  We need another call to the link layer
+		 * code -- gether_updown(...bool) maybe -- to do it right.
+		 */
+		rndis->port.cdc_filter = 0;
+#ifdef CONFIG_ASR_TOE
+		rndis->port.ueth_type = UETHER_RNDIS;
+#endif
+		INFO(cdev, "RNDIS RX/TX early activation ... \n");
+		net = gether_connect(&rndis->port);
+		if (IS_ERR(net))
+			return PTR_ERR(net);
+
+		rndis_set_param_dev(rndis->config, net,
+				&rndis->port.cdc_filter);
+		rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3,
+				bitrate(cdev->gadget) / 100);
+	} else
+		goto fail;
+
+	return 0;
+fail:
+	return -EINVAL;
+}
+
+static void rndis_disable(struct usb_function *f)
+{
+	struct f_rndis		*rndis = func_to_rndis(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	if (!rndis->notify->driver_data)
+		return;
+
+#ifdef CONFIG_ASR_TOE
+	rndis->port.ueth_type = UETHER_UNKNOWN;
+#endif
+
+	INFO(cdev, "rndis deactivated\n");
+
+	rndis_uninit(rndis->config);
+	gether_disconnect(&rndis->port);
+
+	usb_ep_disable(rndis->notify);
+	rndis->notify->driver_data = NULL;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * This isn't quite the same mechanism as CDC Ethernet, since the
+ * notification scheme passes less data, but the same set of link
+ * states must be tested.  A key difference is that altsettings are
+ * not used to tell whether the link should send packets or not.
+ */
+
+static void rndis_open(struct gether *geth)
+{
+	struct f_rndis		*rndis = func_to_rndis(&geth->func);
+	struct usb_composite_dev *cdev = geth->func.config->cdev;
+
+	DBG(cdev, "%s\n", __func__);
+
+	rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3,
+				bitrate(cdev->gadget) / 100);
+	rndis_signal_connect(rndis->config);
+}
+
+static void rndis_close(struct gether *geth)
+{
+	struct f_rndis		*rndis = func_to_rndis(&geth->func);
+
+	DBG(geth->func.config->cdev, "%s\n", __func__);
+
+	rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0);
+	rndis_signal_disconnect(rndis->config);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int
+rndis_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_rndis		*rndis = func_to_rndis(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	rndis->ctrl_id = status;
+	rndis_iad_descriptor.bFirstInterface = status;
+
+	rndis_control_intf.bInterfaceNumber = status;
+	rndis_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	rndis->data_id = status;
+
+	rndis_data_intf.bInterfaceNumber = status;
+	rndis_union_desc.bSlaveInterface0 = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
+	if (!ep)
+		goto fail;
+	rndis->port.in_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
+	if (!ep)
+		goto fail;
+	rndis->port.out_ep = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	/* NOTE:  a status/notification endpoint is, strictly speaking,
+	 * optional.  We don't treat it that way though!  It's simpler,
+	 * and some newer profiles don't treat it as optional.
+	 */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
+	if (!ep)
+		goto fail;
+	rndis->notify = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	status = -ENOMEM;
+
+	/* allocate notification request and buffer */
+	rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!rndis->notify_req)
+		goto fail;
+	rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
+	if (!rndis->notify_req->buf)
+		goto fail;
+	rndis->notify_req->length = STATUS_BYTECOUNT;
+	rndis->notify_req->context = rndis;
+	rndis->notify_req->complete = rndis_response_complete;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	hs_in_desc.bEndpointAddress = fs_in_desc.bEndpointAddress;
+	hs_out_desc.bEndpointAddress = fs_out_desc.bEndpointAddress;
+	hs_notify_desc.bEndpointAddress = fs_notify_desc.bEndpointAddress;
+
+	ss_in_desc.bEndpointAddress = fs_in_desc.bEndpointAddress;
+	ss_out_desc.bEndpointAddress = fs_out_desc.bEndpointAddress;
+	ss_notify_desc.bEndpointAddress = fs_notify_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function,
+			eth_ss_function, eth_ss_function);
+	if (status)
+		goto fail;
+
+	rndis->port.open = rndis_open;
+	rndis->port.close = rndis_close;
+
+	status = rndis_register(rndis_response_available, rndis);
+	if (status < 0)
+		goto fail;
+	rndis->config = status;
+
+	rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0);
+	rndis_set_host_mac(rndis->config, rndis->ethaddr);
+	rndis_set_max_pkt_xfer(rndis->config, rndis_ul_max_pkt_per_xfer);
+
+	if (rndis->manufacturer && rndis->vendorID &&
+			rndis_set_param_vendor(rndis->config, rndis->vendorID,
+					       rndis->manufacturer))
+		goto fail;
+
+	/* NOTE:  all that is done without knowing or caring about
+	 * the network link ... which is unavailable to this code
+	 * until we're activated via set_alt().
+	 */
+
+	DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			rndis->port.in_ep->name, rndis->port.out_ep->name,
+			rndis->notify->name);
+
+#ifdef CONFIG_ASR_TOE
+	rndis->port.ueth_type = UETHER_RNDIS;
+#endif
+
+#ifdef CONFIG_USBNET_USE_SG
+	rndis->port.is_sg_mode = 1;
+#endif
+	return 0;
+
+fail:
+	usb_free_all_descriptors(f);
+
+	if (rndis->notify_req) {
+		kfree(rndis->notify_req->buf);
+		usb_ep_free_request(rndis->notify, rndis->notify_req);
+	}
+
+	/* we might as well release our claims on endpoints */
+	if (rndis->notify)
+		rndis->notify->driver_data = NULL;
+	if (rndis->port.out_ep)
+		rndis->port.out_ep->driver_data = NULL;
+	if (rndis->port.in_ep)
+		rndis->port.in_ep->driver_data = NULL;
+
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static void
+rndis_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_rndis		*rndis = func_to_rndis(f);
+
+#ifdef CONFIG_ASR_TOE
+	rndis->port.ueth_type = UETHER_UNKNOWN;
+#endif
+	rndis_deregister(rndis->config);
+	rndis_exit();
+
+	usb_free_all_descriptors(f);
+
+	kfree(rndis->notify_req->buf);
+	usb_ep_free_request(rndis->notify, rndis->notify_req);
+	kfree(rndis);
+	#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		rndis_string_defs[0].id = 0;
+    #endif
+
+}
+
+/* Some controllers can't support RNDIS ... */
+static inline bool can_support_rndis(struct usb_configuration *c)
+{
+	/* everything else is *presumably* fine */
+	return true;
+}
+
+int
+rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		u32 vendorID, const char *manufacturer, struct eth_dev *dev)
+{
+	struct f_rndis	*rndis;
+	int		status;
+
+	if (!can_support_rndis(c) || !ethaddr)
+		return -EINVAL;
+
+	/* setup RNDIS itself */
+	status = rndis_init();
+	if (status < 0)
+		return status;
+
+	if (rndis_string_defs[0].id == 0) {
+		status = usb_string_ids_tab(c->cdev, rndis_string_defs);
+		if (status)
+			return status;
+
+		rndis_control_intf.iInterface = rndis_string_defs[0].id;
+		rndis_data_intf.iInterface = rndis_string_defs[1].id;
+		rndis_iad_descriptor.iFunction = rndis_string_defs[2].id;
+	}
+
+	/* allocate and initialize one new instance */
+	status = -ENOMEM;
+	rndis = kzalloc(sizeof *rndis, GFP_KERNEL);
+	if (!rndis)
+		goto fail;
+
+	memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
+	rndis->vendorID = vendorID;
+	rndis->manufacturer = manufacturer;
+
+	rndis->port.ioport = dev;
+	/* RNDIS activates when the host changes this filter */
+	rndis->port.cdc_filter = 0;
+
+	/* RNDIS has special (and complex) framing */
+	rndis->port.header_len = sizeof(struct rndis_packet_msg_type);
+
+#ifdef CONFIG_USB_G_RNDIS_MULT_PKT_SUPPORT
+	rndis->port.wrap = rndis_multpkt_add_header;
+#else
+	rndis->port.wrap = rndis_add_header;
+#endif
+	rndis->port.unwrap = rndis_rm_hdr;
+	rndis->port.ul_max_pkts_per_xfer = rndis_ul_max_pkt_per_xfer;
+
+	rndis->port.func.name = "rndis";
+	rndis->port.func.strings = rndis_strings;
+	/* descriptors are per-instance copies */
+	rndis->port.func.bind = rndis_bind;
+	rndis->port.func.unbind = rndis_unbind;
+	rndis->port.func.set_alt = rndis_set_alt;
+	rndis->port.func.setup = rndis_setup;
+	rndis->port.func.disable = rndis_disable;
+
+	status = usb_add_function(c, &rndis->port.func);
+	if (status) {
+		kfree(rndis);
+fail:
+		rndis_exit();
+	}
+	return status;
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/f_serial.c b/marvell/linux/drivers/usb/gadget/function/f_serial.c
new file mode 100644
index 0000000..3f13fb6
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_serial.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_serial.c - generic USB serial function driver
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+
+#include "u_serial.h"
+#include "gadget_chips.h"
+
+
+/*
+ * This function packages a simple "generic serial" port with no real
+ * control mechanisms, just raw data transfer over two bulk endpoints.
+ *
+ * Because it's not standardized, this isn't as interoperable as the
+ * CDC ACM driver.  However, for many purposes it's just as functional
+ * if you can arrange appropriate host side drivers.
+ */
+
+struct f_gser {
+	struct gserial			port;
+	u8				data_id;
+	u8				port_num;
+};
+
+static inline struct f_gser *func_to_gser(struct usb_function *f)
+{
+	return container_of(f, struct f_gser, port.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor gser_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor gser_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor gser_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *gser_fs_function[] = {
+	(struct usb_descriptor_header *) &gser_interface_desc,
+	(struct usb_descriptor_header *) &gser_fs_in_desc,
+	(struct usb_descriptor_header *) &gser_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor gser_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor gser_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *gser_hs_function[] = {
+	(struct usb_descriptor_header *) &gser_interface_desc,
+	(struct usb_descriptor_header *) &gser_hs_in_desc,
+	(struct usb_descriptor_header *) &gser_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor gser_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor gser_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor gser_ss_bulk_comp_desc = {
+	.bLength =              sizeof gser_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *gser_ss_function[] = {
+	(struct usb_descriptor_header *) &gser_interface_desc,
+	(struct usb_descriptor_header *) &gser_ss_in_desc,
+	(struct usb_descriptor_header *) &gser_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &gser_ss_out_desc,
+	(struct usb_descriptor_header *) &gser_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string gser_string_defs[] = {
+	[0].s = "Generic Serial",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings gser_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		gser_string_defs,
+};
+
+static struct usb_gadget_strings *gser_strings[] = {
+	&gser_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_gser		*gser = func_to_gser(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (gser->port.in->enabled) {
+		dev_info(&cdev->gadget->dev,
+			"reset generic ttyGS%d\n", gser->port_num);
+		gserial_disconnect(&gser->port);
+	}
+	if (!gser->port.in->desc || !gser->port.out->desc) {
+		dev_info(&cdev->gadget->dev,
+			"activate generic ttyGS%d\n", gser->port_num);
+		if (config_ep_by_speed(cdev->gadget, f, gser->port.in) ||
+		    config_ep_by_speed(cdev->gadget, f, gser->port.out)) {
+			gser->port.in->desc = NULL;
+			gser->port.out->desc = NULL;
+			return -EINVAL;
+		}
+	}
+	gserial_connect(&gser->port, gser->port_num);
+	return 0;
+}
+
+static void gser_disable(struct usb_function *f)
+{
+	struct f_gser	*gser = func_to_gser(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	dev_info(&cdev->gadget->dev,
+		"generic ttyGS%d deactivated\n", gser->port_num);
+	gserial_disconnect(&gser->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* serial function driver setup/binding */
+
+static int gser_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_gser		*gser = func_to_gser(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* REVISIT might want instance-specific strings to help
+	 * distinguish instances ...
+	 */
+
+	/* maybe allocate device-global string ID */
+	if (gser_string_defs[0].id == 0) {
+		status = usb_string_id(c->cdev);
+		if (status < 0) {
+			dev_info(&cdev->gadget->dev, "usb_string_id error %d\n", status);
+			return status;
+		}
+		gser_string_defs[0].id = status;
+	}
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0) {
+		dev_info(&cdev->gadget->dev, "gser usb_interface_id error %d\n", status);
+		goto fail;
+	}
+	gser->data_id = status;
+	gser_interface_desc.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc);
+	if (!ep) {
+		dev_info(&cdev->gadget->dev, "gser usb_ep_autoconfig in error\n");
+		goto fail;
+	}
+	gser->port.in = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc);
+	if (!ep) {
+		dev_info(&cdev->gadget->dev, "gser usb_ep_autoconfig out error\n");
+		goto fail;
+	}
+
+	gser->port.out = ep;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	gser_hs_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress;
+	gser_hs_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress;
+
+	gser_ss_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress;
+	gser_ss_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, gser_fs_function, gser_hs_function,
+			gser_ss_function, gser_ss_function);
+	if (status) {
+		dev_info(&cdev->gadget->dev, "gser usb_assign_descriptors error %d\n", status);
+		goto fail;
+	}
+
+	dev_info(&cdev->gadget->dev, "generic ttyGSER%d: %s speed IN/%s OUT/%s\n",
+		gser->port_num,
+		gadget_is_superspeed(c->cdev->gadget) ? "super" :
+		gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+		gser->port.in->name, gser->port.out->name);
+	return 0;
+
+fail:
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_serial_opts,
+			    func_inst.group);
+}
+
+static void serial_attr_release(struct config_item *item)
+{
+	struct f_serial_opts *opts = to_f_serial_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations serial_item_ops = {
+	.release	= serial_attr_release,
+};
+
+static ssize_t f_serial_port_num_show(struct config_item *item, char *page)
+{
+	return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
+}
+
+CONFIGFS_ATTR_RO(f_serial_, port_num);
+
+static struct configfs_attribute *acm_attrs[] = {
+	&f_serial_attr_port_num,
+	NULL,
+};
+
+static const struct config_item_type serial_func_type = {
+	.ct_item_ops	= &serial_item_ops,
+	.ct_attrs	= acm_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void gser_free_inst(struct usb_function_instance *f)
+{
+	struct f_serial_opts *opts;
+
+	opts = container_of(f, struct f_serial_opts, func_inst);
+	gserial_free_line(opts->port_num);
+	kfree(opts);
+}
+
+static struct usb_function_instance *gser_alloc_inst(void)
+{
+	struct f_serial_opts *opts;
+	int ret;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	opts->func_inst.free_func_inst = gser_free_inst;
+	ret = gserial_alloc_line(&opts->port_num);
+	if (ret) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &serial_func_type);
+
+	return &opts->func_inst;
+}
+
+static void gser_free(struct usb_function *f)
+{
+	struct f_gser *serial;
+
+	serial = func_to_gser(f);
+	kfree(serial);
+}
+
+static void gser_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	usb_free_all_descriptors(f);
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		gser_string_defs[0].id = 0;
+#endif
+}
+
+static struct usb_function *gser_alloc(struct usb_function_instance *fi)
+{
+	struct f_gser	*gser;
+	struct f_serial_opts *opts;
+
+	/* allocate and initialize one new instance */
+	gser = kzalloc(sizeof(*gser), GFP_KERNEL);
+	if (!gser)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_serial_opts, func_inst);
+
+	gser->port_num = opts->port_num;
+
+	gser->port.func.name = "gser";
+	gser->port.func.strings = gser_strings;
+	gser->port.func.bind = gser_bind;
+	gser->port.func.unbind = gser_unbind;
+	gser->port.func.set_alt = gser_set_alt;
+	gser->port.func.disable = gser_disable;
+	gser->port.func.free_func = gser_free;
+
+	return &gser->port.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Al Borchers");
+MODULE_AUTHOR("David Brownell");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_sourcesink.c b/marvell/linux/drivers/usb/gadget/function/f_sourcesink.c
new file mode 100644
index 0000000..2c65a9b
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_sourcesink.c
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_sourcesink.c - USB peripheral source/sink configuration driver
+ *
+ * Copyright (C) 2003-2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/usb/composite.h>
+#include <linux/err.h>
+
+#include "g_zero.h"
+#include "u_f.h"
+
+/*
+ * SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral
+ * controller drivers.
+ *
+ * This just sinks bulk packets OUT to the peripheral and sources them IN
+ * to the host, optionally with specific data patterns for integrity tests.
+ * As such it supports basic functionality and load tests.
+ *
+ * In terms of control messaging, this supports all the standard requests
+ * plus two that support control-OUT tests.  If the optional "autoresume"
+ * mode is enabled, it provides good functional coverage for the "USBCV"
+ * test harness from USB-IF.
+ */
+struct f_sourcesink {
+	struct usb_function	function;
+
+	struct usb_ep		*in_ep;
+	struct usb_ep		*out_ep;
+	struct usb_ep		*iso_in_ep;
+	struct usb_ep		*iso_out_ep;
+	int			cur_alt;
+
+	unsigned pattern;
+	unsigned isoc_interval;
+	unsigned isoc_maxpacket;
+	unsigned isoc_mult;
+	unsigned isoc_maxburst;
+	unsigned buflen;
+	unsigned bulk_qlen;
+	unsigned iso_qlen;
+};
+
+static inline struct f_sourcesink *func_to_ss(struct usb_function *f)
+{
+	return container_of(f, struct f_sourcesink, function);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_interface_descriptor source_sink_intf_alt0 = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+	/* .iInterface		= DYNAMIC */
+};
+
+static struct usb_interface_descriptor source_sink_intf_alt1 = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	4,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+	/* .iInterface		= DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_iso_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1023),
+	.bInterval =		4,
+};
+
+static struct usb_endpoint_descriptor fs_iso_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1023),
+	.bInterval =		4,
+};
+
+static struct usb_descriptor_header *fs_source_sink_descs[] = {
+	(struct usb_descriptor_header *) &source_sink_intf_alt0,
+	(struct usb_descriptor_header *) &fs_sink_desc,
+	(struct usb_descriptor_header *) &fs_source_desc,
+	(struct usb_descriptor_header *) &source_sink_intf_alt1,
+#define FS_ALT_IFC_1_OFFSET	3
+	(struct usb_descriptor_header *) &fs_sink_desc,
+	(struct usb_descriptor_header *) &fs_source_desc,
+	(struct usb_descriptor_header *) &fs_iso_sink_desc,
+	(struct usb_descriptor_header *) &fs_iso_source_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_iso_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+	.bInterval =		4,
+};
+
+static struct usb_endpoint_descriptor hs_iso_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+	.bInterval =		4,
+};
+
+static struct usb_descriptor_header *hs_source_sink_descs[] = {
+	(struct usb_descriptor_header *) &source_sink_intf_alt0,
+	(struct usb_descriptor_header *) &hs_source_desc,
+	(struct usb_descriptor_header *) &hs_sink_desc,
+	(struct usb_descriptor_header *) &source_sink_intf_alt1,
+#define HS_ALT_IFC_1_OFFSET	3
+	(struct usb_descriptor_header *) &hs_source_desc,
+	(struct usb_descriptor_header *) &hs_sink_desc,
+	(struct usb_descriptor_header *) &hs_iso_source_desc,
+	(struct usb_descriptor_header *) &hs_iso_sink_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_source_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	0,
+};
+
+static struct usb_endpoint_descriptor ss_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_sink_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	0,
+};
+
+static struct usb_endpoint_descriptor ss_iso_source_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+	.bInterval =		4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_iso_source_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_iso_sink_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+	.bInterval =		4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_iso_sink_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	.bMaxBurst =		0,
+	.bmAttributes =		0,
+	.wBytesPerInterval =	cpu_to_le16(1024),
+};
+
+static struct usb_descriptor_header *ss_source_sink_descs[] = {
+	(struct usb_descriptor_header *) &source_sink_intf_alt0,
+	(struct usb_descriptor_header *) &ss_source_desc,
+	(struct usb_descriptor_header *) &ss_source_comp_desc,
+	(struct usb_descriptor_header *) &ss_sink_desc,
+	(struct usb_descriptor_header *) &ss_sink_comp_desc,
+	(struct usb_descriptor_header *) &source_sink_intf_alt1,
+#define SS_ALT_IFC_1_OFFSET	5
+	(struct usb_descriptor_header *) &ss_source_desc,
+	(struct usb_descriptor_header *) &ss_source_comp_desc,
+	(struct usb_descriptor_header *) &ss_sink_desc,
+	(struct usb_descriptor_header *) &ss_sink_comp_desc,
+	(struct usb_descriptor_header *) &ss_iso_source_desc,
+	(struct usb_descriptor_header *) &ss_iso_source_comp_desc,
+	(struct usb_descriptor_header *) &ss_iso_sink_desc,
+	(struct usb_descriptor_header *) &ss_iso_sink_comp_desc,
+	NULL,
+};
+
+/* function-specific strings: */
+
+static struct usb_string strings_sourcesink[] = {
+	[0].s = "source and sink data",
+	{  }			/* end of list */
+};
+
+static struct usb_gadget_strings stringtab_sourcesink = {
+	.language	= 0x0409,	/* en-us */
+	.strings	= strings_sourcesink,
+};
+
+static struct usb_gadget_strings *sourcesink_strings[] = {
+	&stringtab_sourcesink,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len)
+{
+	return alloc_ep_req(ep, len);
+}
+
+static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep)
+{
+	int			value;
+
+	value = usb_ep_disable(ep);
+	if (value < 0)
+		DBG(cdev, "disable %s --> %d\n", ep->name, value);
+}
+
+void disable_endpoints(struct usb_composite_dev *cdev,
+		struct usb_ep *in, struct usb_ep *out,
+		struct usb_ep *iso_in, struct usb_ep *iso_out)
+{
+	disable_ep(cdev, in);
+	disable_ep(cdev, out);
+	if (iso_in)
+		disable_ep(cdev, iso_in);
+	if (iso_out)
+		disable_ep(cdev, iso_out);
+}
+
+static int
+sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_sourcesink	*ss = func_to_ss(f);
+	int	id;
+	int ret;
+
+	/* allocate interface ID(s) */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	source_sink_intf_alt0.bInterfaceNumber = id;
+	source_sink_intf_alt1.bInterfaceNumber = id;
+
+	/* allocate bulk endpoints */
+	ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
+	if (!ss->in_ep) {
+autoconf_fail:
+		ERROR(cdev, "%s: can't autoconfigure on %s\n",
+			f->name, cdev->gadget->name);
+		return -ENODEV;
+	}
+
+	ss->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc);
+	if (!ss->out_ep)
+		goto autoconf_fail;
+
+	/* sanity check the isoc module parameters */
+	if (ss->isoc_interval < 1)
+		ss->isoc_interval = 1;
+	if (ss->isoc_interval > 16)
+		ss->isoc_interval = 16;
+	if (ss->isoc_mult > 2)
+		ss->isoc_mult = 2;
+	if (ss->isoc_maxburst > 15)
+		ss->isoc_maxburst = 15;
+
+	/* fill in the FS isoc descriptors from the module parameters */
+	fs_iso_source_desc.wMaxPacketSize = ss->isoc_maxpacket > 1023 ?
+						1023 : ss->isoc_maxpacket;
+	fs_iso_source_desc.bInterval = ss->isoc_interval;
+	fs_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket > 1023 ?
+						1023 : ss->isoc_maxpacket;
+	fs_iso_sink_desc.bInterval = ss->isoc_interval;
+
+	/* allocate iso endpoints */
+	ss->iso_in_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_source_desc);
+	if (!ss->iso_in_ep)
+		goto no_iso;
+
+	ss->iso_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_sink_desc);
+	if (!ss->iso_out_ep) {
+		usb_ep_autoconfig_release(ss->iso_in_ep);
+		ss->iso_in_ep = NULL;
+no_iso:
+		/*
+		 * We still want to work even if the UDC doesn't have isoc
+		 * endpoints, so null out the alt interface that contains
+		 * them and continue.
+		 */
+		fs_source_sink_descs[FS_ALT_IFC_1_OFFSET] = NULL;
+		hs_source_sink_descs[HS_ALT_IFC_1_OFFSET] = NULL;
+		ss_source_sink_descs[SS_ALT_IFC_1_OFFSET] = NULL;
+	}
+
+	if (ss->isoc_maxpacket > 1024)
+		ss->isoc_maxpacket = 1024;
+
+	/* support high speed hardware */
+	hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress;
+	hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress;
+
+	/*
+	 * Fill in the HS isoc descriptors from the module parameters.
+	 * We assume that the user knows what they are doing and won't
+	 * give parameters that their UDC doesn't support.
+	 */
+	hs_iso_source_desc.wMaxPacketSize = ss->isoc_maxpacket;
+	hs_iso_source_desc.wMaxPacketSize |= ss->isoc_mult << 11;
+	hs_iso_source_desc.bInterval = ss->isoc_interval;
+	hs_iso_source_desc.bEndpointAddress =
+		fs_iso_source_desc.bEndpointAddress;
+
+	hs_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket;
+	hs_iso_sink_desc.wMaxPacketSize |= ss->isoc_mult << 11;
+	hs_iso_sink_desc.bInterval = ss->isoc_interval;
+	hs_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress;
+
+	/* support super speed hardware */
+	ss_source_desc.bEndpointAddress =
+		fs_source_desc.bEndpointAddress;
+	ss_sink_desc.bEndpointAddress =
+		fs_sink_desc.bEndpointAddress;
+
+	/*
+	 * Fill in the SS isoc descriptors from the module parameters.
+	 * We assume that the user knows what they are doing and won't
+	 * give parameters that their UDC doesn't support.
+	 */
+	ss_iso_source_desc.wMaxPacketSize = ss->isoc_maxpacket;
+	ss_iso_source_desc.bInterval = ss->isoc_interval;
+	ss_iso_source_comp_desc.bmAttributes = ss->isoc_mult;
+	ss_iso_source_comp_desc.bMaxBurst = ss->isoc_maxburst;
+	ss_iso_source_comp_desc.wBytesPerInterval = ss->isoc_maxpacket *
+		(ss->isoc_mult + 1) * (ss->isoc_maxburst + 1);
+	ss_iso_source_desc.bEndpointAddress =
+		fs_iso_source_desc.bEndpointAddress;
+
+	ss_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket;
+	ss_iso_sink_desc.bInterval = ss->isoc_interval;
+	ss_iso_sink_comp_desc.bmAttributes = ss->isoc_mult;
+	ss_iso_sink_comp_desc.bMaxBurst = ss->isoc_maxburst;
+	ss_iso_sink_comp_desc.wBytesPerInterval = ss->isoc_maxpacket *
+		(ss->isoc_mult + 1) * (ss->isoc_maxburst + 1);
+	ss_iso_sink_desc.bEndpointAddress = fs_iso_sink_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, fs_source_sink_descs,
+			hs_source_sink_descs, ss_source_sink_descs,
+			ss_source_sink_descs);
+	if (ret)
+		return ret;
+
+	DBG(cdev, "%s speed %s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n",
+	    (gadget_is_superspeed(c->cdev->gadget) ? "super" :
+	     (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
+			f->name, ss->in_ep->name, ss->out_ep->name,
+			ss->iso_in_ep ? ss->iso_in_ep->name : "<none>",
+			ss->iso_out_ep ? ss->iso_out_ep->name : "<none>");
+	return 0;
+}
+
+static void
+sourcesink_free_func(struct usb_function *f)
+{
+	struct f_ss_opts *opts;
+
+	opts = container_of(f->fi, struct f_ss_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	usb_free_all_descriptors(f);
+	kfree(func_to_ss(f));
+}
+
+/* optionally require specific source/sink data patterns  */
+static int check_read_data(struct f_sourcesink *ss, struct usb_request *req)
+{
+	unsigned		i;
+	u8			*buf = req->buf;
+	struct usb_composite_dev *cdev = ss->function.config->cdev;
+	int max_packet_size = le16_to_cpu(ss->out_ep->desc->wMaxPacketSize);
+
+	if (ss->pattern == 2)
+		return 0;
+
+	for (i = 0; i < req->actual; i++, buf++) {
+		switch (ss->pattern) {
+
+		/* all-zeroes has no synchronization issues */
+		case 0:
+			if (*buf == 0)
+				continue;
+			break;
+
+		/* "mod63" stays in sync with short-terminated transfers,
+		 * OR otherwise when host and gadget agree on how large
+		 * each usb transfer request should be.  Resync is done
+		 * with set_interface or set_config.  (We *WANT* it to
+		 * get quickly out of sync if controllers or their drivers
+		 * stutter for any reason, including buffer duplication...)
+		 */
+		case 1:
+			if (*buf == (u8)((i % max_packet_size) % 63))
+				continue;
+			break;
+		}
+		ERROR(cdev, "bad OUT byte, buf[%d] = %d\n", i, *buf);
+		usb_ep_set_halt(ss->out_ep);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void reinit_write_data(struct usb_ep *ep, struct usb_request *req)
+{
+	unsigned	i;
+	u8		*buf = req->buf;
+	int max_packet_size = le16_to_cpu(ep->desc->wMaxPacketSize);
+	struct f_sourcesink *ss = ep->driver_data;
+
+	switch (ss->pattern) {
+	case 0:
+		memset(req->buf, 0, req->length);
+		break;
+	case 1:
+		for  (i = 0; i < req->length; i++)
+			*buf++ = (u8) ((i % max_packet_size) % 63);
+		break;
+	case 2:
+		break;
+	}
+}
+
+static void source_sink_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usb_composite_dev	*cdev;
+	struct f_sourcesink		*ss = ep->driver_data;
+	int				status = req->status;
+
+	/* driver_data will be null if ep has been disabled */
+	if (!ss)
+		return;
+
+	cdev = ss->function.config->cdev;
+
+	switch (status) {
+
+	case 0:				/* normal completion? */
+		if (ep == ss->out_ep) {
+			check_read_data(ss, req);
+			if (ss->pattern != 2)
+				memset(req->buf, 0x55, req->length);
+		}
+		break;
+
+	/* this endpoint is normally active while we're configured */
+	case -ECONNABORTED:		/* hardware forced ep reset */
+	case -ECONNRESET:		/* request dequeued */
+	case -ESHUTDOWN:		/* disconnect from host */
+		VDBG(cdev, "%s gone (%d), %d/%d\n", ep->name, status,
+				req->actual, req->length);
+		if (ep == ss->out_ep)
+			check_read_data(ss, req);
+		free_ep_req(ep, req);
+		return;
+
+	case -EOVERFLOW:		/* buffer overrun on read means that
+					 * we didn't provide a big enough
+					 * buffer.
+					 */
+	default:
+#if 1
+		DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name,
+				status, req->actual, req->length);
+#endif
+	case -EREMOTEIO:		/* short read */
+		break;
+	}
+
+	status = usb_ep_queue(ep, req, GFP_ATOMIC);
+	if (status) {
+		ERROR(cdev, "kill %s:  resubmit %d bytes --> %d\n",
+				ep->name, req->length, status);
+		usb_ep_set_halt(ep);
+		/* FIXME recover later ... somehow */
+	}
+}
+
+static int source_sink_start_ep(struct f_sourcesink *ss, bool is_in,
+		bool is_iso, int speed)
+{
+	struct usb_ep		*ep;
+	struct usb_request	*req;
+	int			i, size, qlen, status = 0;
+
+	if (is_iso) {
+		switch (speed) {
+		case USB_SPEED_SUPER_PLUS:
+		case USB_SPEED_SUPER:
+			size = ss->isoc_maxpacket *
+					(ss->isoc_mult + 1) *
+					(ss->isoc_maxburst + 1);
+			break;
+		case USB_SPEED_HIGH:
+			size = ss->isoc_maxpacket * (ss->isoc_mult + 1);
+			break;
+		default:
+			size = ss->isoc_maxpacket > 1023 ?
+					1023 : ss->isoc_maxpacket;
+			break;
+		}
+		ep = is_in ? ss->iso_in_ep : ss->iso_out_ep;
+		qlen = ss->iso_qlen;
+	} else {
+		ep = is_in ? ss->in_ep : ss->out_ep;
+		qlen = ss->bulk_qlen;
+		size = ss->buflen;
+	}
+
+	for (i = 0; i < qlen; i++) {
+		req = ss_alloc_ep_req(ep, size);
+		if (!req)
+			return -ENOMEM;
+
+		req->complete = source_sink_complete;
+		if (is_in)
+			reinit_write_data(ep, req);
+		else if (ss->pattern != 2)
+			memset(req->buf, 0x55, req->length);
+
+		status = usb_ep_queue(ep, req, GFP_ATOMIC);
+		if (status) {
+			struct usb_composite_dev	*cdev;
+
+			cdev = ss->function.config->cdev;
+			ERROR(cdev, "start %s%s %s --> %d\n",
+			      is_iso ? "ISO-" : "", is_in ? "IN" : "OUT",
+			      ep->name, status);
+			free_ep_req(ep, req);
+			return status;
+		}
+	}
+
+	return status;
+}
+
+static void disable_source_sink(struct f_sourcesink *ss)
+{
+	struct usb_composite_dev	*cdev;
+
+	cdev = ss->function.config->cdev;
+	disable_endpoints(cdev, ss->in_ep, ss->out_ep, ss->iso_in_ep,
+			ss->iso_out_ep);
+	VDBG(cdev, "%s disabled\n", ss->function.name);
+}
+
+static int
+enable_source_sink(struct usb_composite_dev *cdev, struct f_sourcesink *ss,
+		int alt)
+{
+	int					result = 0;
+	int					speed = cdev->gadget->speed;
+	struct usb_ep				*ep;
+
+	/* one bulk endpoint writes (sources) zeroes IN (to the host) */
+	ep = ss->in_ep;
+	result = config_ep_by_speed(cdev->gadget, &(ss->function), ep);
+	if (result)
+		return result;
+	result = usb_ep_enable(ep);
+	if (result < 0)
+		return result;
+	ep->driver_data = ss;
+
+	result = source_sink_start_ep(ss, true, false, speed);
+	if (result < 0) {
+fail:
+		ep = ss->in_ep;
+		usb_ep_disable(ep);
+		return result;
+	}
+
+	/* one bulk endpoint reads (sinks) anything OUT (from the host) */
+	ep = ss->out_ep;
+	result = config_ep_by_speed(cdev->gadget, &(ss->function), ep);
+	if (result)
+		goto fail;
+	result = usb_ep_enable(ep);
+	if (result < 0)
+		goto fail;
+	ep->driver_data = ss;
+
+	result = source_sink_start_ep(ss, false, false, speed);
+	if (result < 0) {
+fail2:
+		ep = ss->out_ep;
+		usb_ep_disable(ep);
+		goto fail;
+	}
+
+	if (alt == 0)
+		goto out;
+
+	/* one iso endpoint writes (sources) zeroes IN (to the host) */
+	ep = ss->iso_in_ep;
+	if (ep) {
+		result = config_ep_by_speed(cdev->gadget, &(ss->function), ep);
+		if (result)
+			goto fail2;
+		result = usb_ep_enable(ep);
+		if (result < 0)
+			goto fail2;
+		ep->driver_data = ss;
+
+		result = source_sink_start_ep(ss, true, true, speed);
+		if (result < 0) {
+fail3:
+			ep = ss->iso_in_ep;
+			if (ep)
+				usb_ep_disable(ep);
+			goto fail2;
+		}
+	}
+
+	/* one iso endpoint reads (sinks) anything OUT (from the host) */
+	ep = ss->iso_out_ep;
+	if (ep) {
+		result = config_ep_by_speed(cdev->gadget, &(ss->function), ep);
+		if (result)
+			goto fail3;
+		result = usb_ep_enable(ep);
+		if (result < 0)
+			goto fail3;
+		ep->driver_data = ss;
+
+		result = source_sink_start_ep(ss, false, true, speed);
+		if (result < 0) {
+			usb_ep_disable(ep);
+			goto fail3;
+		}
+	}
+out:
+	ss->cur_alt = alt;
+
+	DBG(cdev, "%s enabled, alt intf %d\n", ss->function.name, alt);
+	return result;
+}
+
+static int sourcesink_set_alt(struct usb_function *f,
+		unsigned intf, unsigned alt)
+{
+	struct f_sourcesink		*ss = func_to_ss(f);
+	struct usb_composite_dev	*cdev = f->config->cdev;
+
+	disable_source_sink(ss);
+	return enable_source_sink(cdev, ss, alt);
+}
+
+static int sourcesink_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_sourcesink		*ss = func_to_ss(f);
+
+	return ss->cur_alt;
+}
+
+static void sourcesink_disable(struct usb_function *f)
+{
+	struct f_sourcesink	*ss = func_to_ss(f);
+
+	disable_source_sink(ss);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int sourcesink_setup(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_configuration        *c = f->config;
+	struct usb_request	*req = c->cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	req->length = USB_COMP_EP0_BUFSIZ;
+
+	/* composite driver infrastructure handles everything except
+	 * the two control test requests.
+	 */
+	switch (ctrl->bRequest) {
+
+	/*
+	 * These are the same vendor-specific requests supported by
+	 * Intel's USB 2.0 compliance test devices.  We exceed that
+	 * device spec by allowing multiple-packet requests.
+	 *
+	 * NOTE:  the Control-OUT data stays in req->buf ... better
+	 * would be copying it into a scratch buffer, so that other
+	 * requests may safely intervene.
+	 */
+	case 0x5b:	/* control WRITE test -- fill the buffer */
+		if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR))
+			goto unknown;
+		if (w_value || w_index)
+			break;
+		/* just read that many bytes into the buffer */
+		if (w_length > req->length)
+			break;
+		value = w_length;
+		break;
+	case 0x5c:	/* control READ test -- return the buffer */
+		if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR))
+			goto unknown;
+		if (w_value || w_index)
+			break;
+		/* expect those bytes are still in the buffer; send back */
+		if (w_length > req->length)
+			break;
+		value = w_length;
+		break;
+
+	default:
+unknown:
+		VDBG(c->cdev,
+			"unknown control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		VDBG(c->cdev, "source/sink req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(c->cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(c->cdev, "source/sink response, err %d\n",
+					value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static struct usb_function *source_sink_alloc_func(
+		struct usb_function_instance *fi)
+{
+	struct f_sourcesink     *ss;
+	struct f_ss_opts	*ss_opts;
+
+	ss = kzalloc(sizeof(*ss), GFP_KERNEL);
+	if (!ss)
+		return ERR_PTR(-ENOMEM);
+
+	ss_opts =  container_of(fi, struct f_ss_opts, func_inst);
+
+	mutex_lock(&ss_opts->lock);
+	ss_opts->refcnt++;
+	mutex_unlock(&ss_opts->lock);
+
+	ss->pattern = ss_opts->pattern;
+	ss->isoc_interval = ss_opts->isoc_interval;
+	ss->isoc_maxpacket = ss_opts->isoc_maxpacket;
+	ss->isoc_mult = ss_opts->isoc_mult;
+	ss->isoc_maxburst = ss_opts->isoc_maxburst;
+	ss->buflen = ss_opts->bulk_buflen;
+	ss->bulk_qlen = ss_opts->bulk_qlen;
+	ss->iso_qlen = ss_opts->iso_qlen;
+
+	ss->function.name = "source/sink";
+	ss->function.bind = sourcesink_bind;
+	ss->function.set_alt = sourcesink_set_alt;
+	ss->function.get_alt = sourcesink_get_alt;
+	ss->function.disable = sourcesink_disable;
+	ss->function.setup = sourcesink_setup;
+	ss->function.strings = sourcesink_strings;
+
+	ss->function.free_func = sourcesink_free_func;
+
+	return &ss->function;
+}
+
+static inline struct f_ss_opts *to_f_ss_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_ss_opts,
+			    func_inst.group);
+}
+
+static void ss_attr_release(struct config_item *item)
+{
+	struct f_ss_opts *ss_opts = to_f_ss_opts(item);
+
+	usb_put_function_instance(&ss_opts->func_inst);
+}
+
+static struct configfs_item_operations ss_item_ops = {
+	.release		= ss_attr_release,
+};
+
+static ssize_t f_ss_opts_pattern_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->pattern);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_pattern_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u8 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		goto end;
+
+	if (num != 0 && num != 1 && num != 2) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	opts->pattern = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, pattern);
+
+static ssize_t f_ss_opts_isoc_interval_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->isoc_interval);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_isoc_interval_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u8 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		goto end;
+
+	if (num > 16) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	opts->isoc_interval = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, isoc_interval);
+
+static ssize_t f_ss_opts_isoc_maxpacket_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->isoc_maxpacket);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_isoc_maxpacket_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u16 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou16(page, 0, &num);
+	if (ret)
+		goto end;
+
+	if (num > 1024) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	opts->isoc_maxpacket = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, isoc_maxpacket);
+
+static ssize_t f_ss_opts_isoc_mult_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->isoc_mult);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_isoc_mult_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u8 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		goto end;
+
+	if (num > 2) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	opts->isoc_mult = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, isoc_mult);
+
+static ssize_t f_ss_opts_isoc_maxburst_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->isoc_maxburst);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_isoc_maxburst_store(struct config_item *item,
+				       const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u8 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		goto end;
+
+	if (num > 15) {
+		ret = -EINVAL;
+		goto end;
+	}
+
+	opts->isoc_maxburst = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, isoc_maxburst);
+
+static ssize_t f_ss_opts_bulk_buflen_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->bulk_buflen);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_bulk_buflen_store(struct config_item *item,
+					   const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u32 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->bulk_buflen = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, bulk_buflen);
+
+static ssize_t f_ss_opts_bulk_qlen_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->bulk_qlen);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_bulk_qlen_store(struct config_item *item,
+					   const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u32 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->bulk_qlen = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, bulk_qlen);
+
+static ssize_t f_ss_opts_iso_qlen_show(struct config_item *item, char *page)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->iso_qlen);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_ss_opts_iso_qlen_store(struct config_item *item,
+					   const char *page, size_t len)
+{
+	struct f_ss_opts *opts = to_f_ss_opts(item);
+	int ret;
+	u32 num;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = kstrtou32(page, 0, &num);
+	if (ret)
+		goto end;
+
+	opts->iso_qlen = num;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	return ret;
+}
+
+CONFIGFS_ATTR(f_ss_opts_, iso_qlen);
+
+static struct configfs_attribute *ss_attrs[] = {
+	&f_ss_opts_attr_pattern,
+	&f_ss_opts_attr_isoc_interval,
+	&f_ss_opts_attr_isoc_maxpacket,
+	&f_ss_opts_attr_isoc_mult,
+	&f_ss_opts_attr_isoc_maxburst,
+	&f_ss_opts_attr_bulk_buflen,
+	&f_ss_opts_attr_bulk_qlen,
+	&f_ss_opts_attr_iso_qlen,
+	NULL,
+};
+
+static const struct config_item_type ss_func_type = {
+	.ct_item_ops    = &ss_item_ops,
+	.ct_attrs	= ss_attrs,
+	.ct_owner       = THIS_MODULE,
+};
+
+static void source_sink_free_instance(struct usb_function_instance *fi)
+{
+	struct f_ss_opts *ss_opts;
+
+	ss_opts = container_of(fi, struct f_ss_opts, func_inst);
+	kfree(ss_opts);
+}
+
+static struct usb_function_instance *source_sink_alloc_inst(void)
+{
+	struct f_ss_opts *ss_opts;
+
+	ss_opts = kzalloc(sizeof(*ss_opts), GFP_KERNEL);
+	if (!ss_opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&ss_opts->lock);
+	ss_opts->func_inst.free_func_inst = source_sink_free_instance;
+	ss_opts->isoc_interval = GZERO_ISOC_INTERVAL;
+	ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET;
+	ss_opts->bulk_buflen = GZERO_BULK_BUFLEN;
+	ss_opts->bulk_qlen = GZERO_SS_BULK_QLEN;
+	ss_opts->iso_qlen = GZERO_SS_ISO_QLEN;
+
+	config_group_init_type_name(&ss_opts->func_inst.group, "",
+				    &ss_func_type);
+
+	return &ss_opts->func_inst;
+}
+DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst,
+		source_sink_alloc_func);
+
+static int __init sslb_modinit(void)
+{
+	int ret;
+
+	ret = usb_function_register(&SourceSinkusb_func);
+	if (ret)
+		return ret;
+	ret = lb_modinit();
+	if (ret)
+		usb_function_unregister(&SourceSinkusb_func);
+	return ret;
+}
+static void __exit sslb_modexit(void)
+{
+	usb_function_unregister(&SourceSinkusb_func);
+	lb_modexit();
+}
+module_init(sslb_modinit);
+module_exit(sslb_modexit);
+
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_subset.c b/marvell/linux/drivers/usb/gadget/function/f_subset.c
new file mode 100644
index 0000000..51c1cae
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_subset.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_subset.c -- "CDC Subset" Ethernet link function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+
+#include "u_ether.h"
+#include "u_ether_configfs.h"
+#include "u_gether.h"
+
+/*
+ * This function packages a simple "CDC Subset" Ethernet port with no real
+ * control mechanisms; just raw data transfer over two bulk endpoints.
+ * The data transfer model is exactly that of CDC Ethernet, which is
+ * why we call it the "CDC Subset".
+ *
+ * Because it's not standardized, this has some interoperability issues.
+ * They mostly relate to driver binding, since the data transfer model is
+ * so simple (CDC Ethernet).  The original versions of this protocol used
+ * specific product/vendor IDs:  byteswapped IDs for Digital Equipment's
+ * SA-1100 "Itsy" board, which could run Linux 2.4 kernels and supported
+ * daughtercards with USB peripheral connectors.  (It was used more often
+ * with other boards, using the Itsy identifiers.)  Linux hosts recognized
+ * this with CONFIG_USB_ARMLINUX; these devices have only one configuration
+ * and one interface.
+ *
+ * At some point, MCCI defined a (nonconformant) CDC MDLM variant called
+ * "SAFE", which happens to have a mode which is identical to the "CDC
+ * Subset" in terms of data transfer and lack of control model.  This was
+ * adopted by later Sharp Zaurus models, and by some other software which
+ * Linux hosts recognize with CONFIG_USB_NET_ZAURUS.
+ *
+ * Because Microsoft's RNDIS drivers are far from robust, we added a few
+ * descriptors to the CDC Subset code, making this code look like a SAFE
+ * implementation.  This lets you use MCCI's host side MS-Windows drivers
+ * if you get fed up with RNDIS.  It also makes it easier for composite
+ * drivers to work, since they can use class based binding instead of
+ * caring about specific product and vendor IDs.
+ */
+
+struct f_gether {
+	struct gether			port;
+
+	char				ethaddr[14];
+};
+
+static inline struct f_gether *func_to_geth(struct usb_function *f)
+{
+	return container_of(f, struct f_gether, port.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * "Simple" CDC-subset option is a simple vendor-neutral model that most
+ * full speed controllers can handle:  one interface, two bulk endpoints.
+ * To assist host side drivers, we fancy it up a bit, and add descriptors so
+ * some host side drivers will understand it as a "SAFE" variant.
+ *
+ * "SAFE" loosely follows CDC WMC MDLM, violating the spec in various ways.
+ * Data endpoints live in the control interface, there's no data interface.
+ * And it's not used to talk to a cell phone radio.
+ */
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor subset_data_intf = {
+	.bLength =		sizeof subset_data_intf,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =      USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_MDLM,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc mdlm_header_desc = {
+	.bLength =		sizeof mdlm_header_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_mdlm_desc mdlm_desc = {
+	.bLength =		sizeof mdlm_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_MDLM_TYPE,
+
+	.bcdVersion =		cpu_to_le16(0x0100),
+	.bGUID = {
+		0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6,
+		0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f,
+	},
+};
+
+/* since "usb_cdc_mdlm_detail_desc" is a variable length structure, we
+ * can't really use its struct.  All we do here is say that we're using
+ * the submode of "SAFE" which directly matches the CDC Subset.
+ */
+static u8 mdlm_detail_desc[] = {
+	6,
+	USB_DT_CS_INTERFACE,
+	USB_CDC_MDLM_DETAIL_TYPE,
+
+	0,	/* "SAFE" */
+	0,	/* network control capabilities (none) */
+	0,	/* network data capabilities ("raw" encapsulation) */
+};
+
+static struct usb_cdc_ether_desc ether_desc = {
+	.bLength =		sizeof ether_desc,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ETHERNET_TYPE,
+
+	/* this descriptor actually adds value, surprise! */
+	/* .iMACAddress = DYNAMIC */
+	.bmEthernetStatistics =	cpu_to_le32(0), /* no statistics */
+	.wMaxSegmentSize =	cpu_to_le16(ETH_FRAME_LEN),
+	.wNumberMCFilters =	cpu_to_le16(0),
+	.bNumberPowerFilters =	0,
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_subset_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_subset_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_eth_function[] = {
+	(struct usb_descriptor_header *) &subset_data_intf,
+	(struct usb_descriptor_header *) &mdlm_header_desc,
+	(struct usb_descriptor_header *) &mdlm_desc,
+	(struct usb_descriptor_header *) &mdlm_detail_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &fs_subset_in_desc,
+	(struct usb_descriptor_header *) &fs_subset_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_subset_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_subset_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *hs_eth_function[] = {
+	(struct usb_descriptor_header *) &subset_data_intf,
+	(struct usb_descriptor_header *) &mdlm_header_desc,
+	(struct usb_descriptor_header *) &mdlm_desc,
+	(struct usb_descriptor_header *) &mdlm_detail_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &hs_subset_in_desc,
+	(struct usb_descriptor_header *) &hs_subset_out_desc,
+	NULL,
+};
+
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_subset_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_subset_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_subset_bulk_comp_desc = {
+	.bLength =		sizeof ss_subset_bulk_comp_desc,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *ss_eth_function[] = {
+	(struct usb_descriptor_header *) &subset_data_intf,
+	(struct usb_descriptor_header *) &mdlm_header_desc,
+	(struct usb_descriptor_header *) &mdlm_desc,
+	(struct usb_descriptor_header *) &mdlm_detail_desc,
+	(struct usb_descriptor_header *) &ether_desc,
+	(struct usb_descriptor_header *) &ss_subset_in_desc,
+	(struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
+	(struct usb_descriptor_header *) &ss_subset_out_desc,
+	(struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string geth_string_defs[] = {
+	[0].s = "CDC Ethernet Subset/SAFE",
+	[1].s = "",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings geth_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		geth_string_defs,
+};
+
+static struct usb_gadget_strings *geth_strings[] = {
+	&geth_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_gether		*geth = func_to_geth(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct net_device	*net;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (geth->port.in_ep->enabled) {
+		DBG(cdev, "reset cdc subset\n");
+		gether_disconnect(&geth->port);
+	}
+
+	DBG(cdev, "init + activate cdc subset\n");
+	if (config_ep_by_speed(cdev->gadget, f, geth->port.in_ep) ||
+	    config_ep_by_speed(cdev->gadget, f, geth->port.out_ep)) {
+		geth->port.in_ep->desc = NULL;
+		geth->port.out_ep->desc = NULL;
+		return -EINVAL;
+	}
+
+	net = gether_connect(&geth->port);
+	return PTR_ERR_OR_ZERO(net);
+}
+
+static void geth_disable(struct usb_function *f)
+{
+	struct f_gether	*geth = func_to_geth(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "net deactivated\n");
+	gether_disconnect(&geth->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* serial function driver setup/binding */
+
+static int
+geth_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_gether		*geth = func_to_geth(f);
+	struct usb_string	*us;
+	int			status;
+	struct usb_ep		*ep;
+
+	struct f_gether_opts	*gether_opts;
+
+	gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
+
+	/*
+	 * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+	 * configurations are bound in sequence with list_for_each_entry,
+	 * in each configuration its functions are bound in sequence
+	 * with list_for_each_entry, so we assume no race condition
+	 * with regard to gether_opts->bound access
+	 */
+	if (!gether_opts->bound) {
+		mutex_lock(&gether_opts->lock);
+		gether_set_gadget(gether_opts->net, cdev->gadget);
+		status = gether_register_netdev(gether_opts->net);
+		mutex_unlock(&gether_opts->lock);
+		if (status)
+			return status;
+		gether_opts->bound = true;
+	}
+
+	us = usb_gstrings_attach(cdev, geth_strings,
+				 ARRAY_SIZE(geth_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+
+	subset_data_intf.iInterface = us[0].id;
+	ether_desc.iMACAddress = us[1].id;
+
+	/* allocate instance-specific interface IDs */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	subset_data_intf.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
+	if (!ep)
+		goto fail;
+	geth->port.in_ep = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
+	if (!ep)
+		goto fail;
+	geth->port.out_ep = ep;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	hs_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress;
+	hs_subset_out_desc.bEndpointAddress =
+		fs_subset_out_desc.bEndpointAddress;
+
+	ss_subset_in_desc.bEndpointAddress = fs_subset_in_desc.bEndpointAddress;
+	ss_subset_out_desc.bEndpointAddress =
+		fs_subset_out_desc.bEndpointAddress;
+
+	status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function,
+			ss_eth_function, ss_eth_function);
+	if (status)
+		goto fail;
+
+	/* NOTE:  all that is done without knowing or caring about
+	 * the network link ... which is unavailable to this code
+	 * until we're activated via set_alt().
+	 */
+
+	DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n",
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			geth->port.in_ep->name, geth->port.out_ep->name);
+	return 0;
+
+fail:
+	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+	return status;
+}
+
+static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_gether_opts,
+			    func_inst.group);
+}
+
+/* f_gether_item_ops */
+USB_ETHERNET_CONFIGFS_ITEM(gether);
+
+/* f_gether_opts_dev_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(gether);
+
+/* f_gether_opts_host_addr */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(gether);
+
+/* f_gether_opts_qmult */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(gether);
+
+/* f_gether_opts_ifname */
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(gether);
+
+static struct configfs_attribute *gether_attrs[] = {
+	&gether_opts_attr_dev_addr,
+	&gether_opts_attr_host_addr,
+	&gether_opts_attr_qmult,
+	&gether_opts_attr_ifname,
+	NULL,
+};
+
+static const struct config_item_type gether_func_type = {
+	.ct_item_ops	= &gether_item_ops,
+	.ct_attrs	= gether_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void geth_free_inst(struct usb_function_instance *f)
+{
+	struct f_gether_opts *opts;
+
+	opts = container_of(f, struct f_gether_opts, func_inst);
+	if (opts->bound)
+		gether_cleanup(netdev_priv(opts->net));
+	else
+		free_netdev(opts->net);
+	kfree(opts);
+}
+
+static struct usb_function_instance *geth_alloc_inst(void)
+{
+	struct f_gether_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = geth_free_inst;
+	opts->net = gether_setup_default();
+	if (IS_ERR(opts->net)) {
+		struct net_device *net = opts->net;
+		kfree(opts);
+		return ERR_CAST(net);
+	}
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &gether_func_type);
+
+	return &opts->func_inst;
+}
+
+static void geth_free(struct usb_function *f)
+{
+	struct f_gether *eth;
+
+	eth = func_to_geth(f);
+	kfree(eth);
+}
+
+static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	geth_string_defs[0].id = 0;
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *geth_alloc(struct usb_function_instance *fi)
+{
+	struct f_gether	*geth;
+	struct f_gether_opts *opts;
+	int status;
+
+	/* allocate and initialize one new instance */
+	geth = kzalloc(sizeof(*geth), GFP_KERNEL);
+	if (!geth)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_gether_opts, func_inst);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+	/* export host's Ethernet address in CDC format */
+	status = gether_get_host_addr_cdc(opts->net, geth->ethaddr,
+					  sizeof(geth->ethaddr));
+	if (status < 12) {
+		kfree(geth);
+		mutex_unlock(&opts->lock);
+		return ERR_PTR(-EINVAL);
+	}
+	geth_string_defs[1].s = geth->ethaddr;
+
+	geth->port.ioport = netdev_priv(opts->net);
+	mutex_unlock(&opts->lock);
+	geth->port.cdc_filter = DEFAULT_FILTER;
+
+	geth->port.func.name = "cdc_subset";
+	geth->port.func.bind = geth_bind;
+	geth->port.func.unbind = geth_unbind;
+	geth->port.func.set_alt = geth_set_alt;
+	geth->port.func.disable = geth_disable;
+	geth->port.func.free_func = geth_free;
+
+	return &geth->port.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(geth, geth_alloc_inst, geth_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Brownell");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_tcm.c b/marvell/linux/drivers/usb/gadget/function/f_tcm.c
new file mode 100644
index 0000000..41a10bc
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_tcm.c
@@ -0,0 +1,2340 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Target based USB-Gadget
+ *
+ * UAS protocol handling, target callbacks, configfs handling,
+ * BBB (USB Mass Storage Class Bulk-Only (BBB) and Transport protocol handling.
+ *
+ * Author: Sebastian Andrzej Siewior <bigeasy at linutronix dot de>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/configfs.h>
+#include <linux/ctype.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/storage.h>
+#include <scsi/scsi_tcq.h>
+#include <target/target_core_base.h>
+#include <target/target_core_fabric.h>
+#include <asm/unaligned.h>
+
+#include "tcm.h"
+#include "u_tcm.h"
+#include "configfs.h"
+
+#define TPG_INSTANCES		1
+
+struct tpg_instance {
+	struct usb_function_instance	*func_inst;
+	struct usbg_tpg			*tpg;
+};
+
+static struct tpg_instance tpg_instances[TPG_INSTANCES];
+
+static DEFINE_MUTEX(tpg_instances_lock);
+
+static inline struct f_uas *to_f_uas(struct usb_function *f)
+{
+	return container_of(f, struct f_uas, function);
+}
+
+/* Start bot.c code */
+
+static int bot_enqueue_cmd_cbw(struct f_uas *fu)
+{
+	int ret;
+
+	if (fu->flags & USBG_BOT_CMD_PEND)
+		return 0;
+
+	ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC);
+	if (!ret)
+		fu->flags |= USBG_BOT_CMD_PEND;
+	return ret;
+}
+
+static void bot_status_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usbg_cmd *cmd = req->context;
+	struct f_uas *fu = cmd->fu;
+
+	transport_generic_free_cmd(&cmd->se_cmd, 0);
+	if (req->status < 0) {
+		pr_err("ERR %s(%d)\n", __func__, __LINE__);
+		return;
+	}
+
+	/* CSW completed, wait for next CBW */
+	bot_enqueue_cmd_cbw(fu);
+}
+
+static void bot_enqueue_sense_code(struct f_uas *fu, struct usbg_cmd *cmd)
+{
+	struct bulk_cs_wrap *csw = &fu->bot_status.csw;
+	int ret;
+	unsigned int csw_stat;
+
+	csw_stat = cmd->csw_code;
+	csw->Tag = cmd->bot_tag;
+	csw->Status = csw_stat;
+	fu->bot_status.req->context = cmd;
+	ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_ATOMIC);
+	if (ret)
+		pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret);
+}
+
+static void bot_err_compl(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usbg_cmd *cmd = req->context;
+	struct f_uas *fu = cmd->fu;
+
+	if (req->status < 0)
+		pr_err("ERR %s(%d)\n", __func__, __LINE__);
+
+	if (cmd->data_len) {
+		if (cmd->data_len > ep->maxpacket) {
+			req->length = ep->maxpacket;
+			cmd->data_len -= ep->maxpacket;
+		} else {
+			req->length = cmd->data_len;
+			cmd->data_len = 0;
+		}
+
+		usb_ep_queue(ep, req, GFP_ATOMIC);
+		return;
+	}
+	bot_enqueue_sense_code(fu, cmd);
+}
+
+static void bot_send_bad_status(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct bulk_cs_wrap *csw = &fu->bot_status.csw;
+	struct usb_request *req;
+	struct usb_ep *ep;
+
+	csw->Residue = cpu_to_le32(cmd->data_len);
+
+	if (cmd->data_len) {
+		if (cmd->is_read) {
+			ep = fu->ep_in;
+			req = fu->bot_req_in;
+		} else {
+			ep = fu->ep_out;
+			req = fu->bot_req_out;
+		}
+
+		if (cmd->data_len > fu->ep_in->maxpacket) {
+			req->length = ep->maxpacket;
+			cmd->data_len -= ep->maxpacket;
+		} else {
+			req->length = cmd->data_len;
+			cmd->data_len = 0;
+		}
+		req->complete = bot_err_compl;
+		req->context = cmd;
+		req->buf = fu->cmd.buf;
+		usb_ep_queue(ep, req, GFP_KERNEL);
+	} else {
+		bot_enqueue_sense_code(fu, cmd);
+	}
+}
+
+static int bot_send_status(struct usbg_cmd *cmd, bool moved_data)
+{
+	struct f_uas *fu = cmd->fu;
+	struct bulk_cs_wrap *csw = &fu->bot_status.csw;
+	int ret;
+
+	if (cmd->se_cmd.scsi_status == SAM_STAT_GOOD) {
+		if (!moved_data && cmd->data_len) {
+			/*
+			 * the host wants to move data, we don't. Fill / empty
+			 * the pipe and then send the csw with reside set.
+			 */
+			cmd->csw_code = US_BULK_STAT_OK;
+			bot_send_bad_status(cmd);
+			return 0;
+		}
+
+		csw->Tag = cmd->bot_tag;
+		csw->Residue = cpu_to_le32(0);
+		csw->Status = US_BULK_STAT_OK;
+		fu->bot_status.req->context = cmd;
+
+		ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_KERNEL);
+		if (ret)
+			pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret);
+	} else {
+		cmd->csw_code = US_BULK_STAT_FAIL;
+		bot_send_bad_status(cmd);
+	}
+	return 0;
+}
+
+/*
+ * Called after command (no data transfer) or after the write (to device)
+ * operation is completed
+ */
+static int bot_send_status_response(struct usbg_cmd *cmd)
+{
+	bool moved_data = false;
+
+	if (!cmd->is_read)
+		moved_data = true;
+	return bot_send_status(cmd, moved_data);
+}
+
+/* Read request completed, now we have to send the CSW */
+static void bot_read_compl(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usbg_cmd *cmd = req->context;
+
+	if (req->status < 0)
+		pr_err("ERR %s(%d)\n", __func__, __LINE__);
+
+	bot_send_status(cmd, true);
+}
+
+static int bot_send_read_response(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct usb_gadget *gadget = fuas_to_gadget(fu);
+	int ret;
+
+	if (!cmd->data_len) {
+		cmd->csw_code = US_BULK_STAT_PHASE;
+		bot_send_bad_status(cmd);
+		return 0;
+	}
+
+	if (!gadget->sg_supported) {
+		cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC);
+		if (!cmd->data_buf)
+			return -ENOMEM;
+
+		sg_copy_to_buffer(se_cmd->t_data_sg,
+				se_cmd->t_data_nents,
+				cmd->data_buf,
+				se_cmd->data_length);
+
+		fu->bot_req_in->buf = cmd->data_buf;
+	} else {
+		fu->bot_req_in->buf = NULL;
+		fu->bot_req_in->num_sgs = se_cmd->t_data_nents;
+		fu->bot_req_in->sg = se_cmd->t_data_sg;
+	}
+
+	fu->bot_req_in->complete = bot_read_compl;
+	fu->bot_req_in->length = se_cmd->data_length;
+	fu->bot_req_in->context = cmd;
+	ret = usb_ep_queue(fu->ep_in, fu->bot_req_in, GFP_ATOMIC);
+	if (ret)
+		pr_err("%s(%d)\n", __func__, __LINE__);
+	return 0;
+}
+
+static void usbg_data_write_cmpl(struct usb_ep *, struct usb_request *);
+static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *);
+
+static int bot_send_write_request(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct usb_gadget *gadget = fuas_to_gadget(fu);
+	int ret;
+
+	init_completion(&cmd->write_complete);
+	cmd->fu = fu;
+
+	if (!cmd->data_len) {
+		cmd->csw_code = US_BULK_STAT_PHASE;
+		return -EINVAL;
+	}
+
+	if (!gadget->sg_supported) {
+		cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL);
+		if (!cmd->data_buf)
+			return -ENOMEM;
+
+		fu->bot_req_out->buf = cmd->data_buf;
+	} else {
+		fu->bot_req_out->buf = NULL;
+		fu->bot_req_out->num_sgs = se_cmd->t_data_nents;
+		fu->bot_req_out->sg = se_cmd->t_data_sg;
+	}
+
+	fu->bot_req_out->complete = usbg_data_write_cmpl;
+	fu->bot_req_out->length = se_cmd->data_length;
+	fu->bot_req_out->context = cmd;
+
+	ret = usbg_prepare_w_request(cmd, fu->bot_req_out);
+	if (ret)
+		goto cleanup;
+	ret = usb_ep_queue(fu->ep_out, fu->bot_req_out, GFP_KERNEL);
+	if (ret)
+		pr_err("%s(%d)\n", __func__, __LINE__);
+
+	wait_for_completion(&cmd->write_complete);
+	target_execute_cmd(se_cmd);
+cleanup:
+	return ret;
+}
+
+static int bot_submit_command(struct f_uas *, void *, unsigned int);
+
+static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_uas *fu = req->context;
+	int ret;
+
+	fu->flags &= ~USBG_BOT_CMD_PEND;
+
+	if (req->status < 0)
+		return;
+
+	ret = bot_submit_command(fu, req->buf, req->actual);
+	if (ret)
+		pr_err("%s(%d): %d\n", __func__, __LINE__, ret);
+}
+
+static int bot_prepare_reqs(struct f_uas *fu)
+{
+	int ret;
+
+	fu->bot_req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL);
+	if (!fu->bot_req_in)
+		goto err;
+
+	fu->bot_req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL);
+	if (!fu->bot_req_out)
+		goto err_out;
+
+	fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL);
+	if (!fu->cmd.req)
+		goto err_cmd;
+
+	fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL);
+	if (!fu->bot_status.req)
+		goto err_sts;
+
+	fu->bot_status.req->buf = &fu->bot_status.csw;
+	fu->bot_status.req->length = US_BULK_CS_WRAP_LEN;
+	fu->bot_status.req->complete = bot_status_complete;
+	fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN);
+
+	fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL);
+	if (!fu->cmd.buf)
+		goto err_buf;
+
+	fu->cmd.req->complete = bot_cmd_complete;
+	fu->cmd.req->buf = fu->cmd.buf;
+	fu->cmd.req->length = fu->ep_out->maxpacket;
+	fu->cmd.req->context = fu;
+
+	ret = bot_enqueue_cmd_cbw(fu);
+	if (ret)
+		goto err_queue;
+	return 0;
+err_queue:
+	kfree(fu->cmd.buf);
+	fu->cmd.buf = NULL;
+err_buf:
+	usb_ep_free_request(fu->ep_in, fu->bot_status.req);
+err_sts:
+	usb_ep_free_request(fu->ep_out, fu->cmd.req);
+	fu->cmd.req = NULL;
+err_cmd:
+	usb_ep_free_request(fu->ep_out, fu->bot_req_out);
+	fu->bot_req_out = NULL;
+err_out:
+	usb_ep_free_request(fu->ep_in, fu->bot_req_in);
+	fu->bot_req_in = NULL;
+err:
+	pr_err("BOT: endpoint setup failed\n");
+	return -ENOMEM;
+}
+
+static void bot_cleanup_old_alt(struct f_uas *fu)
+{
+	if (!(fu->flags & USBG_ENABLED))
+		return;
+
+	usb_ep_disable(fu->ep_in);
+	usb_ep_disable(fu->ep_out);
+
+	if (!fu->bot_req_in)
+		return;
+
+	usb_ep_free_request(fu->ep_in, fu->bot_req_in);
+	usb_ep_free_request(fu->ep_out, fu->bot_req_out);
+	usb_ep_free_request(fu->ep_out, fu->cmd.req);
+	usb_ep_free_request(fu->ep_in, fu->bot_status.req);
+
+	kfree(fu->cmd.buf);
+
+	fu->bot_req_in = NULL;
+	fu->bot_req_out = NULL;
+	fu->cmd.req = NULL;
+	fu->bot_status.req = NULL;
+	fu->cmd.buf = NULL;
+}
+
+static void bot_set_alt(struct f_uas *fu)
+{
+	struct usb_function *f = &fu->function;
+	struct usb_gadget *gadget = f->config->cdev->gadget;
+	int ret;
+
+	fu->flags = USBG_IS_BOT;
+
+	config_ep_by_speed(gadget, f, fu->ep_in);
+	ret = usb_ep_enable(fu->ep_in);
+	if (ret)
+		goto err_b_in;
+
+	config_ep_by_speed(gadget, f, fu->ep_out);
+	ret = usb_ep_enable(fu->ep_out);
+	if (ret)
+		goto err_b_out;
+
+	ret = bot_prepare_reqs(fu);
+	if (ret)
+		goto err_wq;
+	fu->flags |= USBG_ENABLED;
+	pr_info("Using the BOT protocol\n");
+	return;
+err_wq:
+	usb_ep_disable(fu->ep_out);
+err_b_out:
+	usb_ep_disable(fu->ep_in);
+err_b_in:
+	fu->flags = USBG_IS_BOT;
+}
+
+static int usbg_bot_setup(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct f_uas *fu = to_f_uas(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u16 w_length = le16_to_cpu(ctrl->wLength);
+	int luns;
+	u8 *ret_lun;
+
+	switch (ctrl->bRequest) {
+	case US_BULK_GET_MAX_LUN:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_CLASS |
+					USB_RECIP_INTERFACE))
+			return -ENOTSUPP;
+
+		if (w_length < 1)
+			return -EINVAL;
+		if (w_value != 0)
+			return -EINVAL;
+		luns = atomic_read(&fu->tpg->tpg_port_count);
+		if (!luns) {
+			pr_err("No LUNs configured?\n");
+			return -EINVAL;
+		}
+		/*
+		 * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be
+		 * accessed. The upper limit is 0xf
+		 */
+		luns--;
+		if (luns > 0xf) {
+			pr_info_once("Limiting the number of luns to 16\n");
+			luns = 0xf;
+		}
+		ret_lun = cdev->req->buf;
+		*ret_lun = luns;
+		cdev->req->length = 1;
+		return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+
+	case US_BULK_RESET_REQUEST:
+		/* XXX maybe we should remove previous requests for IN + OUT */
+		bot_enqueue_cmd_cbw(fu);
+		return 0;
+	}
+	return -ENOTSUPP;
+}
+
+/* Start uas.c code */
+
+static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream)
+{
+	/* We have either all three allocated or none */
+	if (!stream->req_in)
+		return;
+
+	usb_ep_free_request(fu->ep_in, stream->req_in);
+	usb_ep_free_request(fu->ep_out, stream->req_out);
+	usb_ep_free_request(fu->ep_status, stream->req_status);
+
+	stream->req_in = NULL;
+	stream->req_out = NULL;
+	stream->req_status = NULL;
+}
+
+static void uasp_free_cmdreq(struct f_uas *fu)
+{
+	usb_ep_free_request(fu->ep_cmd, fu->cmd.req);
+	kfree(fu->cmd.buf);
+	fu->cmd.req = NULL;
+	fu->cmd.buf = NULL;
+}
+
+static void uasp_cleanup_old_alt(struct f_uas *fu)
+{
+	int i;
+
+	if (!(fu->flags & USBG_ENABLED))
+		return;
+
+	usb_ep_disable(fu->ep_in);
+	usb_ep_disable(fu->ep_out);
+	usb_ep_disable(fu->ep_status);
+	usb_ep_disable(fu->ep_cmd);
+
+	for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++)
+		uasp_cleanup_one_stream(fu, &fu->stream[i]);
+	uasp_free_cmdreq(fu);
+}
+
+static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req);
+
+static int uasp_prepare_r_request(struct usbg_cmd *cmd)
+{
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct f_uas *fu = cmd->fu;
+	struct usb_gadget *gadget = fuas_to_gadget(fu);
+	struct uas_stream *stream = cmd->stream;
+
+	if (!gadget->sg_supported) {
+		cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC);
+		if (!cmd->data_buf)
+			return -ENOMEM;
+
+		sg_copy_to_buffer(se_cmd->t_data_sg,
+				se_cmd->t_data_nents,
+				cmd->data_buf,
+				se_cmd->data_length);
+
+		stream->req_in->buf = cmd->data_buf;
+	} else {
+		stream->req_in->buf = NULL;
+		stream->req_in->num_sgs = se_cmd->t_data_nents;
+		stream->req_in->sg = se_cmd->t_data_sg;
+	}
+
+	stream->req_in->complete = uasp_status_data_cmpl;
+	stream->req_in->length = se_cmd->data_length;
+	stream->req_in->context = cmd;
+
+	cmd->state = UASP_SEND_STATUS;
+	return 0;
+}
+
+static void uasp_prepare_status(struct usbg_cmd *cmd)
+{
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct sense_iu *iu = &cmd->sense_iu;
+	struct uas_stream *stream = cmd->stream;
+
+	cmd->state = UASP_QUEUE_COMMAND;
+	iu->iu_id = IU_ID_STATUS;
+	iu->tag = cpu_to_be16(cmd->tag);
+
+	/*
+	 * iu->status_qual = cpu_to_be16(STATUS QUALIFIER SAM-4. Where R U?);
+	 */
+	iu->len = cpu_to_be16(se_cmd->scsi_sense_length);
+	iu->status = se_cmd->scsi_status;
+	stream->req_status->context = cmd;
+	stream->req_status->length = se_cmd->scsi_sense_length + 16;
+	stream->req_status->buf = iu;
+	stream->req_status->complete = uasp_status_data_cmpl;
+}
+
+static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usbg_cmd *cmd = req->context;
+	struct uas_stream *stream = cmd->stream;
+	struct f_uas *fu = cmd->fu;
+	int ret;
+
+	if (req->status < 0)
+		goto cleanup;
+
+	switch (cmd->state) {
+	case UASP_SEND_DATA:
+		ret = uasp_prepare_r_request(cmd);
+		if (ret)
+			goto cleanup;
+		ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d) => %d\n", __func__, __LINE__, ret);
+		break;
+
+	case UASP_RECEIVE_DATA:
+		ret = usbg_prepare_w_request(cmd, stream->req_out);
+		if (ret)
+			goto cleanup;
+		ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d) => %d\n", __func__, __LINE__, ret);
+		break;
+
+	case UASP_SEND_STATUS:
+		uasp_prepare_status(cmd);
+		ret = usb_ep_queue(fu->ep_status, stream->req_status,
+				GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d) => %d\n", __func__, __LINE__, ret);
+		break;
+
+	case UASP_QUEUE_COMMAND:
+		transport_generic_free_cmd(&cmd->se_cmd, 0);
+		usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
+		break;
+
+	default:
+		BUG();
+	}
+	return;
+
+cleanup:
+	transport_generic_free_cmd(&cmd->se_cmd, 0);
+}
+
+static int uasp_send_status_response(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct uas_stream *stream = cmd->stream;
+	struct sense_iu *iu = &cmd->sense_iu;
+
+	iu->tag = cpu_to_be16(cmd->tag);
+	stream->req_status->complete = uasp_status_data_cmpl;
+	stream->req_status->context = cmd;
+	cmd->fu = fu;
+	uasp_prepare_status(cmd);
+	return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC);
+}
+
+static int uasp_send_read_response(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct uas_stream *stream = cmd->stream;
+	struct sense_iu *iu = &cmd->sense_iu;
+	int ret;
+
+	cmd->fu = fu;
+
+	iu->tag = cpu_to_be16(cmd->tag);
+	if (fu->flags & USBG_USE_STREAMS) {
+
+		ret = uasp_prepare_r_request(cmd);
+		if (ret)
+			goto out;
+		ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC);
+		if (ret) {
+			pr_err("%s(%d) => %d\n", __func__, __LINE__, ret);
+			kfree(cmd->data_buf);
+			cmd->data_buf = NULL;
+		}
+
+	} else {
+
+		iu->iu_id = IU_ID_READ_READY;
+		iu->tag = cpu_to_be16(cmd->tag);
+
+		stream->req_status->complete = uasp_status_data_cmpl;
+		stream->req_status->context = cmd;
+
+		cmd->state = UASP_SEND_DATA;
+		stream->req_status->buf = iu;
+		stream->req_status->length = sizeof(struct iu);
+
+		ret = usb_ep_queue(fu->ep_status, stream->req_status,
+				GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d) => %d\n", __func__, __LINE__, ret);
+	}
+out:
+	return ret;
+}
+
+static int uasp_send_write_request(struct usbg_cmd *cmd)
+{
+	struct f_uas *fu = cmd->fu;
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct uas_stream *stream = cmd->stream;
+	struct sense_iu *iu = &cmd->sense_iu;
+	int ret;
+
+	init_completion(&cmd->write_complete);
+	cmd->fu = fu;
+
+	iu->tag = cpu_to_be16(cmd->tag);
+
+	if (fu->flags & USBG_USE_STREAMS) {
+
+		ret = usbg_prepare_w_request(cmd, stream->req_out);
+		if (ret)
+			goto cleanup;
+		ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d)\n", __func__, __LINE__);
+
+	} else {
+
+		iu->iu_id = IU_ID_WRITE_READY;
+		iu->tag = cpu_to_be16(cmd->tag);
+
+		stream->req_status->complete = uasp_status_data_cmpl;
+		stream->req_status->context = cmd;
+
+		cmd->state = UASP_RECEIVE_DATA;
+		stream->req_status->buf = iu;
+		stream->req_status->length = sizeof(struct iu);
+
+		ret = usb_ep_queue(fu->ep_status, stream->req_status,
+				GFP_ATOMIC);
+		if (ret)
+			pr_err("%s(%d)\n", __func__, __LINE__);
+	}
+
+	wait_for_completion(&cmd->write_complete);
+	target_execute_cmd(se_cmd);
+cleanup:
+	return ret;
+}
+
+static int usbg_submit_command(struct f_uas *, void *, unsigned int);
+
+static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_uas *fu = req->context;
+	int ret;
+
+	if (req->status < 0)
+		return;
+
+	ret = usbg_submit_command(fu, req->buf, req->actual);
+	/*
+	 * Once we tune for performance enqueue the command req here again so
+	 * we can receive a second command while we processing this one. Pay
+	 * attention to properly sync STAUS endpoint with DATA IN + OUT so you
+	 * don't break HS.
+	 */
+	if (!ret)
+		return;
+	usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
+}
+
+static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream)
+{
+	stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL);
+	if (!stream->req_in)
+		goto out;
+
+	stream->req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL);
+	if (!stream->req_out)
+		goto err_out;
+
+	stream->req_status = usb_ep_alloc_request(fu->ep_status, GFP_KERNEL);
+	if (!stream->req_status)
+		goto err_sts;
+
+	return 0;
+
+err_sts:
+	usb_ep_free_request(fu->ep_out, stream->req_out);
+	stream->req_out = NULL;
+err_out:
+	usb_ep_free_request(fu->ep_in, stream->req_in);
+	stream->req_in = NULL;
+out:
+	return -ENOMEM;
+}
+
+static int uasp_alloc_cmd(struct f_uas *fu)
+{
+	fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL);
+	if (!fu->cmd.req)
+		goto err;
+
+	fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL);
+	if (!fu->cmd.buf)
+		goto err_buf;
+
+	fu->cmd.req->complete = uasp_cmd_complete;
+	fu->cmd.req->buf = fu->cmd.buf;
+	fu->cmd.req->length = fu->ep_cmd->maxpacket;
+	fu->cmd.req->context = fu;
+	return 0;
+
+err_buf:
+	usb_ep_free_request(fu->ep_cmd, fu->cmd.req);
+err:
+	return -ENOMEM;
+}
+
+static void uasp_setup_stream_res(struct f_uas *fu, int max_streams)
+{
+	int i;
+
+	for (i = 0; i < max_streams; i++) {
+		struct uas_stream *s = &fu->stream[i];
+
+		s->req_in->stream_id = i + 1;
+		s->req_out->stream_id = i + 1;
+		s->req_status->stream_id = i + 1;
+	}
+}
+
+static int uasp_prepare_reqs(struct f_uas *fu)
+{
+	int ret;
+	int i;
+	int max_streams;
+
+	if (fu->flags & USBG_USE_STREAMS)
+		max_streams = UASP_SS_EP_COMP_NUM_STREAMS;
+	else
+		max_streams = 1;
+
+	for (i = 0; i < max_streams; i++) {
+		ret = uasp_alloc_stream_res(fu, &fu->stream[i]);
+		if (ret)
+			goto err_cleanup;
+	}
+
+	ret = uasp_alloc_cmd(fu);
+	if (ret)
+		goto err_free_stream;
+	uasp_setup_stream_res(fu, max_streams);
+
+	ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
+	if (ret)
+		goto err_free_stream;
+
+	return 0;
+
+err_free_stream:
+	uasp_free_cmdreq(fu);
+
+err_cleanup:
+	if (i) {
+		do {
+			uasp_cleanup_one_stream(fu, &fu->stream[i - 1]);
+			i--;
+		} while (i);
+	}
+	pr_err("UASP: endpoint setup failed\n");
+	return ret;
+}
+
+static void uasp_set_alt(struct f_uas *fu)
+{
+	struct usb_function *f = &fu->function;
+	struct usb_gadget *gadget = f->config->cdev->gadget;
+	int ret;
+
+	fu->flags = USBG_IS_UAS;
+
+	if (gadget->speed == USB_SPEED_SUPER)
+		fu->flags |= USBG_USE_STREAMS;
+
+	config_ep_by_speed(gadget, f, fu->ep_in);
+	ret = usb_ep_enable(fu->ep_in);
+	if (ret)
+		goto err_b_in;
+
+	config_ep_by_speed(gadget, f, fu->ep_out);
+	ret = usb_ep_enable(fu->ep_out);
+	if (ret)
+		goto err_b_out;
+
+	config_ep_by_speed(gadget, f, fu->ep_cmd);
+	ret = usb_ep_enable(fu->ep_cmd);
+	if (ret)
+		goto err_cmd;
+	config_ep_by_speed(gadget, f, fu->ep_status);
+	ret = usb_ep_enable(fu->ep_status);
+	if (ret)
+		goto err_status;
+
+	ret = uasp_prepare_reqs(fu);
+	if (ret)
+		goto err_wq;
+	fu->flags |= USBG_ENABLED;
+
+	pr_info("Using the UAS protocol\n");
+	return;
+err_wq:
+	usb_ep_disable(fu->ep_status);
+err_status:
+	usb_ep_disable(fu->ep_cmd);
+err_cmd:
+	usb_ep_disable(fu->ep_out);
+err_b_out:
+	usb_ep_disable(fu->ep_in);
+err_b_in:
+	fu->flags = 0;
+}
+
+static int get_cmd_dir(const unsigned char *cdb)
+{
+	int ret;
+
+	switch (cdb[0]) {
+	case READ_6:
+	case READ_10:
+	case READ_12:
+	case READ_16:
+	case INQUIRY:
+	case MODE_SENSE:
+	case MODE_SENSE_10:
+	case SERVICE_ACTION_IN_16:
+	case MAINTENANCE_IN:
+	case PERSISTENT_RESERVE_IN:
+	case SECURITY_PROTOCOL_IN:
+	case ACCESS_CONTROL_IN:
+	case REPORT_LUNS:
+	case READ_BLOCK_LIMITS:
+	case READ_POSITION:
+	case READ_CAPACITY:
+	case READ_TOC:
+	case READ_FORMAT_CAPACITIES:
+	case REQUEST_SENSE:
+		ret = DMA_FROM_DEVICE;
+		break;
+
+	case WRITE_6:
+	case WRITE_10:
+	case WRITE_12:
+	case WRITE_16:
+	case MODE_SELECT:
+	case MODE_SELECT_10:
+	case WRITE_VERIFY:
+	case WRITE_VERIFY_12:
+	case PERSISTENT_RESERVE_OUT:
+	case MAINTENANCE_OUT:
+	case SECURITY_PROTOCOL_OUT:
+	case ACCESS_CONTROL_OUT:
+		ret = DMA_TO_DEVICE;
+		break;
+	case ALLOW_MEDIUM_REMOVAL:
+	case TEST_UNIT_READY:
+	case SYNCHRONIZE_CACHE:
+	case START_STOP:
+	case ERASE:
+	case REZERO_UNIT:
+	case SEEK_10:
+	case SPACE:
+	case VERIFY:
+	case WRITE_FILEMARKS:
+		ret = DMA_NONE;
+		break;
+	default:
+#define CMD_DIR_MSG "target: Unknown data direction for SCSI Opcode 0x%02x\n"
+		pr_warn(CMD_DIR_MSG, cdb[0]);
+#undef CMD_DIR_MSG
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usbg_cmd *cmd = req->context;
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+
+	if (req->status < 0) {
+		pr_err("%s() state %d transfer failed\n", __func__, cmd->state);
+		goto cleanup;
+	}
+
+	if (req->num_sgs == 0) {
+		sg_copy_from_buffer(se_cmd->t_data_sg,
+				se_cmd->t_data_nents,
+				cmd->data_buf,
+				se_cmd->data_length);
+	}
+
+	complete(&cmd->write_complete);
+	return;
+
+cleanup:
+	transport_generic_free_cmd(&cmd->se_cmd, 0);
+}
+
+static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req)
+{
+	struct se_cmd *se_cmd = &cmd->se_cmd;
+	struct f_uas *fu = cmd->fu;
+	struct usb_gadget *gadget = fuas_to_gadget(fu);
+
+	if (!gadget->sg_supported) {
+		cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC);
+		if (!cmd->data_buf)
+			return -ENOMEM;
+
+		req->buf = cmd->data_buf;
+	} else {
+		req->buf = NULL;
+		req->num_sgs = se_cmd->t_data_nents;
+		req->sg = se_cmd->t_data_sg;
+	}
+
+	req->complete = usbg_data_write_cmpl;
+	req->length = se_cmd->data_length;
+	req->context = cmd;
+	return 0;
+}
+
+static int usbg_send_status_response(struct se_cmd *se_cmd)
+{
+	struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd,
+			se_cmd);
+	struct f_uas *fu = cmd->fu;
+
+	if (fu->flags & USBG_IS_BOT)
+		return bot_send_status_response(cmd);
+	else
+		return uasp_send_status_response(cmd);
+}
+
+static int usbg_send_write_request(struct se_cmd *se_cmd)
+{
+	struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd,
+			se_cmd);
+	struct f_uas *fu = cmd->fu;
+
+	if (fu->flags & USBG_IS_BOT)
+		return bot_send_write_request(cmd);
+	else
+		return uasp_send_write_request(cmd);
+}
+
+static int usbg_send_read_response(struct se_cmd *se_cmd)
+{
+	struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd,
+			se_cmd);
+	struct f_uas *fu = cmd->fu;
+
+	if (fu->flags & USBG_IS_BOT)
+		return bot_send_read_response(cmd);
+	else
+		return uasp_send_read_response(cmd);
+}
+
+static void usbg_cmd_work(struct work_struct *work)
+{
+	struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work);
+	struct se_cmd *se_cmd;
+	struct tcm_usbg_nexus *tv_nexus;
+	struct usbg_tpg *tpg;
+	int dir, flags = (TARGET_SCF_UNKNOWN_SIZE | TARGET_SCF_ACK_KREF);
+
+	se_cmd = &cmd->se_cmd;
+	tpg = cmd->fu->tpg;
+	tv_nexus = tpg->tpg_nexus;
+	dir = get_cmd_dir(cmd->cmd_buf);
+	if (dir < 0) {
+		transport_init_se_cmd(se_cmd,
+				tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo,
+				tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE,
+				cmd->prio_attr, cmd->sense_iu.sense);
+		goto out;
+	}
+
+	if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, cmd->cmd_buf,
+			      cmd->sense_iu.sense, cmd->unpacked_lun, 0,
+			      cmd->prio_attr, dir, flags) < 0)
+		goto out;
+
+	return;
+
+out:
+	transport_send_check_condition_and_sense(se_cmd,
+			TCM_UNSUPPORTED_SCSI_OPCODE, 1);
+	transport_generic_free_cmd(&cmd->se_cmd, 0);
+}
+
+static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu,
+		struct tcm_usbg_nexus *tv_nexus, u32 scsi_tag)
+{
+	struct se_session *se_sess = tv_nexus->tvn_se_sess;
+	struct usbg_cmd *cmd;
+	int tag, cpu;
+
+	tag = sbitmap_queue_get(&se_sess->sess_tag_pool, &cpu);
+	if (tag < 0)
+		return ERR_PTR(-ENOMEM);
+
+	cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[tag];
+	memset(cmd, 0, sizeof(*cmd));
+	cmd->se_cmd.map_tag = tag;
+	cmd->se_cmd.map_cpu = cpu;
+	cmd->se_cmd.tag = cmd->tag = scsi_tag;
+	cmd->fu = fu;
+
+	return cmd;
+}
+
+static void usbg_release_cmd(struct se_cmd *);
+
+static int usbg_submit_command(struct f_uas *fu,
+		void *cmdbuf, unsigned int len)
+{
+	struct command_iu *cmd_iu = cmdbuf;
+	struct usbg_cmd *cmd;
+	struct usbg_tpg *tpg = fu->tpg;
+	struct tcm_usbg_nexus *tv_nexus;
+	u32 cmd_len;
+	u16 scsi_tag;
+
+	if (cmd_iu->iu_id != IU_ID_COMMAND) {
+		pr_err("Unsupported type %d\n", cmd_iu->iu_id);
+		return -EINVAL;
+	}
+
+	tv_nexus = tpg->tpg_nexus;
+	if (!tv_nexus) {
+		pr_err("Missing nexus, ignoring command\n");
+		return -EINVAL;
+	}
+
+	cmd_len = (cmd_iu->len & ~0x3) + 16;
+	if (cmd_len > USBG_MAX_CMD)
+		return -EINVAL;
+
+	scsi_tag = be16_to_cpup(&cmd_iu->tag);
+	cmd = usbg_get_cmd(fu, tv_nexus, scsi_tag);
+	if (IS_ERR(cmd)) {
+		pr_err("usbg_get_cmd failed\n");
+		return -ENOMEM;
+	}
+	memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len);
+
+	if (fu->flags & USBG_USE_STREAMS) {
+		if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS)
+			goto err;
+		if (!cmd->tag)
+			cmd->stream = &fu->stream[0];
+		else
+			cmd->stream = &fu->stream[cmd->tag - 1];
+	} else {
+		cmd->stream = &fu->stream[0];
+	}
+
+	switch (cmd_iu->prio_attr & 0x7) {
+	case UAS_HEAD_TAG:
+		cmd->prio_attr = TCM_HEAD_TAG;
+		break;
+	case UAS_ORDERED_TAG:
+		cmd->prio_attr = TCM_ORDERED_TAG;
+		break;
+	case UAS_ACA:
+		cmd->prio_attr = TCM_ACA_TAG;
+		break;
+	default:
+		pr_debug_once("Unsupported prio_attr: %02x.\n",
+				cmd_iu->prio_attr);
+		/* fall through */
+	case UAS_SIMPLE_TAG:
+		cmd->prio_attr = TCM_SIMPLE_TAG;
+		break;
+	}
+
+	cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun);
+
+	INIT_WORK(&cmd->work, usbg_cmd_work);
+	queue_work(tpg->workqueue, &cmd->work);
+
+	return 0;
+err:
+	usbg_release_cmd(&cmd->se_cmd);
+	return -EINVAL;
+}
+
+static void bot_cmd_work(struct work_struct *work)
+{
+	struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work);
+	struct se_cmd *se_cmd;
+	struct tcm_usbg_nexus *tv_nexus;
+	struct usbg_tpg *tpg;
+	int dir;
+
+	se_cmd = &cmd->se_cmd;
+	tpg = cmd->fu->tpg;
+	tv_nexus = tpg->tpg_nexus;
+	dir = get_cmd_dir(cmd->cmd_buf);
+	if (dir < 0) {
+		transport_init_se_cmd(se_cmd,
+				tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo,
+				tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE,
+				cmd->prio_attr, cmd->sense_iu.sense);
+		goto out;
+	}
+
+	if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess,
+			cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun,
+			cmd->data_len, cmd->prio_attr, dir, 0) < 0)
+		goto out;
+
+	return;
+
+out:
+	transport_send_check_condition_and_sense(se_cmd,
+				TCM_UNSUPPORTED_SCSI_OPCODE, 1);
+	transport_generic_free_cmd(&cmd->se_cmd, 0);
+}
+
+static int bot_submit_command(struct f_uas *fu,
+		void *cmdbuf, unsigned int len)
+{
+	struct bulk_cb_wrap *cbw = cmdbuf;
+	struct usbg_cmd *cmd;
+	struct usbg_tpg *tpg = fu->tpg;
+	struct tcm_usbg_nexus *tv_nexus;
+	u32 cmd_len;
+
+	if (cbw->Signature != cpu_to_le32(US_BULK_CB_SIGN)) {
+		pr_err("Wrong signature on CBW\n");
+		return -EINVAL;
+	}
+	if (len != 31) {
+		pr_err("Wrong length for CBW\n");
+		return -EINVAL;
+	}
+
+	cmd_len = cbw->Length;
+	if (cmd_len < 1 || cmd_len > 16)
+		return -EINVAL;
+
+	tv_nexus = tpg->tpg_nexus;
+	if (!tv_nexus) {
+		pr_err("Missing nexus, ignoring command\n");
+		return -ENODEV;
+	}
+
+	cmd = usbg_get_cmd(fu, tv_nexus, cbw->Tag);
+	if (IS_ERR(cmd)) {
+		pr_err("usbg_get_cmd failed\n");
+		return -ENOMEM;
+	}
+	memcpy(cmd->cmd_buf, cbw->CDB, cmd_len);
+
+	cmd->bot_tag = cbw->Tag;
+	cmd->prio_attr = TCM_SIMPLE_TAG;
+	cmd->unpacked_lun = cbw->Lun;
+	cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0;
+	cmd->data_len = le32_to_cpu(cbw->DataTransferLength);
+	cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag);
+
+	INIT_WORK(&cmd->work, bot_cmd_work);
+	queue_work(tpg->workqueue, &cmd->work);
+
+	return 0;
+}
+
+/* Start fabric.c code */
+
+static int usbg_check_true(struct se_portal_group *se_tpg)
+{
+	return 1;
+}
+
+static int usbg_check_false(struct se_portal_group *se_tpg)
+{
+	return 0;
+}
+
+static char *usbg_get_fabric_wwn(struct se_portal_group *se_tpg)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg,
+				struct usbg_tpg, se_tpg);
+	struct usbg_tport *tport = tpg->tport;
+
+	return &tport->tport_name[0];
+}
+
+static u16 usbg_get_tag(struct se_portal_group *se_tpg)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg,
+				struct usbg_tpg, se_tpg);
+	return tpg->tport_tpgt;
+}
+
+static u32 usbg_tpg_get_inst_index(struct se_portal_group *se_tpg)
+{
+	return 1;
+}
+
+static void usbg_release_cmd(struct se_cmd *se_cmd)
+{
+	struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd,
+			se_cmd);
+	struct se_session *se_sess = se_cmd->se_sess;
+
+	kfree(cmd->data_buf);
+	target_free_tag(se_sess, se_cmd);
+}
+
+static u32 usbg_sess_get_index(struct se_session *se_sess)
+{
+	return 0;
+}
+
+static void usbg_set_default_node_attrs(struct se_node_acl *nacl)
+{
+}
+
+static int usbg_get_cmd_state(struct se_cmd *se_cmd)
+{
+	return 0;
+}
+
+static void usbg_queue_tm_rsp(struct se_cmd *se_cmd)
+{
+}
+
+static void usbg_aborted_task(struct se_cmd *se_cmd)
+{
+}
+
+static const char *usbg_check_wwn(const char *name)
+{
+	const char *n;
+	unsigned int len;
+
+	n = strstr(name, "naa.");
+	if (!n)
+		return NULL;
+	n += 4;
+	len = strlen(n);
+	if (len == 0 || len > USBG_NAMELEN - 1)
+		return NULL;
+	return n;
+}
+
+static int usbg_init_nodeacl(struct se_node_acl *se_nacl, const char *name)
+{
+	if (!usbg_check_wwn(name))
+		return -EINVAL;
+	return 0;
+}
+
+static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
+					     const char *name)
+{
+	struct usbg_tport *tport = container_of(wwn, struct usbg_tport,
+			tport_wwn);
+	struct usbg_tpg *tpg;
+	unsigned long tpgt;
+	int ret;
+	struct f_tcm_opts *opts;
+	unsigned i;
+
+	if (strstr(name, "tpgt_") != name)
+		return ERR_PTR(-EINVAL);
+	if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX)
+		return ERR_PTR(-EINVAL);
+	ret = -ENODEV;
+	mutex_lock(&tpg_instances_lock);
+	for (i = 0; i < TPG_INSTANCES; ++i)
+		if (tpg_instances[i].func_inst && !tpg_instances[i].tpg)
+			break;
+	if (i == TPG_INSTANCES)
+		goto unlock_inst;
+
+	opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
+		func_inst);
+	mutex_lock(&opts->dep_lock);
+	if (!opts->ready)
+		goto unlock_dep;
+
+	if (opts->has_dep) {
+		if (!try_module_get(opts->dependent))
+			goto unlock_dep;
+	} else {
+		ret = configfs_depend_item_unlocked(
+			wwn->wwn_group.cg_subsys,
+			&opts->func_inst.group.cg_item);
+		if (ret)
+			goto unlock_dep;
+	}
+
+	tpg = kzalloc(sizeof(struct usbg_tpg), GFP_KERNEL);
+	ret = -ENOMEM;
+	if (!tpg)
+		goto unref_dep;
+	mutex_init(&tpg->tpg_mutex);
+	atomic_set(&tpg->tpg_port_count, 0);
+	tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1);
+	if (!tpg->workqueue)
+		goto free_tpg;
+
+	tpg->tport = tport;
+	tpg->tport_tpgt = tpgt;
+
+	/*
+	 * SPC doesn't assign a protocol identifier for USB-SCSI, so we
+	 * pretend to be SAS..
+	 */
+	ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS);
+	if (ret < 0)
+		goto free_workqueue;
+
+	tpg_instances[i].tpg = tpg;
+	tpg->fi = tpg_instances[i].func_inst;
+	mutex_unlock(&opts->dep_lock);
+	mutex_unlock(&tpg_instances_lock);
+	return &tpg->se_tpg;
+
+free_workqueue:
+	destroy_workqueue(tpg->workqueue);
+free_tpg:
+	kfree(tpg);
+unref_dep:
+	if (opts->has_dep)
+		module_put(opts->dependent);
+	else
+		configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
+unlock_dep:
+	mutex_unlock(&opts->dep_lock);
+unlock_inst:
+	mutex_unlock(&tpg_instances_lock);
+
+	return ERR_PTR(ret);
+}
+
+static int tcm_usbg_drop_nexus(struct usbg_tpg *);
+
+static void usbg_drop_tpg(struct se_portal_group *se_tpg)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg,
+				struct usbg_tpg, se_tpg);
+	unsigned i;
+	struct f_tcm_opts *opts;
+
+	tcm_usbg_drop_nexus(tpg);
+	core_tpg_deregister(se_tpg);
+	destroy_workqueue(tpg->workqueue);
+
+	mutex_lock(&tpg_instances_lock);
+	for (i = 0; i < TPG_INSTANCES; ++i)
+		if (tpg_instances[i].tpg == tpg)
+			break;
+	if (i < TPG_INSTANCES) {
+		tpg_instances[i].tpg = NULL;
+		opts = container_of(tpg_instances[i].func_inst,
+			struct f_tcm_opts, func_inst);
+		mutex_lock(&opts->dep_lock);
+		if (opts->has_dep)
+			module_put(opts->dependent);
+		else
+			configfs_undepend_item_unlocked(
+				&opts->func_inst.group.cg_item);
+		mutex_unlock(&opts->dep_lock);
+	}
+	mutex_unlock(&tpg_instances_lock);
+
+	kfree(tpg);
+}
+
+static struct se_wwn *usbg_make_tport(
+	struct target_fabric_configfs *tf,
+	struct config_group *group,
+	const char *name)
+{
+	struct usbg_tport *tport;
+	const char *wnn_name;
+	u64 wwpn = 0;
+
+	wnn_name = usbg_check_wwn(name);
+	if (!wnn_name)
+		return ERR_PTR(-EINVAL);
+
+	tport = kzalloc(sizeof(struct usbg_tport), GFP_KERNEL);
+	if (!(tport))
+		return ERR_PTR(-ENOMEM);
+
+	tport->tport_wwpn = wwpn;
+	snprintf(tport->tport_name, sizeof(tport->tport_name), "%s", wnn_name);
+	return &tport->tport_wwn;
+}
+
+static void usbg_drop_tport(struct se_wwn *wwn)
+{
+	struct usbg_tport *tport = container_of(wwn,
+				struct usbg_tport, tport_wwn);
+	kfree(tport);
+}
+
+/*
+ * If somebody feels like dropping the version property, go ahead.
+ */
+static ssize_t usbg_wwn_version_show(struct config_item *item,  char *page)
+{
+	return sprintf(page, "usb-gadget fabric module\n");
+}
+
+CONFIGFS_ATTR_RO(usbg_wwn_, version);
+
+static struct configfs_attribute *usbg_wwn_attrs[] = {
+	&usbg_wwn_attr_version,
+	NULL,
+};
+
+static ssize_t tcm_usbg_tpg_enable_show(struct config_item *item, char *page)
+{
+	struct se_portal_group *se_tpg = to_tpg(item);
+	struct usbg_tpg  *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+
+	return snprintf(page, PAGE_SIZE, "%u\n", tpg->gadget_connect);
+}
+
+static int usbg_attach(struct usbg_tpg *);
+static void usbg_detach(struct usbg_tpg *);
+
+static ssize_t tcm_usbg_tpg_enable_store(struct config_item *item,
+		const char *page, size_t count)
+{
+	struct se_portal_group *se_tpg = to_tpg(item);
+	struct usbg_tpg  *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+	bool op;
+	ssize_t ret;
+
+	ret = strtobool(page, &op);
+	if (ret)
+		return ret;
+
+	if ((op && tpg->gadget_connect) || (!op && !tpg->gadget_connect))
+		return -EINVAL;
+
+	if (op)
+		ret = usbg_attach(tpg);
+	else
+		usbg_detach(tpg);
+	if (ret)
+		return ret;
+
+	tpg->gadget_connect = op;
+
+	return count;
+}
+
+static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page)
+{
+	struct se_portal_group *se_tpg = to_tpg(item);
+	struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+	struct tcm_usbg_nexus *tv_nexus;
+	ssize_t ret;
+
+	mutex_lock(&tpg->tpg_mutex);
+	tv_nexus = tpg->tpg_nexus;
+	if (!tv_nexus) {
+		ret = -ENODEV;
+		goto out;
+	}
+	ret = snprintf(page, PAGE_SIZE, "%s\n",
+			tv_nexus->tvn_se_sess->se_node_acl->initiatorname);
+out:
+	mutex_unlock(&tpg->tpg_mutex);
+	return ret;
+}
+
+static int usbg_alloc_sess_cb(struct se_portal_group *se_tpg,
+			      struct se_session *se_sess, void *p)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg,
+				struct usbg_tpg, se_tpg);
+
+	tpg->tpg_nexus = p;
+	return 0;
+}
+
+static int tcm_usbg_make_nexus(struct usbg_tpg *tpg, char *name)
+{
+	struct tcm_usbg_nexus *tv_nexus;
+	int ret = 0;
+
+	mutex_lock(&tpg->tpg_mutex);
+	if (tpg->tpg_nexus) {
+		ret = -EEXIST;
+		pr_debug("tpg->tpg_nexus already exists\n");
+		goto out_unlock;
+	}
+
+	tv_nexus = kzalloc(sizeof(*tv_nexus), GFP_KERNEL);
+	if (!tv_nexus) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	tv_nexus->tvn_se_sess = target_setup_session(&tpg->se_tpg,
+						     USB_G_DEFAULT_SESSION_TAGS,
+						     sizeof(struct usbg_cmd),
+						     TARGET_PROT_NORMAL, name,
+						     tv_nexus, usbg_alloc_sess_cb);
+	if (IS_ERR(tv_nexus->tvn_se_sess)) {
+#define MAKE_NEXUS_MSG "core_tpg_check_initiator_node_acl() failed for %s\n"
+		pr_debug(MAKE_NEXUS_MSG, name);
+#undef MAKE_NEXUS_MSG
+		ret = PTR_ERR(tv_nexus->tvn_se_sess);
+		kfree(tv_nexus);
+	}
+
+out_unlock:
+	mutex_unlock(&tpg->tpg_mutex);
+	return ret;
+}
+
+static int tcm_usbg_drop_nexus(struct usbg_tpg *tpg)
+{
+	struct se_session *se_sess;
+	struct tcm_usbg_nexus *tv_nexus;
+	int ret = -ENODEV;
+
+	mutex_lock(&tpg->tpg_mutex);
+	tv_nexus = tpg->tpg_nexus;
+	if (!tv_nexus)
+		goto out;
+
+	se_sess = tv_nexus->tvn_se_sess;
+	if (!se_sess)
+		goto out;
+
+	if (atomic_read(&tpg->tpg_port_count)) {
+		ret = -EPERM;
+#define MSG "Unable to remove Host I_T Nexus with active TPG port count: %d\n"
+		pr_err(MSG, atomic_read(&tpg->tpg_port_count));
+#undef MSG
+		goto out;
+	}
+
+	pr_debug("Removing I_T Nexus to Initiator Port: %s\n",
+			tv_nexus->tvn_se_sess->se_node_acl->initiatorname);
+	/*
+	 * Release the SCSI I_T Nexus to the emulated vHost Target Port
+	 */
+	target_remove_session(se_sess);
+	tpg->tpg_nexus = NULL;
+
+	kfree(tv_nexus);
+	ret = 0;
+out:
+	mutex_unlock(&tpg->tpg_mutex);
+	return ret;
+}
+
+static ssize_t tcm_usbg_tpg_nexus_store(struct config_item *item,
+		const char *page, size_t count)
+{
+	struct se_portal_group *se_tpg = to_tpg(item);
+	struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+	unsigned char i_port[USBG_NAMELEN], *ptr;
+	int ret;
+
+	if (!strncmp(page, "NULL", 4)) {
+		ret = tcm_usbg_drop_nexus(tpg);
+		return (!ret) ? count : ret;
+	}
+	if (strlen(page) >= USBG_NAMELEN) {
+
+#define NEXUS_STORE_MSG "Emulated NAA Sas Address: %s, exceeds max: %d\n"
+		pr_err(NEXUS_STORE_MSG, page, USBG_NAMELEN);
+#undef NEXUS_STORE_MSG
+		return -EINVAL;
+	}
+	snprintf(i_port, USBG_NAMELEN, "%s", page);
+
+	ptr = strstr(i_port, "naa.");
+	if (!ptr) {
+		pr_err("Missing 'naa.' prefix\n");
+		return -EINVAL;
+	}
+
+	if (i_port[strlen(i_port) - 1] == '\n')
+		i_port[strlen(i_port) - 1] = '\0';
+
+	ret = tcm_usbg_make_nexus(tpg, &i_port[0]);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+CONFIGFS_ATTR(tcm_usbg_tpg_, enable);
+CONFIGFS_ATTR(tcm_usbg_tpg_, nexus);
+
+static struct configfs_attribute *usbg_base_attrs[] = {
+	&tcm_usbg_tpg_attr_enable,
+	&tcm_usbg_tpg_attr_nexus,
+	NULL,
+};
+
+static int usbg_port_link(struct se_portal_group *se_tpg, struct se_lun *lun)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+
+	atomic_inc(&tpg->tpg_port_count);
+	smp_mb__after_atomic();
+	return 0;
+}
+
+static void usbg_port_unlink(struct se_portal_group *se_tpg,
+		struct se_lun *se_lun)
+{
+	struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg);
+
+	atomic_dec(&tpg->tpg_port_count);
+	smp_mb__after_atomic();
+}
+
+static int usbg_check_stop_free(struct se_cmd *se_cmd)
+{
+	return target_put_sess_cmd(se_cmd);
+}
+
+static const struct target_core_fabric_ops usbg_ops = {
+	.module				= THIS_MODULE,
+	.fabric_name			= "usb_gadget",
+	.tpg_get_wwn			= usbg_get_fabric_wwn,
+	.tpg_get_tag			= usbg_get_tag,
+	.tpg_check_demo_mode		= usbg_check_true,
+	.tpg_check_demo_mode_cache	= usbg_check_false,
+	.tpg_check_demo_mode_write_protect = usbg_check_false,
+	.tpg_check_prod_mode_write_protect = usbg_check_false,
+	.tpg_get_inst_index		= usbg_tpg_get_inst_index,
+	.release_cmd			= usbg_release_cmd,
+	.sess_get_index			= usbg_sess_get_index,
+	.sess_get_initiator_sid		= NULL,
+	.write_pending			= usbg_send_write_request,
+	.set_default_node_attributes	= usbg_set_default_node_attrs,
+	.get_cmd_state			= usbg_get_cmd_state,
+	.queue_data_in			= usbg_send_read_response,
+	.queue_status			= usbg_send_status_response,
+	.queue_tm_rsp			= usbg_queue_tm_rsp,
+	.aborted_task			= usbg_aborted_task,
+	.check_stop_free		= usbg_check_stop_free,
+
+	.fabric_make_wwn		= usbg_make_tport,
+	.fabric_drop_wwn		= usbg_drop_tport,
+	.fabric_make_tpg		= usbg_make_tpg,
+	.fabric_drop_tpg		= usbg_drop_tpg,
+	.fabric_post_link		= usbg_port_link,
+	.fabric_pre_unlink		= usbg_port_unlink,
+	.fabric_init_nodeacl		= usbg_init_nodeacl,
+
+	.tfc_wwn_attrs			= usbg_wwn_attrs,
+	.tfc_tpg_base_attrs		= usbg_base_attrs,
+};
+
+/* Start gadget.c code */
+
+static struct usb_interface_descriptor bot_intf_desc = {
+	.bLength =              sizeof(bot_intf_desc),
+	.bDescriptorType =      USB_DT_INTERFACE,
+	.bNumEndpoints =        2,
+	.bAlternateSetting =	USB_G_ALT_INT_BBB,
+	.bInterfaceClass =      USB_CLASS_MASS_STORAGE,
+	.bInterfaceSubClass =   USB_SC_SCSI,
+	.bInterfaceProtocol =   USB_PR_BULK,
+};
+
+static struct usb_interface_descriptor uasp_intf_desc = {
+	.bLength =		sizeof(uasp_intf_desc),
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	4,
+	.bAlternateSetting =	USB_G_ALT_INT_UAS,
+	.bInterfaceClass =	USB_CLASS_MASS_STORAGE,
+	.bInterfaceSubClass =	USB_SC_SCSI,
+	.bInterfaceProtocol =	USB_PR_UAS,
+};
+
+static struct usb_endpoint_descriptor uasp_bi_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor uasp_fs_bi_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_pipe_usage_descriptor uasp_bi_pipe_desc = {
+	.bLength =		sizeof(uasp_bi_pipe_desc),
+	.bDescriptorType =	USB_DT_PIPE_USAGE,
+	.bPipeID =		DATA_IN_PIPE_ID,
+};
+
+static struct usb_endpoint_descriptor uasp_ss_bi_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = {
+	.bLength =		sizeof(uasp_bi_ep_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst =		0,
+	.bmAttributes =		UASP_SS_EP_COMP_LOG_STREAMS,
+	.wBytesPerInterval =	0,
+};
+
+static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = {
+	.bLength =		sizeof(bot_bi_ep_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst =		0,
+};
+
+static struct usb_endpoint_descriptor uasp_bo_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor uasp_fs_bo_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_pipe_usage_descriptor uasp_bo_pipe_desc = {
+	.bLength =		sizeof(uasp_bo_pipe_desc),
+	.bDescriptorType =	USB_DT_PIPE_USAGE,
+	.bPipeID =		DATA_OUT_PIPE_ID,
+};
+
+static struct usb_endpoint_descriptor uasp_ss_bo_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(0x400),
+};
+
+static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = {
+	.bLength =		sizeof(uasp_bo_ep_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bmAttributes =		UASP_SS_EP_COMP_LOG_STREAMS,
+};
+
+static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = {
+	.bLength =		sizeof(bot_bo_ep_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_endpoint_descriptor uasp_status_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor uasp_fs_status_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_pipe_usage_descriptor uasp_status_pipe_desc = {
+	.bLength =		sizeof(uasp_status_pipe_desc),
+	.bDescriptorType =	USB_DT_PIPE_USAGE,
+	.bPipeID =		STATUS_PIPE_ID,
+};
+
+static struct usb_endpoint_descriptor uasp_ss_status_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor uasp_status_in_ep_comp_desc = {
+	.bLength =		sizeof(uasp_status_in_ep_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+	.bmAttributes =		UASP_SS_EP_COMP_LOG_STREAMS,
+};
+
+static struct usb_endpoint_descriptor uasp_cmd_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor uasp_fs_cmd_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_pipe_usage_descriptor uasp_cmd_pipe_desc = {
+	.bLength =		sizeof(uasp_cmd_pipe_desc),
+	.bDescriptorType =	USB_DT_PIPE_USAGE,
+	.bPipeID =		CMD_PIPE_ID,
+};
+
+static struct usb_endpoint_descriptor uasp_ss_cmd_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor uasp_cmd_comp_desc = {
+	.bLength =		sizeof(uasp_cmd_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *uasp_fs_function_desc[] = {
+	(struct usb_descriptor_header *) &bot_intf_desc,
+	(struct usb_descriptor_header *) &uasp_fs_bi_desc,
+	(struct usb_descriptor_header *) &uasp_fs_bo_desc,
+
+	(struct usb_descriptor_header *) &uasp_intf_desc,
+	(struct usb_descriptor_header *) &uasp_fs_bi_desc,
+	(struct usb_descriptor_header *) &uasp_bi_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_fs_bo_desc,
+	(struct usb_descriptor_header *) &uasp_bo_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_fs_status_desc,
+	(struct usb_descriptor_header *) &uasp_status_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_fs_cmd_desc,
+	(struct usb_descriptor_header *) &uasp_cmd_pipe_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *uasp_hs_function_desc[] = {
+	(struct usb_descriptor_header *) &bot_intf_desc,
+	(struct usb_descriptor_header *) &uasp_bi_desc,
+	(struct usb_descriptor_header *) &uasp_bo_desc,
+
+	(struct usb_descriptor_header *) &uasp_intf_desc,
+	(struct usb_descriptor_header *) &uasp_bi_desc,
+	(struct usb_descriptor_header *) &uasp_bi_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_bo_desc,
+	(struct usb_descriptor_header *) &uasp_bo_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_status_desc,
+	(struct usb_descriptor_header *) &uasp_status_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_cmd_desc,
+	(struct usb_descriptor_header *) &uasp_cmd_pipe_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *uasp_ss_function_desc[] = {
+	(struct usb_descriptor_header *) &bot_intf_desc,
+	(struct usb_descriptor_header *) &uasp_ss_bi_desc,
+	(struct usb_descriptor_header *) &bot_bi_ep_comp_desc,
+	(struct usb_descriptor_header *) &uasp_ss_bo_desc,
+	(struct usb_descriptor_header *) &bot_bo_ep_comp_desc,
+
+	(struct usb_descriptor_header *) &uasp_intf_desc,
+	(struct usb_descriptor_header *) &uasp_ss_bi_desc,
+	(struct usb_descriptor_header *) &uasp_bi_ep_comp_desc,
+	(struct usb_descriptor_header *) &uasp_bi_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_ss_bo_desc,
+	(struct usb_descriptor_header *) &uasp_bo_ep_comp_desc,
+	(struct usb_descriptor_header *) &uasp_bo_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_ss_status_desc,
+	(struct usb_descriptor_header *) &uasp_status_in_ep_comp_desc,
+	(struct usb_descriptor_header *) &uasp_status_pipe_desc,
+	(struct usb_descriptor_header *) &uasp_ss_cmd_desc,
+	(struct usb_descriptor_header *) &uasp_cmd_comp_desc,
+	(struct usb_descriptor_header *) &uasp_cmd_pipe_desc,
+	NULL,
+};
+
+static struct usb_string	tcm_us_strings[] = {
+	[USB_G_STR_INT_UAS].s		= "USB Attached SCSI",
+	[USB_G_STR_INT_BBB].s		= "Bulk Only Transport",
+	{ },
+};
+
+static struct usb_gadget_strings tcm_stringtab = {
+	.language = 0x0409,
+	.strings = tcm_us_strings,
+};
+
+static struct usb_gadget_strings *tcm_strings[] = {
+	&tcm_stringtab,
+	NULL,
+};
+
+static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_uas		*fu = to_f_uas(f);
+	struct usb_string	*us;
+	struct usb_gadget	*gadget = c->cdev->gadget;
+	struct usb_ep		*ep;
+	struct f_tcm_opts	*opts;
+	int			iface;
+	int			ret;
+
+	opts = container_of(f->fi, struct f_tcm_opts, func_inst);
+
+	mutex_lock(&opts->dep_lock);
+	if (!opts->can_attach) {
+		mutex_unlock(&opts->dep_lock);
+		return -ENODEV;
+	}
+	mutex_unlock(&opts->dep_lock);
+	us = usb_gstrings_attach(c->cdev, tcm_strings,
+		ARRAY_SIZE(tcm_us_strings));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	bot_intf_desc.iInterface = us[USB_G_STR_INT_BBB].id;
+	uasp_intf_desc.iInterface = us[USB_G_STR_INT_UAS].id;
+
+	iface = usb_interface_id(c, f);
+	if (iface < 0)
+		return iface;
+
+	bot_intf_desc.bInterfaceNumber = iface;
+	uasp_intf_desc.bInterfaceNumber = iface;
+	fu->iface = iface;
+	ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc,
+			&uasp_bi_ep_comp_desc);
+	if (!ep)
+		goto ep_fail;
+
+	fu->ep_in = ep;
+
+	ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc,
+			&uasp_bo_ep_comp_desc);
+	if (!ep)
+		goto ep_fail;
+	fu->ep_out = ep;
+
+	ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc,
+			&uasp_status_in_ep_comp_desc);
+	if (!ep)
+		goto ep_fail;
+	fu->ep_status = ep;
+
+	ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc,
+			&uasp_cmd_comp_desc);
+	if (!ep)
+		goto ep_fail;
+	fu->ep_cmd = ep;
+
+	/* Assume endpoint addresses are the same for both speeds */
+	uasp_bi_desc.bEndpointAddress =	uasp_ss_bi_desc.bEndpointAddress;
+	uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress;
+	uasp_status_desc.bEndpointAddress =
+		uasp_ss_status_desc.bEndpointAddress;
+	uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress;
+
+	uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress;
+	uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress;
+	uasp_fs_status_desc.bEndpointAddress =
+		uasp_ss_status_desc.bEndpointAddress;
+	uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, uasp_fs_function_desc,
+			uasp_hs_function_desc, uasp_ss_function_desc,
+			uasp_ss_function_desc);
+	if (ret)
+		goto ep_fail;
+
+	return 0;
+ep_fail:
+	pr_err("Can't claim all required eps\n");
+
+	return -ENOTSUPP;
+}
+
+struct guas_setup_wq {
+	struct work_struct work;
+	struct f_uas *fu;
+	unsigned int alt;
+};
+
+static void tcm_delayed_set_alt(struct work_struct *wq)
+{
+	struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq,
+			work);
+	struct f_uas *fu = work->fu;
+	int alt = work->alt;
+
+	kfree(work);
+
+	if (fu->flags & USBG_IS_BOT)
+		bot_cleanup_old_alt(fu);
+	if (fu->flags & USBG_IS_UAS)
+		uasp_cleanup_old_alt(fu);
+
+	if (alt == USB_G_ALT_INT_BBB)
+		bot_set_alt(fu);
+	else if (alt == USB_G_ALT_INT_UAS)
+		uasp_set_alt(fu);
+	usb_composite_setup_continue(fu->function.config->cdev);
+}
+
+static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_uas *fu = to_f_uas(f);
+
+	if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) {
+		struct guas_setup_wq *work;
+
+		work = kmalloc(sizeof(*work), GFP_ATOMIC);
+		if (!work)
+			return -ENOMEM;
+		INIT_WORK(&work->work, tcm_delayed_set_alt);
+		work->fu = fu;
+		work->alt = alt;
+		schedule_work(&work->work);
+		return USB_GADGET_DELAYED_STATUS;
+	}
+	return -EOPNOTSUPP;
+}
+
+static void tcm_disable(struct usb_function *f)
+{
+	struct f_uas *fu = to_f_uas(f);
+
+	if (fu->flags & USBG_IS_UAS)
+		uasp_cleanup_old_alt(fu);
+	else if (fu->flags & USBG_IS_BOT)
+		bot_cleanup_old_alt(fu);
+	fu->flags = 0;
+}
+
+static int tcm_setup(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct f_uas *fu = to_f_uas(f);
+
+	if (!(fu->flags & USBG_IS_BOT))
+		return -EOPNOTSUPP;
+
+	return usbg_bot_setup(f, ctrl);
+}
+
+static inline struct f_tcm_opts *to_f_tcm_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_tcm_opts,
+		func_inst.group);
+}
+
+static void tcm_attr_release(struct config_item *item)
+{
+	struct f_tcm_opts *opts = to_f_tcm_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations tcm_item_ops = {
+	.release		= tcm_attr_release,
+};
+
+static const struct config_item_type tcm_func_type = {
+	.ct_item_ops	= &tcm_item_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void tcm_free_inst(struct usb_function_instance *f)
+{
+	struct f_tcm_opts *opts;
+	unsigned i;
+
+	opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	mutex_lock(&tpg_instances_lock);
+	for (i = 0; i < TPG_INSTANCES; ++i)
+		if (tpg_instances[i].func_inst == f)
+			break;
+	if (i < TPG_INSTANCES)
+		tpg_instances[i].func_inst = NULL;
+	mutex_unlock(&tpg_instances_lock);
+
+	kfree(opts);
+}
+
+static int tcm_register_callback(struct usb_function_instance *f)
+{
+	struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	mutex_lock(&opts->dep_lock);
+	opts->can_attach = true;
+	mutex_unlock(&opts->dep_lock);
+
+	return 0;
+}
+
+static void tcm_unregister_callback(struct usb_function_instance *f)
+{
+	struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	mutex_lock(&opts->dep_lock);
+	unregister_gadget_item(opts->
+		func_inst.group.cg_item.ci_parent->ci_parent);
+	opts->can_attach = false;
+	mutex_unlock(&opts->dep_lock);
+}
+
+static int usbg_attach(struct usbg_tpg *tpg)
+{
+	struct usb_function_instance *f = tpg->fi;
+	struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	if (opts->tcm_register_callback)
+		return opts->tcm_register_callback(f);
+
+	return 0;
+}
+
+static void usbg_detach(struct usbg_tpg *tpg)
+{
+	struct usb_function_instance *f = tpg->fi;
+	struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	if (opts->tcm_unregister_callback)
+		opts->tcm_unregister_callback(f);
+}
+
+static int tcm_set_name(struct usb_function_instance *f, const char *name)
+{
+	struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
+
+	pr_debug("tcm: Activating %s\n", name);
+
+	mutex_lock(&opts->dep_lock);
+	opts->ready = true;
+	mutex_unlock(&opts->dep_lock);
+
+	return 0;
+}
+
+static struct usb_function_instance *tcm_alloc_inst(void)
+{
+	struct f_tcm_opts *opts;
+	int i;
+
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_lock(&tpg_instances_lock);
+	for (i = 0; i < TPG_INSTANCES; ++i)
+		if (!tpg_instances[i].func_inst)
+			break;
+
+	if (i == TPG_INSTANCES) {
+		mutex_unlock(&tpg_instances_lock);
+		kfree(opts);
+		return ERR_PTR(-EBUSY);
+	}
+	tpg_instances[i].func_inst = &opts->func_inst;
+	mutex_unlock(&tpg_instances_lock);
+
+	mutex_init(&opts->dep_lock);
+	opts->func_inst.set_inst_name = tcm_set_name;
+	opts->func_inst.free_func_inst = tcm_free_inst;
+	opts->tcm_register_callback = tcm_register_callback;
+	opts->tcm_unregister_callback = tcm_unregister_callback;
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+			&tcm_func_type);
+
+	return &opts->func_inst;
+}
+
+static void tcm_free(struct usb_function *f)
+{
+	struct f_uas *tcm = to_f_uas(f);
+
+	kfree(tcm);
+}
+
+static void tcm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
+{
+	struct f_uas *fu;
+	unsigned i;
+
+	mutex_lock(&tpg_instances_lock);
+	for (i = 0; i < TPG_INSTANCES; ++i)
+		if (tpg_instances[i].func_inst == fi)
+			break;
+	if (i == TPG_INSTANCES) {
+		mutex_unlock(&tpg_instances_lock);
+		return ERR_PTR(-ENODEV);
+	}
+
+	fu = kzalloc(sizeof(*fu), GFP_KERNEL);
+	if (!fu) {
+		mutex_unlock(&tpg_instances_lock);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	fu->function.name = "Target Function";
+	fu->function.bind = tcm_bind;
+	fu->function.unbind = tcm_unbind;
+	fu->function.set_alt = tcm_set_alt;
+	fu->function.setup = tcm_setup;
+	fu->function.disable = tcm_disable;
+	fu->function.free_func = tcm_free;
+	fu->tpg = tpg_instances[i].tpg;
+	mutex_unlock(&tpg_instances_lock);
+
+	return &fu->function;
+}
+
+DECLARE_USB_FUNCTION(tcm, tcm_alloc_inst, tcm_alloc);
+
+static int tcm_init(void)
+{
+	int ret;
+
+	ret = usb_function_register(&tcmusb_func);
+	if (ret)
+		return ret;
+
+	ret = target_register_template(&usbg_ops);
+	if (ret)
+		usb_function_unregister(&tcmusb_func);
+
+	return ret;
+}
+module_init(tcm_init);
+
+static void tcm_exit(void)
+{
+	target_unregister_template(&usbg_ops);
+	usb_function_unregister(&tcmusb_func);
+}
+module_exit(tcm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sebastian Andrzej Siewior");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_uac1.c b/marvell/linux/drivers/usb/gadget/function/f_uac1.c
new file mode 100644
index 0000000..e65f474
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_uac1.c
@@ -0,0 +1,844 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
+ *
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * This driver doesn't expect any real Audio codec to be present
+ * on the device - the audio streams are simply sinked to and
+ * sourced from a virtual ALSA sound card created.
+ *
+ * This file is based on f_uac1.c which is
+ *   Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ *   Copyright (C) 2008 Analog Devices, Inc
+ */
+
+#include <linux/usb/audio.h>
+#include <linux/module.h>
+
+#include "u_audio.h"
+#include "u_uac1.h"
+
+/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
+#define UAC1_CHANNEL_MASK 0x0FFF
+
+struct f_uac1 {
+	struct g_audio g_audio;
+	u8 ac_intf, as_in_intf, as_out_intf;
+	u8 ac_alt, as_in_alt, as_out_alt;	/* needed for get_alt() */
+};
+
+static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
+{
+	return container_of(f, struct f_uac1, g_audio.func);
+}
+
+static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
+{
+	return container_of(audio->func.fi, struct f_uac1_opts, func_inst);
+}
+
+/*
+ * DESCRIPTORS ... most are static, but strings and full
+ * configuration descriptors are built on demand.
+ */
+
+/*
+ * We have three interfaces - one AudioControl and two AudioStreaming
+ *
+ * The driver implements a simple UAC_1 topology.
+ * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture
+ * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN
+ */
+#define F_AUDIO_AC_INTERFACE		0
+#define F_AUDIO_AS_OUT_INTERFACE	1
+#define F_AUDIO_AS_IN_INTERFACE		2
+/* Number of streaming interfaces */
+#define F_AUDIO_NUM_INTERFACES		2
+
+/* B.3.1  Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+};
+
+/*
+ * The number of AudioStreaming and MIDIStreaming interfaces
+ * in the Audio Interface Collection
+ */
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
+
+#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
+/* 2 input terminals and 2 output terminals */
+#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
+	+ 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)
+/* B.3.2  Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_2 ac_header_desc = {
+	.bLength =		UAC_DT_AC_HEADER_LENGTH,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_HEADER,
+	.bcdADC =		cpu_to_le16(0x0100),
+	.wTotalLength =		cpu_to_le16(UAC_DT_TOTAL_LENGTH),
+	.bInCollection =	F_AUDIO_NUM_INTERFACES,
+	.baInterfaceNr = {
+	/* Interface number of the AudioStream interfaces */
+		[0] =		1,
+		[1] =		2,
+	}
+};
+
+#define USB_OUT_IT_ID	1
+static struct uac_input_terminal_descriptor usb_out_it_desc = {
+	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_INPUT_TERMINAL,
+	.bTerminalID =		USB_OUT_IT_ID,
+	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal =	0,
+	.wChannelConfig =	cpu_to_le16(0x3),
+};
+
+#define IO_OUT_OT_ID	2
+static struct uac1_output_terminal_descriptor io_out_ot_desc = {
+	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL,
+	.bTerminalID		= IO_OUT_OT_ID,
+	.wTerminalType		= cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
+	.bAssocTerminal		= 0,
+	.bSourceID		= USB_OUT_IT_ID,
+};
+
+#define IO_IN_IT_ID	3
+static struct uac_input_terminal_descriptor io_in_it_desc = {
+	.bLength		= UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_INPUT_TERMINAL,
+	.bTerminalID		= IO_IN_IT_ID,
+	.wTerminalType		= cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
+	.bAssocTerminal		= 0,
+	.wChannelConfig		= cpu_to_le16(0x3),
+};
+
+#define USB_IN_OT_ID	4
+static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
+	.bLength =		UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_OUTPUT_TERMINAL,
+	.bTerminalID =		USB_IN_OT_ID,
+	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal =	0,
+	.bSourceID =		IO_IN_IT_ID,
+};
+
+/* B.4.1  Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_out_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_out_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_in_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_in_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2  Class-Specific AS Interface Descriptor */
+static struct uac1_as_header_descriptor as_out_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	USB_OUT_IT_ID,
+	.bDelay =		1,
+	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
+};
+
+static struct uac1_as_header_descriptor as_in_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	USB_IN_OT_ID,
+	.bDelay =		1,
+	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
+};
+
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+
+static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor as_out_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_SYNC_ADAPTIVE
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize	=	cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+	.bInterval =		4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes =		1,
+	.bLockDelayUnits =	1,
+	.wLockDelay =		cpu_to_le16(1),
+};
+
+static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor as_in_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_SYNC_ASYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize	=	cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+	.bInterval =		4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes =		1,
+	.bLockDelayUnits =	0,
+	.wLockDelay =		0,
+};
+
+static struct usb_descriptor_header *f_audio_desc[] = {
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+
+	(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_out_header_desc,
+
+	(struct usb_descriptor_header *)&as_out_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_out_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_in_header_desc,
+
+	(struct usb_descriptor_header *)&as_in_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_in_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+enum {
+	STR_AC_IF,
+	STR_USB_OUT_IT,
+	STR_USB_OUT_IT_CH_NAMES,
+	STR_IO_OUT_OT,
+	STR_IO_IN_IT,
+	STR_IO_IN_IT_CH_NAMES,
+	STR_USB_IN_OT,
+	STR_AS_OUT_IF_ALT0,
+	STR_AS_OUT_IF_ALT1,
+	STR_AS_IN_IF_ALT0,
+	STR_AS_IN_IF_ALT1,
+};
+
+static struct usb_string strings_uac1[] = {
+	[STR_AC_IF].s = "AC Interface",
+	[STR_USB_OUT_IT].s = "Playback Input terminal",
+	[STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
+	[STR_IO_OUT_OT].s = "Playback Output terminal",
+	[STR_IO_IN_IT].s = "Capture Input terminal",
+	[STR_IO_IN_IT_CH_NAMES].s = "Capture Channels",
+	[STR_USB_IN_OT].s = "Capture Output terminal",
+	[STR_AS_OUT_IF_ALT0].s = "Playback Inactive",
+	[STR_AS_OUT_IF_ALT1].s = "Playback Active",
+	[STR_AS_IN_IF_ALT0].s = "Capture Inactive",
+	[STR_AS_IN_IF_ALT1].s = "Capture Active",
+	{ },
+};
+
+static struct usb_gadget_strings str_uac1 = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_uac1,
+};
+
+static struct usb_gadget_strings *uac1_strings[] = {
+	&str_uac1,
+	NULL,
+};
+
+/*
+ * This function is an ALSA sound card following USB Audio Class Spec 1.0.
+ */
+
+static int audio_set_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int			value = -EOPNOTSUPP;
+	u16			ep = le16_to_cpu(ctrl->wIndex);
+	u16			len = le16_to_cpu(ctrl->wLength);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_SET_CUR:
+		value = len;
+		break;
+
+	case UAC_SET_MIN:
+		break;
+
+	case UAC_SET_MAX:
+		break;
+
+	case UAC_SET_RES:
+		break;
+
+	case UAC_SET_MEM:
+		break;
+
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int audio_get_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int value = -EOPNOTSUPP;
+	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16 len = le16_to_cpu(ctrl->wLength);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_GET_CUR:
+	case UAC_GET_MIN:
+	case UAC_GET_MAX:
+	case UAC_GET_RES:
+		value = len;
+		break;
+	case UAC_GET_MEM:
+		break;
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int
+f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything; interface
+	 * activation uses set_alt().
+	 */
+	switch (ctrl->bRequestType) {
+	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_set_endpoint_req(f, ctrl);
+		break;
+
+	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_get_endpoint_req(f, ctrl);
+		break;
+
+	default:
+		ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "audio response on err %d\n", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	struct f_uac1 *uac1 = func_to_uac1(f);
+	int ret = 0;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == uac1->ac_intf) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (intf == uac1->as_out_intf) {
+		uac1->as_out_alt = alt;
+
+		if (alt)
+			ret = u_audio_start_capture(&uac1->g_audio);
+		else
+			u_audio_stop_capture(&uac1->g_audio);
+	} else if (intf == uac1->as_in_intf) {
+		uac1->as_in_alt = alt;
+
+		if (alt)
+			ret = u_audio_start_playback(&uac1->g_audio);
+		else
+			u_audio_stop_playback(&uac1->g_audio);
+	} else {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int f_audio_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	struct f_uac1 *uac1 = func_to_uac1(f);
+
+	if (intf == uac1->ac_intf)
+		return uac1->ac_alt;
+	else if (intf == uac1->as_out_intf)
+		return uac1->as_out_alt;
+	else if (intf == uac1->as_in_intf)
+		return uac1->as_in_alt;
+	else
+		dev_err(dev, "%s:%d Invalid Interface %d!\n",
+			__func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+
+static void f_audio_disable(struct usb_function *f)
+{
+	struct f_uac1 *uac1 = func_to_uac1(f);
+
+	uac1->as_out_alt = 0;
+	uac1->as_in_alt = 0;
+
+	u_audio_stop_playback(&uac1->g_audio);
+	u_audio_stop_capture(&uac1->g_audio);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
+{
+	struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+
+	if (!opts->p_chmask && !opts->c_chmask) {
+		dev_err(dev, "Error: no playback and capture channels\n");
+		return -EINVAL;
+	} else if (opts->p_chmask & ~UAC1_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported playback channels mask\n");
+		return -EINVAL;
+	} else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported capture channels mask\n");
+		return -EINVAL;
+	} else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
+		dev_err(dev, "Error: incorrect playback sample size\n");
+		return -EINVAL;
+	} else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
+		dev_err(dev, "Error: incorrect capture sample size\n");
+		return -EINVAL;
+	} else if (!opts->p_srate) {
+		dev_err(dev, "Error: incorrect playback sampling rate\n");
+		return -EINVAL;
+	} else if (!opts->c_srate) {
+		dev_err(dev, "Error: incorrect capture sampling rate\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* audio function driver setup/binding */
+static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev	*cdev = c->cdev;
+	struct usb_gadget		*gadget = cdev->gadget;
+	struct device			*dev = &gadget->dev;
+	struct f_uac1			*uac1 = func_to_uac1(f);
+	struct g_audio			*audio = func_to_g_audio(f);
+	struct f_uac1_opts		*audio_opts;
+	struct usb_ep			*ep = NULL;
+	struct usb_string		*us;
+	u8				*sam_freq;
+	int				rate;
+	int				status;
+
+	status = f_audio_validate_opts(audio, dev);
+	if (status)
+		return status;
+
+	audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+
+	us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	ac_interface_desc.iInterface = us[STR_AC_IF].id;
+	usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
+	usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
+	io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id;
+	as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id;
+	as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id;
+	io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id;
+	io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id;
+	usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id;
+	as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
+	as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
+
+	/* Set channel numbers */
+	usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+	usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask);
+	as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+	as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize;
+	as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8;
+	io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+	io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask);
+	as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+	as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize;
+	as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
+
+	/* Set sample rates */
+	rate = audio_opts->c_srate;
+	sam_freq = as_out_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+	rate = audio_opts->p_srate;
+	sam_freq = as_in_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ac_interface_desc.bInterfaceNumber = status;
+	uac1->ac_intf = status;
+	uac1->ac_alt = 0;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_out_interface_alt_0_desc.bInterfaceNumber = status;
+	as_out_interface_alt_1_desc.bInterfaceNumber = status;
+	ac_header_desc.baInterfaceNr[0] = status;
+	uac1->as_out_intf = status;
+	uac1->as_out_alt = 0;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_in_interface_alt_0_desc.bInterfaceNumber = status;
+	as_in_interface_alt_1_desc.bInterfaceNumber = status;
+	ac_header_desc.baInterfaceNr[1] = status;
+	uac1->as_in_intf = status;
+	uac1->as_in_alt = 0;
+
+	audio->gadget = gadget;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->out_ep = ep;
+	audio->out_ep->desc = &as_out_ep_desc;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->in_ep = ep;
+	audio->in_ep->desc = &as_in_ep_desc;
+
+	/* copy descriptors, and track endpoint copies */
+	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
+					NULL);
+	if (status)
+		goto fail;
+
+	audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
+	audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
+	audio->params.c_chmask = audio_opts->c_chmask;
+	audio->params.c_srate = audio_opts->c_srate;
+	audio->params.c_ssize = audio_opts->c_ssize;
+	audio->params.p_chmask = audio_opts->p_chmask;
+	audio->params.p_srate = audio_opts->p_srate;
+	audio->params.p_ssize = audio_opts->p_ssize;
+	audio->params.req_number = audio_opts->req_number;
+
+	status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget");
+	if (status)
+		goto err_card_register;
+
+	return 0;
+
+err_card_register:
+	usb_free_all_descriptors(f);
+fail:
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_uac1_opts,
+			    func_inst.group);
+}
+
+static void f_uac1_attr_release(struct config_item *item)
+{
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_uac1_item_ops = {
+	.release	= f_uac1_attr_release,
+};
+
+#define UAC1_ATTRIBUTE(name)						\
+static ssize_t f_uac1_opts_##name##_show(				\
+					  struct config_item *item,	\
+					  char *page)			\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac1_opts_##name##_store(				\
+					  struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac1_opts *opts = to_f_uac1_opts(item);		\
+	int ret;							\
+	u32 num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou32(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_ATTRIBUTE(c_chmask);
+UAC1_ATTRIBUTE(c_srate);
+UAC1_ATTRIBUTE(c_ssize);
+UAC1_ATTRIBUTE(p_chmask);
+UAC1_ATTRIBUTE(p_srate);
+UAC1_ATTRIBUTE(p_ssize);
+UAC1_ATTRIBUTE(req_number);
+
+static struct configfs_attribute *f_uac1_attrs[] = {
+	&f_uac1_opts_attr_c_chmask,
+	&f_uac1_opts_attr_c_srate,
+	&f_uac1_opts_attr_c_ssize,
+	&f_uac1_opts_attr_p_chmask,
+	&f_uac1_opts_attr_p_srate,
+	&f_uac1_opts_attr_p_ssize,
+	&f_uac1_opts_attr_req_number,
+	NULL,
+};
+
+static const struct config_item_type f_uac1_func_type = {
+	.ct_item_ops	= &f_uac1_item_ops,
+	.ct_attrs	= f_uac1_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_audio_free_inst(struct usb_function_instance *f)
+{
+	struct f_uac1_opts *opts;
+
+	opts = container_of(f, struct f_uac1_opts, func_inst);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_audio_alloc_inst(void)
+{
+	struct f_uac1_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_audio_free_inst;
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &f_uac1_func_type);
+
+	opts->c_chmask = UAC1_DEF_CCHMASK;
+	opts->c_srate = UAC1_DEF_CSRATE;
+	opts->c_ssize = UAC1_DEF_CSSIZE;
+	opts->p_chmask = UAC1_DEF_PCHMASK;
+	opts->p_srate = UAC1_DEF_PSRATE;
+	opts->p_ssize = UAC1_DEF_PSSIZE;
+	opts->req_number = UAC1_DEF_REQ_NUM;
+	return &opts->func_inst;
+}
+
+static void f_audio_free(struct usb_function *f)
+{
+	struct g_audio *audio;
+	struct f_uac1_opts *opts;
+
+	audio = func_to_g_audio(f);
+	opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+	kfree(audio);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct g_audio *audio = func_to_g_audio(f);
+
+	g_audio_cleanup(audio);
+	usb_free_all_descriptors(f);
+
+	audio->gadget = NULL;
+}
+
+static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
+{
+	struct f_uac1 *uac1;
+	struct f_uac1_opts *opts;
+
+	/* allocate and initialize one new instance */
+	uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL);
+	if (!uac1)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_uac1_opts, func_inst);
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	uac1->g_audio.func.name = "uac1_func";
+	uac1->g_audio.func.bind = f_audio_bind;
+	uac1->g_audio.func.unbind = f_audio_unbind;
+	uac1->g_audio.func.set_alt = f_audio_set_alt;
+	uac1->g_audio.func.get_alt = f_audio_get_alt;
+	uac1->g_audio.func.setup = f_audio_setup;
+	uac1->g_audio.func.disable = f_audio_disable;
+	uac1->g_audio.func.free_func = f_audio_free;
+
+	return &uac1->g_audio.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_uac1_legacy.c b/marvell/linux/drivers/usb/gadget/function/f_uac1_legacy.c
new file mode 100644
index 0000000..06ee6e9
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -0,0 +1,1020 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_audio.c -- USB Audio class function driver
+  *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/atomic.h>
+
+#include "u_uac1_legacy.h"
+
+static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
+static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
+
+/*
+ * DESCRIPTORS ... most are static, but strings and full
+ * configuration descriptors are built on demand.
+ */
+
+/*
+ * We have two interfaces- AudioControl and AudioStreaming
+ * TODO: only supcard playback currently
+ */
+#define F_AUDIO_AC_INTERFACE	0
+#define F_AUDIO_AS_INTERFACE	1
+#define F_AUDIO_NUM_INTERFACES	1
+
+/* B.3.1  Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
+};
+
+/*
+ * The number of AudioStreaming and MIDIStreaming interfaces
+ * in the Audio Interface Collection
+ */
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
+
+#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
+/* 1 input terminal, 1 output terminal and 1 feature unit */
+#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \
+	+ UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0))
+/* B.3.2  Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_1 ac_header_desc = {
+	.bLength =		UAC_DT_AC_HEADER_LENGTH,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_HEADER,
+	.bcdADC =		cpu_to_le16(0x0100),
+	.wTotalLength =		cpu_to_le16(UAC_DT_TOTAL_LENGTH),
+	.bInCollection =	F_AUDIO_NUM_INTERFACES,
+	.baInterfaceNr = {
+	/* Interface number of the first AudioStream interface */
+		[0] =		1,
+	}
+};
+
+#define INPUT_TERMINAL_ID	1
+static struct uac_input_terminal_descriptor input_terminal_desc = {
+	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_INPUT_TERMINAL,
+	.bTerminalID =		INPUT_TERMINAL_ID,
+	.wTerminalType =	UAC_TERMINAL_STREAMING,
+	.bAssocTerminal =	0,
+	.wChannelConfig =	0x3,
+};
+
+DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
+
+#define FEATURE_UNIT_ID		2
+static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
+	.bLength		= UAC_DT_FEATURE_UNIT_SIZE(0),
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_FEATURE_UNIT,
+	.bUnitID		= FEATURE_UNIT_ID,
+	.bSourceID		= INPUT_TERMINAL_ID,
+	.bControlSize		= 2,
+	.bmaControls[0]		= (UAC_FU_MUTE | UAC_FU_VOLUME),
+};
+
+static struct usb_audio_control mute_control = {
+	.list = LIST_HEAD_INIT(mute_control.list),
+	.name = "Mute Control",
+	.type = UAC_FU_MUTE,
+	/* Todo: add real Mute control code */
+	.set = generic_set_cmd,
+	.get = generic_get_cmd,
+};
+
+static struct usb_audio_control volume_control = {
+	.list = LIST_HEAD_INIT(volume_control.list),
+	.name = "Volume Control",
+	.type = UAC_FU_VOLUME,
+	/* Todo: add real Volume control code */
+	.set = generic_set_cmd,
+	.get = generic_get_cmd,
+};
+
+static struct usb_audio_control_selector feature_unit = {
+	.list = LIST_HEAD_INIT(feature_unit.list),
+	.id = FEATURE_UNIT_ID,
+	.name = "Mute & Volume Control",
+	.type = UAC_FEATURE_UNIT,
+	.desc = (struct usb_descriptor_header *)&feature_unit_desc,
+};
+
+#define OUTPUT_TERMINAL_ID	3
+static struct uac1_output_terminal_descriptor output_terminal_desc = {
+	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,
+	.bDescriptorType	= USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL,
+	.bTerminalID		= OUTPUT_TERMINAL_ID,
+	.wTerminalType		= UAC_OUTPUT_TERMINAL_SPEAKER,
+	.bAssocTerminal		= FEATURE_UNIT_ID,
+	.bSourceID		= FEATURE_UNIT_ID,
+};
+
+/* B.4.1  Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_interface_alt_0_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_interface_alt_1_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_AUDIO,
+	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2  Class-Specific AS Interface Descriptor */
+static struct uac1_as_header_descriptor as_header_desc = {
+	.bLength =		UAC_DT_AS_HEADER_SIZE,
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_AS_GENERAL,
+	.bTerminalLink =	INPUT_TERMINAL_ID,
+	.bDelay =		1,
+	.wFormatTag =		UAC_FORMAT_TYPE_I_PCM,
+};
+
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+
+static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = {
+	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype =	UAC_FORMAT_TYPE,
+	.bFormatType =		UAC_FORMAT_TYPE_I,
+	.bSubframeSize =	2,
+	.bBitResolution =	16,
+	.bSamFreqType =		1,
+};
+
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor as_out_ep_desc  = {
+	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_SYNC_ADAPTIVE
+				| USB_ENDPOINT_XFER_ISOC,
+	.wMaxPacketSize	=	cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+	.bInterval =		4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE,
+	.bDescriptorType =	USB_DT_CS_ENDPOINT,
+	.bDescriptorSubtype =	UAC_EP_GENERAL,
+	.bmAttributes = 	1,
+	.bLockDelayUnits =	1,
+	.wLockDelay =		cpu_to_le16(1),
+};
+
+static struct usb_descriptor_header *f_audio_desc[] = {
+	(struct usb_descriptor_header *)&ac_interface_desc,
+	(struct usb_descriptor_header *)&ac_header_desc,
+
+	(struct usb_descriptor_header *)&input_terminal_desc,
+	(struct usb_descriptor_header *)&output_terminal_desc,
+	(struct usb_descriptor_header *)&feature_unit_desc,
+
+	(struct usb_descriptor_header *)&as_interface_alt_0_desc,
+	(struct usb_descriptor_header *)&as_interface_alt_1_desc,
+	(struct usb_descriptor_header *)&as_header_desc,
+
+	(struct usb_descriptor_header *)&as_type_i_desc,
+
+	(struct usb_descriptor_header *)&as_out_ep_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+	NULL,
+};
+
+enum {
+	STR_AC_IF,
+	STR_INPUT_TERMINAL,
+	STR_INPUT_TERMINAL_CH_NAMES,
+	STR_FEAT_DESC_0,
+	STR_OUTPUT_TERMINAL,
+	STR_AS_IF_ALT0,
+	STR_AS_IF_ALT1,
+};
+
+static struct usb_string strings_uac1[] = {
+	[STR_AC_IF].s = "AC Interface",
+	[STR_INPUT_TERMINAL].s = "Input terminal",
+	[STR_INPUT_TERMINAL_CH_NAMES].s = "Channels",
+	[STR_FEAT_DESC_0].s = "Volume control & mute",
+	[STR_OUTPUT_TERMINAL].s = "Output terminal",
+	[STR_AS_IF_ALT0].s = "AS Interface",
+	[STR_AS_IF_ALT1].s = "AS Interface",
+	{ },
+};
+
+static struct usb_gadget_strings str_uac1 = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_uac1,
+};
+
+static struct usb_gadget_strings *uac1_strings[] = {
+	&str_uac1,
+	NULL,
+};
+
+/*
+ * This function is an ALSA sound card following USB Audio Class Spec 1.0.
+ */
+
+/*-------------------------------------------------------------------------*/
+struct f_audio_buf {
+	u8 *buf;
+	int actual;
+	struct list_head list;
+};
+
+static struct f_audio_buf *f_audio_buffer_alloc(int buf_size)
+{
+	struct f_audio_buf *copy_buf;
+
+	copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC);
+	if (!copy_buf)
+		return ERR_PTR(-ENOMEM);
+
+	copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC);
+	if (!copy_buf->buf) {
+		kfree(copy_buf);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return copy_buf;
+}
+
+static void f_audio_buffer_free(struct f_audio_buf *audio_buf)
+{
+	kfree(audio_buf->buf);
+	kfree(audio_buf);
+}
+/*-------------------------------------------------------------------------*/
+
+struct f_audio {
+	struct gaudio			card;
+
+	u8 ac_intf, ac_alt;
+	u8 as_intf, as_alt;
+
+	/* endpoints handle full and/or high speeds */
+	struct usb_ep			*out_ep;
+
+	spinlock_t			lock;
+	struct f_audio_buf *copy_buf;
+	struct work_struct playback_work;
+	struct list_head play_queue;
+
+	/* Control Set command */
+	struct list_head cs;
+	u8 set_cmd;
+	struct usb_audio_control *set_con;
+};
+
+static inline struct f_audio *func_to_audio(struct usb_function *f)
+{
+	return container_of(f, struct f_audio, card.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void f_audio_playback_work(struct work_struct *data)
+{
+	struct f_audio *audio = container_of(data, struct f_audio,
+					playback_work);
+	struct f_audio_buf *play_buf;
+
+	spin_lock_irq(&audio->lock);
+	if (list_empty(&audio->play_queue)) {
+		spin_unlock_irq(&audio->lock);
+		return;
+	}
+	play_buf = list_first_entry(&audio->play_queue,
+			struct f_audio_buf, list);
+	list_del(&play_buf->list);
+	spin_unlock_irq(&audio->lock);
+
+	u_audio_playback(&audio->card, play_buf->buf, play_buf->actual);
+	f_audio_buffer_free(play_buf);
+}
+
+static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_audio *audio = req->context;
+	struct usb_composite_dev *cdev = audio->card.func.config->cdev;
+	struct f_audio_buf *copy_buf = audio->copy_buf;
+	struct f_uac1_legacy_opts *opts;
+	int audio_buf_size;
+	int err;
+
+	opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts,
+			    func_inst);
+	audio_buf_size = opts->audio_buf_size;
+
+	if (!copy_buf)
+		return -EINVAL;
+
+	/* Copy buffer is full, add it to the play_queue */
+	if (audio_buf_size - copy_buf->actual < req->actual) {
+		spin_lock_irq(&audio->lock);
+		list_add_tail(&copy_buf->list, &audio->play_queue);
+		spin_unlock_irq(&audio->lock);
+		schedule_work(&audio->playback_work);
+		copy_buf = f_audio_buffer_alloc(audio_buf_size);
+		if (IS_ERR(copy_buf))
+			return -ENOMEM;
+	}
+
+	memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual);
+	copy_buf->actual += req->actual;
+	audio->copy_buf = copy_buf;
+
+	err = usb_ep_queue(ep, req, GFP_ATOMIC);
+	if (err)
+		ERROR(cdev, "%s queue req: %d\n", ep->name, err);
+
+	return 0;
+
+}
+
+static void f_audio_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_audio *audio = req->context;
+	int status = req->status;
+	u32 data = 0;
+	struct usb_ep *out_ep = audio->out_ep;
+
+	switch (status) {
+
+	case 0:				/* normal completion? */
+		if (ep == out_ep)
+			f_audio_out_ep_complete(ep, req);
+		else if (audio->set_con) {
+			memcpy(&data, req->buf, req->length);
+			audio->set_con->set(audio->set_con, audio->set_cmd,
+					le16_to_cpu(data));
+			audio->set_con = NULL;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int audio_set_intf_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct f_audio		*audio = func_to_audio(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	u8			id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16			len = le16_to_cpu(ctrl->wLength);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u8			con_sel = (w_value >> 8) & 0xFF;
+	u8			cmd = (ctrl->bRequest & 0x0F);
+	struct usb_audio_control_selector *cs;
+	struct usb_audio_control *con;
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
+			ctrl->bRequest, w_value, len, id);
+
+	list_for_each_entry(cs, &audio->cs, list) {
+		if (cs->id == id) {
+			list_for_each_entry(con, &cs->control, list) {
+				if (con->type == con_sel) {
+					audio->set_con = con;
+					break;
+				}
+			}
+			break;
+		}
+	}
+
+	audio->set_cmd = cmd;
+	req->context = audio;
+	req->complete = f_audio_complete;
+
+	return len;
+}
+
+static int audio_get_intf_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct f_audio		*audio = func_to_audio(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u8			id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16			len = le16_to_cpu(ctrl->wLength);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u8			con_sel = (w_value >> 8) & 0xFF;
+	u8			cmd = (ctrl->bRequest & 0x0F);
+	struct usb_audio_control_selector *cs;
+	struct usb_audio_control *con;
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
+			ctrl->bRequest, w_value, len, id);
+
+	list_for_each_entry(cs, &audio->cs, list) {
+		if (cs->id == id) {
+			list_for_each_entry(con, &cs->control, list) {
+				if (con->type == con_sel && con->get) {
+					value = con->get(con, cmd);
+					break;
+				}
+			}
+			break;
+		}
+	}
+
+	req->context = audio;
+	req->complete = f_audio_complete;
+	len = min_t(size_t, sizeof(value), len);
+	memcpy(req->buf, &value, len);
+
+	return len;
+}
+
+static int audio_set_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int			value = -EOPNOTSUPP;
+	u16			ep = le16_to_cpu(ctrl->wIndex);
+	u16			len = le16_to_cpu(ctrl->wLength);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_SET_CUR:
+		value = len;
+		break;
+
+	case UAC_SET_MIN:
+		break;
+
+	case UAC_SET_MAX:
+		break;
+
+	case UAC_SET_RES:
+		break;
+
+	case UAC_SET_MEM:
+		break;
+
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int audio_get_endpoint_req(struct usb_function *f,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	int value = -EOPNOTSUPP;
+	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+	u16 len = le16_to_cpu(ctrl->wLength);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+
+	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+			ctrl->bRequest, w_value, len, ep);
+
+	switch (ctrl->bRequest) {
+	case UAC_GET_CUR:
+	case UAC_GET_MIN:
+	case UAC_GET_MAX:
+	case UAC_GET_RES:
+		value = len;
+		break;
+	case UAC_GET_MEM:
+		break;
+	default:
+		break;
+	}
+
+	return value;
+}
+
+static int
+f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything; interface
+	 * activation uses set_alt().
+	 */
+	switch (ctrl->bRequestType) {
+	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+		value = audio_set_intf_req(f, ctrl);
+		break;
+
+	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
+		value = audio_get_intf_req(f, ctrl);
+		break;
+
+	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_set_endpoint_req(f, ctrl);
+		break;
+
+	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+		value = audio_get_endpoint_req(f, ctrl);
+		break;
+
+	default:
+		ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "audio response on err %d\n", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct f_audio		*audio = func_to_audio(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_ep *out_ep = audio->out_ep;
+	struct usb_request *req;
+	struct f_uac1_legacy_opts *opts;
+	int req_buf_size, req_count, audio_buf_size;
+	int i = 0, err = 0;
+
+	DBG(cdev, "intf %d, alt %d\n", intf, alt);
+
+	opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+	req_buf_size = opts->req_buf_size;
+	req_count = opts->req_count;
+	audio_buf_size = opts->audio_buf_size;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == audio->ac_intf) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	} else if (intf == audio->as_intf) {
+		if (alt == 1) {
+			err = config_ep_by_speed(cdev->gadget, f, out_ep);
+			if (err)
+				return err;
+
+			usb_ep_enable(out_ep);
+			audio->copy_buf = f_audio_buffer_alloc(audio_buf_size);
+			if (IS_ERR(audio->copy_buf))
+				return -ENOMEM;
+
+			/*
+			 * allocate a bunch of read buffers
+			 * and queue them all at once.
+			 */
+			for (i = 0; i < req_count && err == 0; i++) {
+				req = usb_ep_alloc_request(out_ep, GFP_ATOMIC);
+				if (req) {
+					req->buf = kzalloc(req_buf_size,
+							GFP_ATOMIC);
+					if (req->buf) {
+						req->length = req_buf_size;
+						req->context = audio;
+						req->complete =
+							f_audio_complete;
+						err = usb_ep_queue(out_ep,
+							req, GFP_ATOMIC);
+						if (err)
+							ERROR(cdev,
+							"%s queue req: %d\n",
+							out_ep->name, err);
+					} else
+						err = -ENOMEM;
+				} else
+					err = -ENOMEM;
+			}
+
+		} else {
+			struct f_audio_buf *copy_buf = audio->copy_buf;
+			if (copy_buf) {
+				list_add_tail(&copy_buf->list,
+						&audio->play_queue);
+				schedule_work(&audio->playback_work);
+			}
+		}
+		audio->as_alt = alt;
+	}
+
+	return err;
+}
+
+static int f_audio_get_alt(struct usb_function *f, unsigned intf)
+{
+	struct f_audio		*audio = func_to_audio(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	if (intf == audio->ac_intf)
+		return audio->ac_alt;
+	else if (intf == audio->as_intf)
+		return audio->as_alt;
+	else
+		ERROR(cdev, "%s:%d Invalid Interface %d!\n",
+		      __func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+static void f_audio_disable(struct usb_function *f)
+{
+	return;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void f_audio_build_desc(struct f_audio *audio)
+{
+	struct gaudio *card = &audio->card;
+	u8 *sam_freq;
+	int rate;
+
+	/* Set channel numbers */
+	input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card);
+	as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card);
+
+	/* Set sample rates */
+	rate = u_audio_get_playback_rate(card);
+	sam_freq = as_type_i_desc.tSamFreq[0];
+	memcpy(sam_freq, &rate, 3);
+
+	/* Todo: Set Sample bits and other parameters */
+
+	return;
+}
+
+/* audio function driver setup/binding */
+static int
+f_audio_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_audio		*audio = func_to_audio(f);
+	struct usb_string	*us;
+	int			status;
+	struct usb_ep		*ep = NULL;
+	struct f_uac1_legacy_opts	*audio_opts;
+
+	audio_opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+	audio->card.gadget = c->cdev->gadget;
+	/* set up ASLA audio devices */
+	if (!audio_opts->bound) {
+		status = gaudio_setup(&audio->card);
+		if (status < 0)
+			return status;
+		audio_opts->bound = true;
+	}
+	us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	ac_interface_desc.iInterface = us[STR_AC_IF].id;
+	input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id;
+	input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id;
+	feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id;
+	output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id;
+	as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id;
+	as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id;
+
+
+	f_audio_build_desc(audio);
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	ac_interface_desc.bInterfaceNumber = status;
+	audio->ac_intf = status;
+	audio->ac_alt = 0;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	as_interface_alt_0_desc.bInterfaceNumber = status;
+	as_interface_alt_1_desc.bInterfaceNumber = status;
+	audio->as_intf = status;
+	audio->as_alt = 0;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+	if (!ep)
+		goto fail;
+	audio->out_ep = ep;
+	audio->out_ep->desc = &as_out_ep_desc;
+
+	status = -ENOMEM;
+
+	/* copy descriptors, and track endpoint copies */
+	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
+					NULL);
+	if (status)
+		goto fail;
+	return 0;
+
+fail:
+	gaudio_cleanup(&audio->card);
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
+{
+	con->data[cmd] = value;
+
+	return 0;
+}
+
+static int generic_get_cmd(struct usb_audio_control *con, u8 cmd)
+{
+	return con->data[cmd];
+}
+
+/* Todo: add more control selecotor dynamically */
+static int control_selector_init(struct f_audio *audio)
+{
+	INIT_LIST_HEAD(&audio->cs);
+	list_add(&feature_unit.list, &audio->cs);
+
+	INIT_LIST_HEAD(&feature_unit.control);
+	list_add(&mute_control.list, &feature_unit.control);
+	list_add(&volume_control.list, &feature_unit.control);
+
+	volume_control.data[UAC__CUR] = 0xffc0;
+	volume_control.data[UAC__MIN] = 0xe3a0;
+	volume_control.data[UAC__MAX] = 0xfff0;
+	volume_control.data[UAC__RES] = 0x0030;
+
+	return 0;
+}
+
+static inline
+struct f_uac1_legacy_opts *to_f_uac1_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_uac1_legacy_opts,
+			    func_inst.group);
+}
+
+static void f_uac1_attr_release(struct config_item *item)
+{
+	struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_uac1_item_ops = {
+	.release	= f_uac1_attr_release,
+};
+
+#define UAC1_INT_ATTRIBUTE(name)					\
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item,	\
+					 char *page)			\
+{									\
+	struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item,		\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);		\
+	int ret;							\
+	u32 num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou32(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_INT_ATTRIBUTE(req_buf_size);
+UAC1_INT_ATTRIBUTE(req_count);
+UAC1_INT_ATTRIBUTE(audio_buf_size);
+
+#define UAC1_STR_ATTRIBUTE(name)					\
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item,	\
+					 char *page)			\
+{									\
+	struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%s\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);		\
+	int ret = -EBUSY;						\
+	char *tmp;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt)						\
+		goto end;						\
+									\
+	tmp = kstrndup(page, len, GFP_KERNEL);				\
+	if (tmp) {							\
+		ret = -ENOMEM;						\
+		goto end;						\
+	}								\
+	if (opts->name##_alloc)						\
+		kfree(opts->name);					\
+	opts->name##_alloc = true;					\
+	opts->name = tmp;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_STR_ATTRIBUTE(fn_play);
+UAC1_STR_ATTRIBUTE(fn_cap);
+UAC1_STR_ATTRIBUTE(fn_cntl);
+
+static struct configfs_attribute *f_uac1_attrs[] = {
+	&f_uac1_opts_attr_req_buf_size,
+	&f_uac1_opts_attr_req_count,
+	&f_uac1_opts_attr_audio_buf_size,
+	&f_uac1_opts_attr_fn_play,
+	&f_uac1_opts_attr_fn_cap,
+	&f_uac1_opts_attr_fn_cntl,
+	NULL,
+};
+
+static const struct config_item_type f_uac1_func_type = {
+	.ct_item_ops	= &f_uac1_item_ops,
+	.ct_attrs	= f_uac1_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_audio_free_inst(struct usb_function_instance *f)
+{
+	struct f_uac1_legacy_opts *opts;
+
+	opts = container_of(f, struct f_uac1_legacy_opts, func_inst);
+	if (opts->fn_play_alloc)
+		kfree(opts->fn_play);
+	if (opts->fn_cap_alloc)
+		kfree(opts->fn_cap);
+	if (opts->fn_cntl_alloc)
+		kfree(opts->fn_cntl);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_audio_alloc_inst(void)
+{
+	struct f_uac1_legacy_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_audio_free_inst;
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &f_uac1_func_type);
+
+	opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
+	opts->req_count = UAC1_REQ_COUNT;
+	opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE;
+	opts->fn_play = FILE_PCM_PLAYBACK;
+	opts->fn_cap = FILE_PCM_CAPTURE;
+	opts->fn_cntl = FILE_CONTROL;
+	return &opts->func_inst;
+}
+
+static void f_audio_free(struct usb_function *f)
+{
+	struct f_audio *audio = func_to_audio(f);
+	struct f_uac1_legacy_opts *opts;
+
+	gaudio_cleanup(&audio->card);
+	opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+	kfree(audio);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
+{
+	struct f_audio *audio;
+	struct f_uac1_legacy_opts *opts;
+
+	/* allocate and initialize one new instance */
+	audio = kzalloc(sizeof(*audio), GFP_KERNEL);
+	if (!audio)
+		return ERR_PTR(-ENOMEM);
+
+	audio->card.func.name = "g_audio";
+
+	opts = container_of(fi, struct f_uac1_legacy_opts, func_inst);
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+	INIT_LIST_HEAD(&audio->play_queue);
+	spin_lock_init(&audio->lock);
+
+	audio->card.func.bind = f_audio_bind;
+	audio->card.func.unbind = f_audio_unbind;
+	audio->card.func.set_alt = f_audio_set_alt;
+	audio->card.func.get_alt = f_audio_get_alt;
+	audio->card.func.setup = f_audio_setup;
+	audio->card.func.disable = f_audio_disable;
+	audio->card.func.free_func = f_audio_free;
+
+	control_selector_init(audio);
+
+	INIT_WORK(&audio->playback_work, f_audio_playback_work);
+
+	return &audio->card.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bryan Wu");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_uac2.c b/marvell/linux/drivers/usb/gadget/function/f_uac2.c
new file mode 100644
index 0000000..11cc605
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_uac2.c
@@ -0,0 +1,1213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * f_uac2.c -- USB Audio Class 2.0 Function
+ *
+ * Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01@gmail.com)
+ *    Jaswinder Singh (jaswinder.singh@linaro.org)
+ */
+
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/module.h>
+
+#include "u_audio.h"
+#include "u_uac2.h"
+
+/* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */
+#define UAC2_CHANNEL_MASK 0x07FFFFFF
+
+/*
+ * The driver implements a simple UAC_2 topology.
+ * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
+ * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
+ * Capture and Playback sampling rates are independently
+ *  controlled by two clock sources :
+ *    CLK_5 := c_srate, and CLK_6 := p_srate
+ */
+#define USB_OUT_CLK_ID	(out_clk_src_desc.bClockID)
+#define USB_IN_CLK_ID	(in_clk_src_desc.bClockID)
+
+#define CONTROL_ABSENT	0
+#define CONTROL_RDONLY	1
+#define CONTROL_RDWR	3
+
+#define CLK_FREQ_CTRL	0
+#define CLK_VLD_CTRL	2
+
+#define COPY_CTRL	0
+#define CONN_CTRL	2
+#define OVRLD_CTRL	4
+#define CLSTR_CTRL	6
+#define UNFLW_CTRL	8
+#define OVFLW_CTRL	10
+
+#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
+#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+
+struct f_uac2 {
+	struct g_audio g_audio;
+	u8 ac_intf, as_in_intf, as_out_intf;
+	u8 ac_alt, as_in_alt, as_out_alt;	/* needed for get_alt() */
+};
+
+static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
+{
+	return container_of(f, struct f_uac2, g_audio.func);
+}
+
+static inline
+struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
+{
+	return container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
+}
+
+/* --------- USB Function Interface ------------- */
+
+enum {
+	STR_ASSOC,
+	STR_IF_CTRL,
+	STR_CLKSRC_IN,
+	STR_CLKSRC_OUT,
+	STR_USB_IT,
+	STR_IO_IT,
+	STR_USB_OT,
+	STR_IO_OT,
+	STR_AS_OUT_ALT0,
+	STR_AS_OUT_ALT1,
+	STR_AS_IN_ALT0,
+	STR_AS_IN_ALT1,
+};
+
+static char clksrc_in[8];
+static char clksrc_out[8];
+
+static struct usb_string strings_fn[] = {
+	[STR_ASSOC].s = "Source/Sink",
+	[STR_IF_CTRL].s = "Topology Control",
+	[STR_CLKSRC_IN].s = clksrc_in,
+	[STR_CLKSRC_OUT].s = clksrc_out,
+	[STR_USB_IT].s = "USBH Out",
+	[STR_IO_IT].s = "USBD Out",
+	[STR_USB_OT].s = "USBH In",
+	[STR_IO_OT].s = "USBD In",
+	[STR_AS_OUT_ALT0].s = "Playback Inactive",
+	[STR_AS_OUT_ALT1].s = "Playback Active",
+	[STR_AS_IN_ALT0].s = "Capture Inactive",
+	[STR_AS_IN_ALT1].s = "Capture Active",
+	{ },
+};
+
+static struct usb_gadget_strings str_fn = {
+	.language = 0x0409,	/* en-us */
+	.strings = strings_fn,
+};
+
+static struct usb_gadget_strings *fn_strings[] = {
+	&str_fn,
+	NULL,
+};
+
+static struct usb_interface_assoc_descriptor iad_desc = {
+	.bLength = sizeof iad_desc,
+	.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+	.bFirstInterface = 0,
+	.bInterfaceCount = 3,
+	.bFunctionClass = USB_CLASS_AUDIO,
+	.bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED,
+	.bFunctionProtocol = UAC_VERSION_2,
+};
+
+/* Audio Control Interface */
+static struct usb_interface_descriptor std_ac_if_desc = {
+	.bLength = sizeof std_ac_if_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Clock source for IN traffic */
+static struct uac_clock_source_descriptor in_clk_src_desc = {
+	.bLength = sizeof in_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	/* .bClockID = DYNAMIC */
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Clock source for OUT traffic */
+static struct uac_clock_source_descriptor out_clk_src_desc = {
+	.bLength = sizeof out_clk_src_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
+	/* .bClockID = DYNAMIC */
+	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
+	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bAssocTerminal = 0,
+};
+
+/* Input Terminal for USB_OUT */
+static struct uac2_input_terminal_descriptor usb_out_it_desc = {
+	.bLength = sizeof usb_out_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	/* .bTerminalID = DYNAMIC */
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	/* .bCSourceID = DYNAMIC */
+	.iChannelNames = 0,
+	.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Input Terminal for I/O-In */
+static struct uac2_input_terminal_descriptor io_in_it_desc = {
+	.bLength = sizeof io_in_it_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
+	/* .bTerminalID = DYNAMIC */
+	.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
+	.bAssocTerminal = 0,
+	/* .bCSourceID = DYNAMIC */
+	.iChannelNames = 0,
+	.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for USB_IN */
+static struct uac2_output_terminal_descriptor usb_in_ot_desc = {
+	.bLength = sizeof usb_in_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	/* .bTerminalID = DYNAMIC */
+	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
+	.bAssocTerminal = 0,
+	/* .bSourceID = DYNAMIC */
+	/* .bCSourceID = DYNAMIC */
+	.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
+};
+
+/* Ouput Terminal for I/O-Out */
+static struct uac2_output_terminal_descriptor io_out_ot_desc = {
+	.bLength = sizeof io_out_ot_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+	/* .bTerminalID = DYNAMIC */
+	.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
+	.bAssocTerminal = 0,
+	/* .bSourceID = DYNAMIC */
+	/* .bCSourceID = DYNAMIC */
+	.bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
+};
+
+static struct uac2_ac_header_descriptor ac_hdr_desc = {
+	.bLength = sizeof ac_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_MS_HEADER,
+	.bcdADC = cpu_to_le16(0x200),
+	.bCategory = UAC2_FUNCTION_IO_BOX,
+	/* .wTotalLength = DYNAMIC */
+	.bmControls = 0,
+};
+
+/* Audio Streaming OUT Interface - Alt0 */
+static struct usb_interface_descriptor std_as_out_if0_desc = {
+	.bLength = sizeof std_as_out_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming OUT Interface - Alt1 */
+static struct usb_interface_descriptor std_as_out_if1_desc = {
+	.bLength = sizeof std_as_out_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream OUT Intface Desc */
+static struct uac2_as_header_descriptor as_out_hdr_desc = {
+	.bLength = sizeof as_out_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	/* .bTerminalLink = DYNAMIC */
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_OUT Format */
+static struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
+	.bLength = sizeof as_out_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO OUT Endpoint */
+static struct usb_endpoint_descriptor fs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	/* .wMaxPacketSize = DYNAMIC */
+	.bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_epout_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	/* .wMaxPacketSize = DYNAMIC */
+	.bInterval = 4,
+};
+
+/* CS AS ISO OUT Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
+	.bLength = sizeof as_iso_out_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+/* Audio Streaming IN Interface - Alt0 */
+static struct usb_interface_descriptor std_as_in_if0_desc = {
+	.bLength = sizeof std_as_in_if0_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Streaming IN Interface - Alt1 */
+static struct usb_interface_descriptor std_as_in_if1_desc = {
+	.bLength = sizeof std_as_in_if1_desc,
+	.bDescriptorType = USB_DT_INTERFACE,
+
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+	.bInterfaceProtocol = UAC_VERSION_2,
+};
+
+/* Audio Stream IN Intface Desc */
+static struct uac2_as_header_descriptor as_in_hdr_desc = {
+	.bLength = sizeof as_in_hdr_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+
+	.bDescriptorSubtype = UAC_AS_GENERAL,
+	/* .bTerminalLink = DYNAMIC */
+	.bmControls = 0,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
+	.iChannelNames = 0,
+};
+
+/* Audio USB_IN Format */
+static struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
+	.bLength = sizeof as_in_fmt1_desc,
+	.bDescriptorType = USB_DT_CS_INTERFACE,
+	.bDescriptorSubtype = UAC_FORMAT_TYPE,
+	.bFormatType = UAC_FORMAT_TYPE_I,
+};
+
+/* STD AS ISO IN Endpoint */
+static struct usb_endpoint_descriptor fs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bEndpointAddress = USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	/* .wMaxPacketSize = DYNAMIC */
+	.bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_epin_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+
+	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+	/* .wMaxPacketSize = DYNAMIC */
+	.bInterval = 4,
+};
+
+/* CS AS ISO IN Endpoint */
+static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
+	.bLength = sizeof as_iso_in_desc,
+	.bDescriptorType = USB_DT_CS_ENDPOINT,
+
+	.bDescriptorSubtype = UAC_EP_GENERAL,
+	.bmAttributes = 0,
+	.bmControls = 0,
+	.bLockDelayUnits = 0,
+	.wLockDelay = 0,
+};
+
+static struct usb_descriptor_header *fs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&fs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *hs_audio_desc[] = {
+	(struct usb_descriptor_header *)&iad_desc,
+	(struct usb_descriptor_header *)&std_ac_if_desc,
+
+	(struct usb_descriptor_header *)&ac_hdr_desc,
+	(struct usb_descriptor_header *)&in_clk_src_desc,
+	(struct usb_descriptor_header *)&out_clk_src_desc,
+	(struct usb_descriptor_header *)&usb_out_it_desc,
+	(struct usb_descriptor_header *)&io_in_it_desc,
+	(struct usb_descriptor_header *)&usb_in_ot_desc,
+	(struct usb_descriptor_header *)&io_out_ot_desc,
+
+	(struct usb_descriptor_header *)&std_as_out_if0_desc,
+	(struct usb_descriptor_header *)&std_as_out_if1_desc,
+
+	(struct usb_descriptor_header *)&as_out_hdr_desc,
+	(struct usb_descriptor_header *)&as_out_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epout_desc,
+	(struct usb_descriptor_header *)&as_iso_out_desc,
+
+	(struct usb_descriptor_header *)&std_as_in_if0_desc,
+	(struct usb_descriptor_header *)&std_as_in_if1_desc,
+
+	(struct usb_descriptor_header *)&as_in_hdr_desc,
+	(struct usb_descriptor_header *)&as_in_fmt1_desc,
+	(struct usb_descriptor_header *)&hs_epin_desc,
+	(struct usb_descriptor_header *)&as_iso_in_desc,
+	NULL,
+};
+
+struct cntrl_cur_lay3 {
+	__le32	dCUR;
+};
+
+struct cntrl_range_lay3 {
+	__le16	wNumSubRanges;
+	__le32	dMIN;
+	__le32	dMAX;
+	__le32	dRES;
+} __packed;
+
+static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
+	struct usb_endpoint_descriptor *ep_desc,
+	enum usb_device_speed speed, bool is_playback)
+{
+	int chmask, srate, ssize;
+	u16 max_size_bw, max_size_ep;
+	unsigned int factor;
+
+	switch (speed) {
+	case USB_SPEED_FULL:
+		max_size_ep = 1023;
+		factor = 1000;
+		break;
+
+	case USB_SPEED_HIGH:
+		max_size_ep = 1024;
+		factor = 8000;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (is_playback) {
+		chmask = uac2_opts->p_chmask;
+		srate = uac2_opts->p_srate;
+		ssize = uac2_opts->p_ssize;
+	} else {
+		chmask = uac2_opts->c_chmask;
+		srate = uac2_opts->c_srate;
+		ssize = uac2_opts->c_ssize;
+	}
+
+	max_size_bw = num_channels(chmask) * ssize *
+		((srate / (factor / (1 << (ep_desc->bInterval - 1)))) + 1);
+	ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw,
+						    max_size_ep));
+
+	return 0;
+}
+
+/* Use macro to overcome line length limitation */
+#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+
+static void setup_descriptor(struct f_uac2_opts *opts)
+{
+	/* patch descriptors */
+	int i = 1; /* ID's start with 1 */
+
+	if (EPOUT_EN(opts))
+		usb_out_it_desc.bTerminalID = i++;
+	if (EPIN_EN(opts))
+		io_in_it_desc.bTerminalID = i++;
+	if (EPOUT_EN(opts))
+		io_out_ot_desc.bTerminalID = i++;
+	if (EPIN_EN(opts))
+		usb_in_ot_desc.bTerminalID = i++;
+	if (EPOUT_EN(opts))
+		out_clk_src_desc.bClockID = i++;
+	if (EPIN_EN(opts))
+		in_clk_src_desc.bClockID = i++;
+
+	usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID;
+	usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
+	usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID;
+	io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID;
+	io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID;
+	io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+	as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
+	as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+
+	iad_desc.bInterfaceCount = 1;
+	ac_hdr_desc.wTotalLength = cpu_to_le16(sizeof(ac_hdr_desc));
+
+	if (EPIN_EN(opts)) {
+		u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
+
+		len += sizeof(in_clk_src_desc);
+		len += sizeof(usb_in_ot_desc);
+		len += sizeof(io_in_it_desc);
+		ac_hdr_desc.wTotalLength = cpu_to_le16(len);
+		iad_desc.bInterfaceCount++;
+	}
+	if (EPOUT_EN(opts)) {
+		u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
+
+		len += sizeof(out_clk_src_desc);
+		len += sizeof(usb_out_it_desc);
+		len += sizeof(io_out_ot_desc);
+		ac_hdr_desc.wTotalLength = cpu_to_le16(len);
+		iad_desc.bInterfaceCount++;
+	}
+
+	i = 0;
+	fs_audio_desc[i++] = USBDHDR(&iad_desc);
+	fs_audio_desc[i++] = USBDHDR(&std_ac_if_desc);
+	fs_audio_desc[i++] = USBDHDR(&ac_hdr_desc);
+	if (EPIN_EN(opts))
+		fs_audio_desc[i++] = USBDHDR(&in_clk_src_desc);
+	if (EPOUT_EN(opts)) {
+		fs_audio_desc[i++] = USBDHDR(&out_clk_src_desc);
+		fs_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
+	}
+	if (EPIN_EN(opts)) {
+		fs_audio_desc[i++] = USBDHDR(&io_in_it_desc);
+		fs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
+	}
+	if (EPOUT_EN(opts)) {
+		fs_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
+		fs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc);
+		fs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc);
+		fs_audio_desc[i++] = USBDHDR(&fs_epout_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
+	}
+	if (EPIN_EN(opts)) {
+		fs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc);
+		fs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc);
+		fs_audio_desc[i++] = USBDHDR(&fs_epin_desc);
+		fs_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
+	}
+	fs_audio_desc[i] = NULL;
+
+	i = 0;
+	hs_audio_desc[i++] = USBDHDR(&iad_desc);
+	hs_audio_desc[i++] = USBDHDR(&std_ac_if_desc);
+	hs_audio_desc[i++] = USBDHDR(&ac_hdr_desc);
+	if (EPIN_EN(opts))
+		hs_audio_desc[i++] = USBDHDR(&in_clk_src_desc);
+	if (EPOUT_EN(opts)) {
+		hs_audio_desc[i++] = USBDHDR(&out_clk_src_desc);
+		hs_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
+	}
+	if (EPIN_EN(opts)) {
+		hs_audio_desc[i++] = USBDHDR(&io_in_it_desc);
+		hs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
+	}
+	if (EPOUT_EN(opts)) {
+		hs_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
+		hs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc);
+		hs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc);
+		hs_audio_desc[i++] = USBDHDR(&hs_epout_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
+	}
+	if (EPIN_EN(opts)) {
+		hs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc);
+		hs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc);
+		hs_audio_desc[i++] = USBDHDR(&hs_epin_desc);
+		hs_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
+	}
+	hs_audio_desc[i] = NULL;
+}
+
+static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
+{
+	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+
+	if (!opts->p_chmask && !opts->c_chmask) {
+		dev_err(dev, "Error: no playback and capture channels\n");
+		return -EINVAL;
+	} else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported playback channels mask\n");
+		return -EINVAL;
+	} else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) {
+		dev_err(dev, "Error: unsupported capture channels mask\n");
+		return -EINVAL;
+	} else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
+		dev_err(dev, "Error: incorrect playback sample size\n");
+		return -EINVAL;
+	} else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
+		dev_err(dev, "Error: incorrect capture sample size\n");
+		return -EINVAL;
+	} else if (!opts->p_srate) {
+		dev_err(dev, "Error: incorrect playback sampling rate\n");
+		return -EINVAL;
+	} else if (!opts->c_srate) {
+		dev_err(dev, "Error: incorrect capture sampling rate\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
+{
+	struct f_uac2 *uac2 = func_to_uac2(fn);
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct usb_composite_dev *cdev = cfg->cdev;
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	struct f_uac2_opts *uac2_opts = g_audio_to_uac2_opts(agdev);
+	struct usb_string *us;
+	int ret;
+
+	ret = afunc_validate_opts(agdev, dev);
+	if (ret)
+		return ret;
+
+	us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+	iad_desc.iFunction = us[STR_ASSOC].id;
+	std_ac_if_desc.iInterface = us[STR_IF_CTRL].id;
+	in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id;
+	out_clk_src_desc.iClockSource = us[STR_CLKSRC_OUT].id;
+	usb_out_it_desc.iTerminal = us[STR_USB_IT].id;
+	io_in_it_desc.iTerminal = us[STR_IO_IT].id;
+	usb_in_ot_desc.iTerminal = us[STR_USB_OT].id;
+	io_out_ot_desc.iTerminal = us[STR_IO_OT].id;
+	std_as_out_if0_desc.iInterface = us[STR_AS_OUT_ALT0].id;
+	std_as_out_if1_desc.iInterface = us[STR_AS_OUT_ALT1].id;
+	std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id;
+	std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id;
+
+
+	/* Initialize the configurable parameters */
+	usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
+	usb_out_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
+	io_in_it_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
+	io_in_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
+	as_out_hdr_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
+	as_out_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
+	as_in_hdr_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
+	as_in_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
+	as_out_fmt1_desc.bSubslotSize = uac2_opts->c_ssize;
+	as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8;
+	as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize;
+	as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8;
+
+	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
+	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
+
+	ret = usb_interface_id(cfg, fn);
+	if (ret < 0) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+	iad_desc.bFirstInterface = ret;
+
+	std_ac_if_desc.bInterfaceNumber = ret;
+	uac2->ac_intf = ret;
+	uac2->ac_alt = 0;
+
+	if (EPOUT_EN(uac2_opts)) {
+		ret = usb_interface_id(cfg, fn);
+		if (ret < 0) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return ret;
+		}
+		std_as_out_if0_desc.bInterfaceNumber = ret;
+		std_as_out_if1_desc.bInterfaceNumber = ret;
+		uac2->as_out_intf = ret;
+		uac2->as_out_alt = 0;
+	}
+
+	if (EPIN_EN(uac2_opts)) {
+		ret = usb_interface_id(cfg, fn);
+		if (ret < 0) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return ret;
+		}
+		std_as_in_if0_desc.bInterfaceNumber = ret;
+		std_as_in_if1_desc.bInterfaceNumber = ret;
+		uac2->as_in_intf = ret;
+		uac2->as_in_alt = 0;
+	}
+
+	/* Calculate wMaxPacketSize according to audio bandwidth */
+	ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL,
+				     true);
+	if (ret < 0) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+
+	ret = set_ep_max_packet_size(uac2_opts, &fs_epout_desc, USB_SPEED_FULL,
+				     false);
+	if (ret < 0) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+
+	ret = set_ep_max_packet_size(uac2_opts, &hs_epin_desc, USB_SPEED_HIGH,
+				     true);
+	if (ret < 0) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+
+	ret = set_ep_max_packet_size(uac2_opts, &hs_epout_desc, USB_SPEED_HIGH,
+				     false);
+	if (ret < 0) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return ret;
+	}
+
+	if (EPOUT_EN(uac2_opts)) {
+		agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
+		if (!agdev->out_ep) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -ENODEV;
+		}
+	}
+
+	if (EPIN_EN(uac2_opts)) {
+		agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
+		if (!agdev->in_ep) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -ENODEV;
+		}
+	}
+
+	agdev->in_ep_maxpsize = max_t(u16,
+				le16_to_cpu(fs_epin_desc.wMaxPacketSize),
+				le16_to_cpu(hs_epin_desc.wMaxPacketSize));
+	agdev->out_ep_maxpsize = max_t(u16,
+				le16_to_cpu(fs_epout_desc.wMaxPacketSize),
+				le16_to_cpu(hs_epout_desc.wMaxPacketSize));
+
+	hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
+	hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
+
+	setup_descriptor(uac2_opts);
+
+	ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL,
+				     NULL);
+	if (ret)
+		return ret;
+
+	agdev->gadget = gadget;
+
+	agdev->params.p_chmask = uac2_opts->p_chmask;
+	agdev->params.p_srate = uac2_opts->p_srate;
+	agdev->params.p_ssize = uac2_opts->p_ssize;
+	agdev->params.c_chmask = uac2_opts->c_chmask;
+	agdev->params.c_srate = uac2_opts->c_srate;
+	agdev->params.c_ssize = uac2_opts->c_ssize;
+	agdev->params.req_number = uac2_opts->req_number;
+	ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
+	if (ret)
+		goto err_free_descs;
+	return 0;
+
+err_free_descs:
+	usb_free_all_descriptors(fn);
+	agdev->gadget = NULL;
+	return ret;
+}
+
+static int
+afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct f_uac2 *uac2 = func_to_uac2(fn);
+	struct usb_gadget *gadget = cdev->gadget;
+	struct device *dev = &gadget->dev;
+	int ret = 0;
+
+	/* No i/f has more than 2 alt settings */
+	if (alt > 1) {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	if (intf == uac2->ac_intf) {
+		/* Control I/f has only 1 AltSetting - 0 */
+		if (alt) {
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (intf == uac2->as_out_intf) {
+		uac2->as_out_alt = alt;
+
+		if (alt)
+			ret = u_audio_start_capture(&uac2->g_audio);
+		else
+			u_audio_stop_capture(&uac2->g_audio);
+	} else if (intf == uac2->as_in_intf) {
+		uac2->as_in_alt = alt;
+
+		if (alt)
+			ret = u_audio_start_playback(&uac2->g_audio);
+		else
+			u_audio_stop_playback(&uac2->g_audio);
+	} else {
+		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int
+afunc_get_alt(struct usb_function *fn, unsigned intf)
+{
+	struct f_uac2 *uac2 = func_to_uac2(fn);
+	struct g_audio *agdev = func_to_g_audio(fn);
+
+	if (intf == uac2->ac_intf)
+		return uac2->ac_alt;
+	else if (intf == uac2->as_out_intf)
+		return uac2->as_out_alt;
+	else if (intf == uac2->as_in_intf)
+		return uac2->as_in_alt;
+	else
+		dev_err(&agdev->gadget->dev,
+			"%s:%d Invalid Interface %d!\n",
+			__func__, __LINE__, intf);
+
+	return -EINVAL;
+}
+
+static void
+afunc_disable(struct usb_function *fn)
+{
+	struct f_uac2 *uac2 = func_to_uac2(fn);
+
+	uac2->as_in_alt = 0;
+	uac2->as_out_alt = 0;
+	u_audio_stop_capture(&uac2->g_audio);
+	u_audio_stop_playback(&uac2->g_audio);
+}
+
+static int
+in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac2_opts *opts;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	u16 w_index = le16_to_cpu(cr->wIndex);
+	u16 w_value = le16_to_cpu(cr->wValue);
+	u8 entity_id = (w_index >> 8) & 0xff;
+	u8 control_selector = w_value >> 8;
+	int value = -EOPNOTSUPP;
+	int p_srate, c_srate;
+
+	opts = g_audio_to_uac2_opts(agdev);
+	p_srate = opts->p_srate;
+	c_srate = opts->c_srate;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		struct cntrl_cur_lay3 c;
+		memset(&c, 0, sizeof(struct cntrl_cur_lay3));
+
+		if (entity_id == USB_IN_CLK_ID)
+			c.dCUR = cpu_to_le32(p_srate);
+		else if (entity_id == USB_OUT_CLK_ID)
+			c.dCUR = cpu_to_le32(c_srate);
+
+		value = min_t(unsigned, w_length, sizeof c);
+		memcpy(req->buf, &c, value);
+	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
+		*(u8 *)req->buf = 1;
+		value = min_t(unsigned, w_length, 1);
+	} else {
+		dev_err(&agdev->gadget->dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_request *req = fn->config->cdev->req;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac2_opts *opts;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	u16 w_index = le16_to_cpu(cr->wIndex);
+	u16 w_value = le16_to_cpu(cr->wValue);
+	u8 entity_id = (w_index >> 8) & 0xff;
+	u8 control_selector = w_value >> 8;
+	struct cntrl_range_lay3 r;
+	int value = -EOPNOTSUPP;
+	int p_srate, c_srate;
+
+	opts = g_audio_to_uac2_opts(agdev);
+	p_srate = opts->p_srate;
+	c_srate = opts->c_srate;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		if (entity_id == USB_IN_CLK_ID)
+			r.dMIN = cpu_to_le32(p_srate);
+		else if (entity_id == USB_OUT_CLK_ID)
+			r.dMIN = cpu_to_le32(c_srate);
+		else
+			return -EOPNOTSUPP;
+
+		r.dMAX = r.dMIN;
+		r.dRES = 0;
+		r.wNumSubRanges = cpu_to_le16(1);
+
+		value = min_t(unsigned, w_length, sizeof r);
+		memcpy(req->buf, &r, value);
+	} else {
+		dev_err(&agdev->gadget->dev,
+			"%s:%d control_selector=%d TODO!\n",
+			__func__, __LINE__, control_selector);
+	}
+
+	return value;
+}
+
+static int
+ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	if (cr->bRequest == UAC2_CS_CUR)
+		return in_rq_cur(fn, cr);
+	else if (cr->bRequest == UAC2_CS_RANGE)
+		return in_rq_range(fn, cr);
+	else
+		return -EOPNOTSUPP;
+}
+
+static int
+out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	u16 w_length = le16_to_cpu(cr->wLength);
+	u16 w_value = le16_to_cpu(cr->wValue);
+	u8 control_selector = w_value >> 8;
+
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+		return w_length;
+
+	return -EOPNOTSUPP;
+}
+
+static int
+setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct f_uac2 *uac2 = func_to_uac2(fn);
+	struct g_audio *agdev = func_to_g_audio(fn);
+	u16 w_index = le16_to_cpu(cr->wIndex);
+	u8 intf = w_index & 0xff;
+
+	if (intf != uac2->ac_intf) {
+		dev_err(&agdev->gadget->dev,
+			"%s:%d Error!\n", __func__, __LINE__);
+		return -EOPNOTSUPP;
+	}
+
+	if (cr->bRequestType & USB_DIR_IN)
+		return ac_rq_in(fn, cr);
+	else if (cr->bRequest == UAC2_CS_CUR)
+		return out_rq_cur(fn, cr);
+
+	return -EOPNOTSUPP;
+}
+
+static int
+afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
+{
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct usb_request *req = cdev->req;
+	u16 w_length = le16_to_cpu(cr->wLength);
+	int value = -EOPNOTSUPP;
+
+	/* Only Class specific requests are supposed to reach here */
+	if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
+		return -EOPNOTSUPP;
+
+	if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
+		value = setup_rq_inf(fn, cr);
+	else
+		dev_err(&agdev->gadget->dev, "%s:%d Error!\n",
+				__func__, __LINE__);
+
+	if (value >= 0) {
+		req->length = value;
+		req->zero = value < w_length;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			dev_err(&agdev->gadget->dev,
+				"%s:%d Error!\n", __func__, __LINE__);
+			req->status = 0;
+		}
+	}
+
+	return value;
+}
+
+static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_uac2_opts,
+			    func_inst.group);
+}
+
+static void f_uac2_attr_release(struct config_item *item)
+{
+	struct f_uac2_opts *opts = to_f_uac2_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_uac2_item_ops = {
+	.release	= f_uac2_attr_release,
+};
+
+#define UAC2_ATTRIBUTE(name)						\
+static ssize_t f_uac2_opts_##name##_show(struct config_item *item,	\
+					 char *page)			\
+{									\
+	struct f_uac2_opts *opts = to_f_uac2_opts(item);		\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", opts->name);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac2_opts_##name##_store(struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac2_opts *opts = to_f_uac2_opts(item);		\
+	int ret;							\
+	u32 num;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou32(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	opts->name = num;						\
+	ret = len;							\
+									\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac2_opts_, name)
+
+UAC2_ATTRIBUTE(p_chmask);
+UAC2_ATTRIBUTE(p_srate);
+UAC2_ATTRIBUTE(p_ssize);
+UAC2_ATTRIBUTE(c_chmask);
+UAC2_ATTRIBUTE(c_srate);
+UAC2_ATTRIBUTE(c_ssize);
+UAC2_ATTRIBUTE(req_number);
+
+static struct configfs_attribute *f_uac2_attrs[] = {
+	&f_uac2_opts_attr_p_chmask,
+	&f_uac2_opts_attr_p_srate,
+	&f_uac2_opts_attr_p_ssize,
+	&f_uac2_opts_attr_c_chmask,
+	&f_uac2_opts_attr_c_srate,
+	&f_uac2_opts_attr_c_ssize,
+	&f_uac2_opts_attr_req_number,
+	NULL,
+};
+
+static const struct config_item_type f_uac2_func_type = {
+	.ct_item_ops	= &f_uac2_item_ops,
+	.ct_attrs	= f_uac2_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void afunc_free_inst(struct usb_function_instance *f)
+{
+	struct f_uac2_opts *opts;
+
+	opts = container_of(f, struct f_uac2_opts, func_inst);
+	kfree(opts);
+}
+
+static struct usb_function_instance *afunc_alloc_inst(void)
+{
+	struct f_uac2_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = afunc_free_inst;
+
+	config_group_init_type_name(&opts->func_inst.group, "",
+				    &f_uac2_func_type);
+
+	opts->p_chmask = UAC2_DEF_PCHMASK;
+	opts->p_srate = UAC2_DEF_PSRATE;
+	opts->p_ssize = UAC2_DEF_PSSIZE;
+	opts->c_chmask = UAC2_DEF_CCHMASK;
+	opts->c_srate = UAC2_DEF_CSRATE;
+	opts->c_ssize = UAC2_DEF_CSSIZE;
+	opts->req_number = UAC2_DEF_REQ_NUM;
+	return &opts->func_inst;
+}
+
+static void afunc_free(struct usb_function *f)
+{
+	struct g_audio *agdev;
+	struct f_uac2_opts *opts;
+
+	agdev = func_to_g_audio(f);
+	opts = container_of(f->fi, struct f_uac2_opts, func_inst);
+	kfree(agdev);
+	mutex_lock(&opts->lock);
+	--opts->refcnt;
+	mutex_unlock(&opts->lock);
+}
+
+static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct g_audio *agdev = func_to_g_audio(f);
+
+	g_audio_cleanup(agdev);
+	usb_free_all_descriptors(f);
+
+	agdev->gadget = NULL;
+}
+
+static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
+{
+	struct f_uac2	*uac2;
+	struct f_uac2_opts *opts;
+
+	uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL);
+	if (uac2 == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	opts = container_of(fi, struct f_uac2_opts, func_inst);
+	mutex_lock(&opts->lock);
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	uac2->g_audio.func.name = "uac2_func";
+	uac2->g_audio.func.bind = afunc_bind;
+	uac2->g_audio.func.unbind = afunc_unbind;
+	uac2->g_audio.func.set_alt = afunc_set_alt;
+	uac2->g_audio.func.get_alt = afunc_get_alt;
+	uac2->g_audio.func.disable = afunc_disable;
+	uac2->g_audio.func.setup = afunc_setup;
+	uac2->g_audio.func.free_func = afunc_free;
+
+	return &uac2->g_audio.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yadwinder Singh");
+MODULE_AUTHOR("Jaswinder Singh");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_uvc.c b/marvell/linux/drivers/usb/gadget/function/f_uvc.c
new file mode 100644
index 0000000..094a88f
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_uvc.c
@@ -0,0 +1,959 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	uvc_gadget.c  --  USB Video Class Gadget driver
+ *
+ *	Copyright (C) 2009-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/g_uvc.h>
+#include <linux/usb/video.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+
+#include "u_uvc.h"
+#include "uvc.h"
+#include "uvc_configfs.h"
+#include "uvc_v4l2.h"
+#include "uvc_video.h"
+
+unsigned int uvc_gadget_trace_param;
+module_param_named(trace, uvc_gadget_trace_param, uint, 0644);
+MODULE_PARM_DESC(trace, "Trace level bitmask");
+
+/* --------------------------------------------------------------------------
+ * Function descriptors
+ */
+
+/* string IDs are assigned dynamically */
+
+#define UVC_STRING_CONTROL_IDX			0
+#define UVC_STRING_STREAMING_IDX		1
+
+static struct usb_string uvc_en_us_strings[] = {
+	[UVC_STRING_CONTROL_IDX].s = "UVC Camera",
+	[UVC_STRING_STREAMING_IDX].s = "Video Streaming",
+	{  }
+};
+
+static struct usb_gadget_strings uvc_stringtab = {
+	.language = 0x0409,	/* en-us */
+	.strings = uvc_en_us_strings,
+};
+
+static struct usb_gadget_strings *uvc_function_strings[] = {
+	&uvc_stringtab,
+	NULL,
+};
+
+#define UVC_INTF_VIDEO_CONTROL			0
+#define UVC_INTF_VIDEO_STREAMING		1
+
+#define UVC_STATUS_MAX_PACKET_SIZE		16	/* 16 bytes status */
+
+static struct usb_interface_assoc_descriptor uvc_iad = {
+	.bLength		= sizeof(uvc_iad),
+	.bDescriptorType	= USB_DT_INTERFACE_ASSOCIATION,
+	.bFirstInterface	= 0,
+	.bInterfaceCount	= 2,
+	.bFunctionClass		= USB_CLASS_VIDEO,
+	.bFunctionSubClass	= UVC_SC_VIDEO_INTERFACE_COLLECTION,
+	.bFunctionProtocol	= 0x00,
+	.iFunction		= 0,
+};
+
+static struct usb_interface_descriptor uvc_control_intf = {
+	.bLength		= USB_DT_INTERFACE_SIZE,
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= UVC_INTF_VIDEO_CONTROL,
+	.bAlternateSetting	= 0,
+	.bNumEndpoints		= 1,
+	.bInterfaceClass	= USB_CLASS_VIDEO,
+	.bInterfaceSubClass	= UVC_SC_VIDEOCONTROL,
+	.bInterfaceProtocol	= 0x00,
+	.iInterface		= 0,
+};
+
+static struct usb_endpoint_descriptor uvc_control_ep = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize		= cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
+	.bInterval		= 8,
+};
+
+static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = {
+	.bLength		= sizeof(uvc_ss_control_comp),
+	.bDescriptorType	= USB_DT_SS_ENDPOINT_COMP,
+	/* The following 3 values can be tweaked if necessary. */
+	.bMaxBurst		= 0,
+	.bmAttributes		= 0,
+	.wBytesPerInterval	= cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
+};
+
+static struct uvc_control_endpoint_descriptor uvc_control_cs_ep = {
+	.bLength		= UVC_DT_CONTROL_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_CS_ENDPOINT,
+	.bDescriptorSubType	= UVC_EP_INTERRUPT,
+	.wMaxTransferSize	= cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE),
+};
+
+static struct usb_interface_descriptor uvc_streaming_intf_alt0 = {
+	.bLength		= USB_DT_INTERFACE_SIZE,
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= UVC_INTF_VIDEO_STREAMING,
+	.bAlternateSetting	= 0,
+	.bNumEndpoints		= 0,
+	.bInterfaceClass	= USB_CLASS_VIDEO,
+	.bInterfaceSubClass	= UVC_SC_VIDEOSTREAMING,
+	.bInterfaceProtocol	= 0x00,
+	.iInterface		= 0,
+};
+
+static struct usb_interface_descriptor uvc_streaming_intf_alt1 = {
+	.bLength		= USB_DT_INTERFACE_SIZE,
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bInterfaceNumber	= UVC_INTF_VIDEO_STREAMING,
+	.bAlternateSetting	= 1,
+	.bNumEndpoints		= 1,
+	.bInterfaceClass	= USB_CLASS_VIDEO,
+	.bInterfaceSubClass	= UVC_SC_VIDEOSTREAMING,
+	.bInterfaceProtocol	= 0x00,
+	.iInterface		= 0,
+};
+
+static struct usb_endpoint_descriptor uvc_fs_streaming_ep = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_SYNC_ASYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	/* The wMaxPacketSize and bInterval values will be initialized from
+	 * module parameters.
+	 */
+};
+
+static struct usb_endpoint_descriptor uvc_hs_streaming_ep = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_SYNC_ASYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	/* The wMaxPacketSize and bInterval values will be initialized from
+	 * module parameters.
+	 */
+};
+
+static struct usb_endpoint_descriptor uvc_ss_streaming_ep = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_SYNC_ASYNC
+				| USB_ENDPOINT_XFER_ISOC,
+	/* The wMaxPacketSize and bInterval values will be initialized from
+	 * module parameters.
+	 */
+};
+
+static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = {
+	.bLength		= sizeof(uvc_ss_streaming_comp),
+	.bDescriptorType	= USB_DT_SS_ENDPOINT_COMP,
+	/* The bMaxBurst, bmAttributes and wBytesPerInterval values will be
+	 * initialized from module parameters.
+	 */
+};
+
+static const struct usb_descriptor_header * const uvc_fs_streaming[] = {
+	(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
+	(struct usb_descriptor_header *) &uvc_fs_streaming_ep,
+	NULL,
+};
+
+static const struct usb_descriptor_header * const uvc_hs_streaming[] = {
+	(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
+	(struct usb_descriptor_header *) &uvc_hs_streaming_ep,
+	NULL,
+};
+
+static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
+	(struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
+	(struct usb_descriptor_header *) &uvc_ss_streaming_ep,
+	(struct usb_descriptor_header *) &uvc_ss_streaming_comp,
+	NULL,
+};
+
+/* --------------------------------------------------------------------------
+ * Control requests
+ */
+
+static void
+uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct uvc_device *uvc = req->context;
+	struct v4l2_event v4l2_event;
+	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+
+	if (uvc->event_setup_out) {
+		uvc->event_setup_out = 0;
+
+		memset(&v4l2_event, 0, sizeof(v4l2_event));
+		v4l2_event.type = UVC_EVENT_DATA;
+		uvc_event->data.length = min_t(unsigned int, req->actual,
+			sizeof(uvc_event->data.data));
+		memcpy(&uvc_event->data.data, req->buf, uvc_event->data.length);
+		v4l2_event_queue(&uvc->vdev, &v4l2_event);
+	}
+}
+
+static int
+uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct uvc_device *uvc = to_uvc(f);
+	struct v4l2_event v4l2_event;
+	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
+		uvcg_info(f, "invalid request type\n");
+		return -EINVAL;
+	}
+
+	/* Stall too big requests. */
+	if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE)
+		return -EINVAL;
+
+	/* Tell the complete callback to generate an event for the next request
+	 * that will be enqueued by UVCIOC_SEND_RESPONSE.
+	 */
+	uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
+	uvc->event_length = le16_to_cpu(ctrl->wLength);
+
+	memset(&v4l2_event, 0, sizeof(v4l2_event));
+	v4l2_event.type = UVC_EVENT_SETUP;
+	memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
+	v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+	return 0;
+}
+
+void uvc_function_setup_continue(struct uvc_device *uvc)
+{
+	struct usb_composite_dev *cdev = uvc->func.config->cdev;
+
+	usb_composite_setup_continue(cdev);
+}
+
+static int
+uvc_function_get_alt(struct usb_function *f, unsigned interface)
+{
+	struct uvc_device *uvc = to_uvc(f);
+
+	uvcg_info(f, "%s(%u)\n", __func__, interface);
+
+	if (interface == uvc->control_intf)
+		return 0;
+	else if (interface != uvc->streaming_intf)
+		return -EINVAL;
+	else
+		return uvc->video.ep->enabled ? 1 : 0;
+}
+
+static int
+uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
+{
+	struct uvc_device *uvc = to_uvc(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct v4l2_event v4l2_event;
+	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+	int ret;
+
+	uvcg_info(f, "%s(%u, %u)\n", __func__, interface, alt);
+
+	if (interface == uvc->control_intf) {
+		if (alt)
+			return -EINVAL;
+
+		uvcg_info(f, "reset UVC Control\n");
+		usb_ep_disable(uvc->control_ep);
+
+		if (!uvc->control_ep->desc)
+			if (config_ep_by_speed(cdev->gadget, f, uvc->control_ep))
+				return -EINVAL;
+
+		usb_ep_enable(uvc->control_ep);
+
+		if (uvc->state == UVC_STATE_DISCONNECTED) {
+			memset(&v4l2_event, 0, sizeof(v4l2_event));
+			v4l2_event.type = UVC_EVENT_CONNECT;
+			uvc_event->speed = cdev->gadget->speed;
+			v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+			uvc->state = UVC_STATE_CONNECTED;
+		}
+
+		return 0;
+	}
+
+	if (interface != uvc->streaming_intf)
+		return -EINVAL;
+
+	/* TODO
+	if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))
+		return alt ? -EINVAL : 0;
+	*/
+
+	switch (alt) {
+	case 0:
+		if (uvc->state != UVC_STATE_STREAMING)
+			return 0;
+
+		if (uvc->video.ep)
+			usb_ep_disable(uvc->video.ep);
+
+		memset(&v4l2_event, 0, sizeof(v4l2_event));
+		v4l2_event.type = UVC_EVENT_STREAMOFF;
+		v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+		uvc->state = UVC_STATE_CONNECTED;
+		return 0;
+
+	case 1:
+		if (uvc->state != UVC_STATE_CONNECTED)
+			return 0;
+
+		if (!uvc->video.ep)
+			return -EINVAL;
+
+		uvcg_info(f, "reset UVC\n");
+		usb_ep_disable(uvc->video.ep);
+
+		ret = config_ep_by_speed(f->config->cdev->gadget,
+				&(uvc->func), uvc->video.ep);
+		if (ret)
+			return ret;
+		usb_ep_enable(uvc->video.ep);
+
+		memset(&v4l2_event, 0, sizeof(v4l2_event));
+		v4l2_event.type = UVC_EVENT_STREAMON;
+		v4l2_event_queue(&uvc->vdev, &v4l2_event);
+		return USB_GADGET_DELAYED_STATUS;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static void
+uvc_function_disable(struct usb_function *f)
+{
+	struct uvc_device *uvc = to_uvc(f);
+	struct v4l2_event v4l2_event;
+
+	uvcg_info(f, "%s()\n", __func__);
+
+	memset(&v4l2_event, 0, sizeof(v4l2_event));
+	v4l2_event.type = UVC_EVENT_DISCONNECT;
+	v4l2_event_queue(&uvc->vdev, &v4l2_event);
+
+	uvc->state = UVC_STATE_DISCONNECTED;
+
+	usb_ep_disable(uvc->video.ep);
+	usb_ep_disable(uvc->control_ep);
+}
+
+/* --------------------------------------------------------------------------
+ * Connection / disconnection
+ */
+
+void
+uvc_function_connect(struct uvc_device *uvc)
+{
+	int ret;
+
+	if ((ret = usb_function_activate(&uvc->func)) < 0)
+		uvcg_info(&uvc->func, "UVC connect failed with %d\n", ret);
+}
+
+void
+uvc_function_disconnect(struct uvc_device *uvc)
+{
+	int ret;
+
+	if ((ret = usb_function_deactivate(&uvc->func)) < 0)
+		uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret);
+}
+
+/* --------------------------------------------------------------------------
+ * USB probe and disconnect
+ */
+
+static ssize_t function_name_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct uvc_device *uvc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", uvc->func.fi->group.cg_item.ci_name);
+}
+
+static DEVICE_ATTR_RO(function_name);
+
+static int
+uvc_register_video(struct uvc_device *uvc)
+{
+	struct usb_composite_dev *cdev = uvc->func.config->cdev;
+	int ret;
+
+	/* TODO reference counting. */
+	uvc->vdev.v4l2_dev = &uvc->v4l2_dev;
+	uvc->vdev.fops = &uvc_v4l2_fops;
+	uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops;
+	uvc->vdev.release = video_device_release_empty;
+	uvc->vdev.vfl_dir = VFL_DIR_TX;
+	uvc->vdev.lock = &uvc->video.mutex;
+	uvc->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+	strlcpy(uvc->vdev.name, cdev->gadget->name, sizeof(uvc->vdev.name));
+
+	video_set_drvdata(&uvc->vdev, uvc);
+
+	ret = video_register_device(&uvc->vdev, VFL_TYPE_GRABBER, -1);
+	if (ret < 0)
+		return ret;
+
+	ret = device_create_file(&uvc->vdev.dev, &dev_attr_function_name);
+	if (ret < 0) {
+		video_unregister_device(&uvc->vdev);
+		return ret;
+	}
+
+	return 0;
+}
+
+#define UVC_COPY_DESCRIPTOR(mem, dst, desc) \
+	do { \
+		memcpy(mem, desc, (desc)->bLength); \
+		*(dst)++ = mem; \
+		mem += (desc)->bLength; \
+	} while (0);
+
+#define UVC_COPY_DESCRIPTORS(mem, dst, src) \
+	do { \
+		const struct usb_descriptor_header * const *__src; \
+		for (__src = src; *__src; ++__src) { \
+			memcpy(mem, *__src, (*__src)->bLength); \
+			*dst++ = mem; \
+			mem += (*__src)->bLength; \
+		} \
+	} while (0)
+
+static struct usb_descriptor_header **
+uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
+{
+	struct uvc_input_header_descriptor *uvc_streaming_header;
+	struct uvc_header_descriptor *uvc_control_header;
+	const struct uvc_descriptor_header * const *uvc_control_desc;
+	const struct uvc_descriptor_header * const *uvc_streaming_cls;
+	const struct usb_descriptor_header * const *uvc_streaming_std;
+	const struct usb_descriptor_header * const *src;
+	struct usb_descriptor_header **dst;
+	struct usb_descriptor_header **hdr;
+	unsigned int control_size;
+	unsigned int streaming_size;
+	unsigned int n_desc;
+	unsigned int bytes;
+	void *mem;
+
+	switch (speed) {
+	case USB_SPEED_SUPER:
+		uvc_control_desc = uvc->desc.ss_control;
+		uvc_streaming_cls = uvc->desc.ss_streaming;
+		uvc_streaming_std = uvc_ss_streaming;
+		break;
+
+	case USB_SPEED_HIGH:
+		uvc_control_desc = uvc->desc.fs_control;
+		uvc_streaming_cls = uvc->desc.hs_streaming;
+		uvc_streaming_std = uvc_hs_streaming;
+		break;
+
+	case USB_SPEED_FULL:
+	default:
+		uvc_control_desc = uvc->desc.fs_control;
+		uvc_streaming_cls = uvc->desc.fs_streaming;
+		uvc_streaming_std = uvc_fs_streaming;
+		break;
+	}
+
+	if (!uvc_control_desc || !uvc_streaming_cls)
+		return ERR_PTR(-ENODEV);
+
+	/* Descriptors layout
+	 *
+	 * uvc_iad
+	 * uvc_control_intf
+	 * Class-specific UVC control descriptors
+	 * uvc_control_ep
+	 * uvc_control_cs_ep
+	 * uvc_ss_control_comp (for SS only)
+	 * uvc_streaming_intf_alt0
+	 * Class-specific UVC streaming descriptors
+	 * uvc_{fs|hs}_streaming
+	 */
+
+	/* Count descriptors and compute their size. */
+	control_size = 0;
+	streaming_size = 0;
+	bytes = uvc_iad.bLength + uvc_control_intf.bLength
+	      + uvc_control_ep.bLength + uvc_control_cs_ep.bLength
+	      + uvc_streaming_intf_alt0.bLength;
+
+	if (speed == USB_SPEED_SUPER) {
+		bytes += uvc_ss_control_comp.bLength;
+		n_desc = 6;
+	} else {
+		n_desc = 5;
+	}
+
+	for (src = (const struct usb_descriptor_header **)uvc_control_desc;
+	     *src; ++src) {
+		control_size += (*src)->bLength;
+		bytes += (*src)->bLength;
+		n_desc++;
+	}
+	for (src = (const struct usb_descriptor_header **)uvc_streaming_cls;
+	     *src; ++src) {
+		streaming_size += (*src)->bLength;
+		bytes += (*src)->bLength;
+		n_desc++;
+	}
+	for (src = uvc_streaming_std; *src; ++src) {
+		bytes += (*src)->bLength;
+		n_desc++;
+	}
+
+	mem = kmalloc((n_desc + 1) * sizeof(*src) + bytes, GFP_KERNEL);
+	if (mem == NULL)
+		return NULL;
+
+	hdr = mem;
+	dst = mem;
+	mem += (n_desc + 1) * sizeof(*src);
+
+	/* Copy the descriptors. */
+	UVC_COPY_DESCRIPTOR(mem, dst, &uvc_iad);
+	UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_intf);
+
+	uvc_control_header = mem;
+	UVC_COPY_DESCRIPTORS(mem, dst,
+		(const struct usb_descriptor_header **)uvc_control_desc);
+	uvc_control_header->wTotalLength = cpu_to_le16(control_size);
+	uvc_control_header->bInCollection = 1;
+	uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf;
+
+	UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep);
+	if (speed == USB_SPEED_SUPER)
+		UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp);
+
+	UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep);
+	UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0);
+
+	uvc_streaming_header = mem;
+	UVC_COPY_DESCRIPTORS(mem, dst,
+		(const struct usb_descriptor_header**)uvc_streaming_cls);
+	uvc_streaming_header->wTotalLength = cpu_to_le16(streaming_size);
+	uvc_streaming_header->bEndpointAddress = uvc->video.ep->address;
+
+	UVC_COPY_DESCRIPTORS(mem, dst, uvc_streaming_std);
+
+	*dst = NULL;
+	return hdr;
+}
+
+static int
+uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct uvc_device *uvc = to_uvc(f);
+	struct usb_string *us;
+	unsigned int max_packet_mult;
+	unsigned int max_packet_size;
+	struct usb_ep *ep;
+	struct f_uvc_opts *opts;
+	int ret = -EINVAL;
+
+	uvcg_info(f, "%s()\n", __func__);
+
+	opts = fi_to_f_uvc_opts(f->fi);
+	/* Sanity check the streaming endpoint module parameters.
+	 */
+	opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U);
+	opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U);
+	opts->streaming_maxburst = min(opts->streaming_maxburst, 15U);
+
+	/* For SS, wMaxPacketSize has to be 1024 if bMaxBurst is not 0 */
+	if (opts->streaming_maxburst &&
+	    (opts->streaming_maxpacket % 1024) != 0) {
+		opts->streaming_maxpacket = roundup(opts->streaming_maxpacket, 1024);
+		uvcg_info(f, "overriding streaming_maxpacket to %d\n",
+			  opts->streaming_maxpacket);
+	}
+
+	/* Fill in the FS/HS/SS Video Streaming specific descriptors from the
+	 * module parameters.
+	 *
+	 * NOTE: We assume that the user knows what they are doing and won't
+	 * give parameters that their UDC doesn't support.
+	 */
+	if (opts->streaming_maxpacket <= 1024) {
+		max_packet_mult = 1;
+		max_packet_size = opts->streaming_maxpacket;
+	} else if (opts->streaming_maxpacket <= 2048) {
+		max_packet_mult = 2;
+		max_packet_size = opts->streaming_maxpacket / 2;
+	} else {
+		max_packet_mult = 3;
+		max_packet_size = opts->streaming_maxpacket / 3;
+	}
+
+	uvc_fs_streaming_ep.wMaxPacketSize =
+		cpu_to_le16(min(opts->streaming_maxpacket, 1023U));
+	uvc_fs_streaming_ep.bInterval = opts->streaming_interval;
+
+	uvc_hs_streaming_ep.wMaxPacketSize =
+		cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11));
+
+	/* A high-bandwidth endpoint must specify a bInterval value of 1 */
+	if (max_packet_mult > 1)
+		uvc_hs_streaming_ep.bInterval = 1;
+	else
+		uvc_hs_streaming_ep.bInterval = opts->streaming_interval;
+
+	uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size);
+	uvc_ss_streaming_ep.bInterval = opts->streaming_interval;
+	uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1;
+	uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst;
+	uvc_ss_streaming_comp.wBytesPerInterval =
+		cpu_to_le16(max_packet_size * max_packet_mult *
+			    (opts->streaming_maxburst + 1));
+
+	/* Allocate endpoints. */
+	ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep);
+	if (!ep) {
+		uvcg_info(f, "Unable to allocate control EP\n");
+		goto error;
+	}
+	uvc->control_ep = ep;
+
+	if (gadget_is_superspeed(c->cdev->gadget))
+		ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep,
+					  &uvc_ss_streaming_comp);
+	else if (gadget_is_dualspeed(cdev->gadget))
+		ep = usb_ep_autoconfig(cdev->gadget, &uvc_hs_streaming_ep);
+	else
+		ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep);
+
+	if (!ep) {
+		uvcg_info(f, "Unable to allocate streaming EP\n");
+		goto error;
+	}
+	uvc->video.ep = ep;
+
+	uvc_fs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
+	uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
+	uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address;
+
+	us = usb_gstrings_attach(cdev, uvc_function_strings,
+				 ARRAY_SIZE(uvc_en_us_strings));
+	if (IS_ERR(us)) {
+		ret = PTR_ERR(us);
+		goto error;
+	}
+	uvc_iad.iFunction = us[UVC_STRING_CONTROL_IDX].id;
+	uvc_control_intf.iInterface = us[UVC_STRING_CONTROL_IDX].id;
+	ret = us[UVC_STRING_STREAMING_IDX].id;
+	uvc_streaming_intf_alt0.iInterface = ret;
+	uvc_streaming_intf_alt1.iInterface = ret;
+
+	/* Allocate interface IDs. */
+	if ((ret = usb_interface_id(c, f)) < 0)
+		goto error;
+	uvc_iad.bFirstInterface = ret;
+	uvc_control_intf.bInterfaceNumber = ret;
+	uvc->control_intf = ret;
+	opts->control_interface = ret;
+
+	if ((ret = usb_interface_id(c, f)) < 0)
+		goto error;
+	uvc_streaming_intf_alt0.bInterfaceNumber = ret;
+	uvc_streaming_intf_alt1.bInterfaceNumber = ret;
+	uvc->streaming_intf = ret;
+	opts->streaming_interface = ret;
+
+	/* Copy descriptors */
+	f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL);
+	if (IS_ERR(f->fs_descriptors)) {
+		ret = PTR_ERR(f->fs_descriptors);
+		f->fs_descriptors = NULL;
+		goto error;
+	}
+	if (gadget_is_dualspeed(cdev->gadget)) {
+		f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH);
+		if (IS_ERR(f->hs_descriptors)) {
+			ret = PTR_ERR(f->hs_descriptors);
+			f->hs_descriptors = NULL;
+			goto error;
+		}
+	}
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER);
+		if (IS_ERR(f->ss_descriptors)) {
+			ret = PTR_ERR(f->ss_descriptors);
+			f->ss_descriptors = NULL;
+			goto error;
+		}
+	}
+
+	/* Preallocate control endpoint request. */
+	uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+	uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL);
+	if (uvc->control_req == NULL || uvc->control_buf == NULL) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	uvc->control_req->buf = uvc->control_buf;
+	uvc->control_req->complete = uvc_function_ep0_complete;
+	uvc->control_req->context = uvc;
+
+	if (v4l2_device_register(&cdev->gadget->dev, &uvc->v4l2_dev)) {
+		uvcg_err(f, "failed to register V4L2 device\n");
+		goto error;
+	}
+
+	/* Initialise video. */
+	ret = uvcg_video_init(&uvc->video, uvc);
+	if (ret < 0)
+		goto error;
+
+	/* Register a V4L2 device. */
+	ret = uvc_register_video(uvc);
+	if (ret < 0) {
+		uvcg_err(f, "failed to register video device\n");
+		goto error;
+	}
+
+	return 0;
+
+error:
+	v4l2_device_unregister(&uvc->v4l2_dev);
+
+	if (uvc->control_req)
+		usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+	kfree(uvc->control_buf);
+
+	usb_free_all_descriptors(f);
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * USB gadget function
+ */
+
+static void uvc_free_inst(struct usb_function_instance *f)
+{
+	struct f_uvc_opts *opts = fi_to_f_uvc_opts(f);
+
+	mutex_destroy(&opts->lock);
+	kfree(opts);
+}
+
+static struct usb_function_instance *uvc_alloc_inst(void)
+{
+	struct f_uvc_opts *opts;
+	struct uvc_camera_terminal_descriptor *cd;
+	struct uvc_processing_unit_descriptor *pd;
+	struct uvc_output_terminal_descriptor *od;
+	struct uvc_color_matching_descriptor *md;
+	struct uvc_descriptor_header **ctl_cls;
+	int ret;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+	opts->func_inst.free_func_inst = uvc_free_inst;
+	mutex_init(&opts->lock);
+
+	cd = &opts->uvc_camera_terminal;
+	cd->bLength			= UVC_DT_CAMERA_TERMINAL_SIZE(3);
+	cd->bDescriptorType		= USB_DT_CS_INTERFACE;
+	cd->bDescriptorSubType		= UVC_VC_INPUT_TERMINAL;
+	cd->bTerminalID			= 1;
+	cd->wTerminalType		= cpu_to_le16(0x0201);
+	cd->bAssocTerminal		= 0;
+	cd->iTerminal			= 0;
+	cd->wObjectiveFocalLengthMin	= cpu_to_le16(0);
+	cd->wObjectiveFocalLengthMax	= cpu_to_le16(0);
+	cd->wOcularFocalLength		= cpu_to_le16(0);
+	cd->bControlSize		= 3;
+	cd->bmControls[0]		= 2;
+	cd->bmControls[1]		= 0;
+	cd->bmControls[2]		= 0;
+
+	pd = &opts->uvc_processing;
+	pd->bLength			= UVC_DT_PROCESSING_UNIT_SIZE(2);
+	pd->bDescriptorType		= USB_DT_CS_INTERFACE;
+	pd->bDescriptorSubType		= UVC_VC_PROCESSING_UNIT;
+	pd->bUnitID			= 2;
+	pd->bSourceID			= 1;
+	pd->wMaxMultiplier		= cpu_to_le16(16*1024);
+	pd->bControlSize		= 2;
+	pd->bmControls[0]		= 1;
+	pd->bmControls[1]		= 0;
+	pd->iProcessing			= 0;
+	pd->bmVideoStandards		= 0;
+
+	od = &opts->uvc_output_terminal;
+	od->bLength			= UVC_DT_OUTPUT_TERMINAL_SIZE;
+	od->bDescriptorType		= USB_DT_CS_INTERFACE;
+	od->bDescriptorSubType		= UVC_VC_OUTPUT_TERMINAL;
+	od->bTerminalID			= 3;
+	od->wTerminalType		= cpu_to_le16(0x0101);
+	od->bAssocTerminal		= 0;
+	od->bSourceID			= 2;
+	od->iTerminal			= 0;
+
+	md = &opts->uvc_color_matching;
+	md->bLength			= UVC_DT_COLOR_MATCHING_SIZE;
+	md->bDescriptorType		= USB_DT_CS_INTERFACE;
+	md->bDescriptorSubType		= UVC_VS_COLORFORMAT;
+	md->bColorPrimaries		= 1;
+	md->bTransferCharacteristics	= 1;
+	md->bMatrixCoefficients		= 4;
+
+	/* Prepare fs control class descriptors for configfs-based gadgets */
+	ctl_cls = opts->uvc_fs_control_cls;
+	ctl_cls[0] = NULL;	/* assigned elsewhere by configfs */
+	ctl_cls[1] = (struct uvc_descriptor_header *)cd;
+	ctl_cls[2] = (struct uvc_descriptor_header *)pd;
+	ctl_cls[3] = (struct uvc_descriptor_header *)od;
+	ctl_cls[4] = NULL;	/* NULL-terminate */
+	opts->fs_control =
+		(const struct uvc_descriptor_header * const *)ctl_cls;
+
+	/* Prepare hs control class descriptors for configfs-based gadgets */
+	ctl_cls = opts->uvc_ss_control_cls;
+	ctl_cls[0] = NULL;	/* assigned elsewhere by configfs */
+	ctl_cls[1] = (struct uvc_descriptor_header *)cd;
+	ctl_cls[2] = (struct uvc_descriptor_header *)pd;
+	ctl_cls[3] = (struct uvc_descriptor_header *)od;
+	ctl_cls[4] = NULL;	/* NULL-terminate */
+	opts->ss_control =
+		(const struct uvc_descriptor_header * const *)ctl_cls;
+
+	opts->streaming_interval = 1;
+	opts->streaming_maxpacket = 1024;
+
+	ret = uvcg_attach_configfs(opts);
+	if (ret < 0) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+
+	return &opts->func_inst;
+}
+
+static void uvc_free(struct usb_function *f)
+{
+	struct uvc_device *uvc = to_uvc(f);
+	struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts,
+					       func_inst);
+	--opts->refcnt;
+	kfree(uvc);
+}
+
+static void uvc_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct uvc_device *uvc = to_uvc(f);
+
+	uvcg_info(f, "%s\n", __func__);
+
+	device_remove_file(&uvc->vdev.dev, &dev_attr_function_name);
+	video_unregister_device(&uvc->vdev);
+	v4l2_device_unregister(&uvc->v4l2_dev);
+
+	usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+	kfree(uvc->control_buf);
+
+	usb_free_all_descriptors(f);
+}
+
+static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
+{
+	struct uvc_device *uvc;
+	struct f_uvc_opts *opts;
+	struct uvc_descriptor_header **strm_cls;
+
+	uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
+	if (uvc == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&uvc->video.mutex);
+	uvc->state = UVC_STATE_DISCONNECTED;
+	opts = fi_to_f_uvc_opts(fi);
+
+	mutex_lock(&opts->lock);
+	if (opts->uvc_fs_streaming_cls) {
+		strm_cls = opts->uvc_fs_streaming_cls;
+		opts->fs_streaming =
+			(const struct uvc_descriptor_header * const *)strm_cls;
+	}
+	if (opts->uvc_hs_streaming_cls) {
+		strm_cls = opts->uvc_hs_streaming_cls;
+		opts->hs_streaming =
+			(const struct uvc_descriptor_header * const *)strm_cls;
+	}
+	if (opts->uvc_ss_streaming_cls) {
+		strm_cls = opts->uvc_ss_streaming_cls;
+		opts->ss_streaming =
+			(const struct uvc_descriptor_header * const *)strm_cls;
+	}
+
+	uvc->desc.fs_control = opts->fs_control;
+	uvc->desc.ss_control = opts->ss_control;
+	uvc->desc.fs_streaming = opts->fs_streaming;
+	uvc->desc.hs_streaming = opts->hs_streaming;
+	uvc->desc.ss_streaming = opts->ss_streaming;
+	++opts->refcnt;
+	mutex_unlock(&opts->lock);
+
+	/* Register the function. */
+	uvc->func.name = "uvc";
+	uvc->func.bind = uvc_function_bind;
+	uvc->func.unbind = uvc_unbind;
+	uvc->func.get_alt = uvc_function_get_alt;
+	uvc->func.set_alt = uvc_function_set_alt;
+	uvc->func.disable = uvc_function_disable;
+	uvc->func.setup = uvc_function_setup;
+	uvc->func.free_func = uvc_free;
+	uvc->func.bind_deactivated = true;
+
+	return &uvc->func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Laurent Pinchart");
diff --git a/marvell/linux/drivers/usb/gadget/function/f_uvc.h b/marvell/linux/drivers/usb/gadget/function/f_uvc.h
new file mode 100644
index 0000000..a81a177
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/f_uvc.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	f_uvc.h  --  USB Video Class Gadget driver
+ *
+ *	Copyright (C) 2009-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#ifndef _F_UVC_H_
+#define _F_UVC_H_
+
+struct uvc_device;
+
+void uvc_function_setup_continue(struct uvc_device *uvc);
+
+void uvc_function_connect(struct uvc_device *uvc);
+
+void uvc_function_disconnect(struct uvc_device *uvc);
+
+#endif /* _F_UVC_H_ */
diff --git a/marvell/linux/drivers/usb/gadget/function/g_zero.h b/marvell/linux/drivers/usb/gadget/function/g_zero.h
new file mode 100644
index 0000000..98b8462
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/g_zero.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header declares the utility functions used by "Gadget Zero", plus
+ * interfaces to its two single-configuration function drivers.
+ */
+
+#ifndef __G_ZERO_H
+#define __G_ZERO_H
+
+#define GZERO_BULK_BUFLEN	4096
+#define GZERO_QLEN		32
+#define GZERO_ISOC_INTERVAL	4
+#define GZERO_ISOC_MAXPACKET	1024
+#define GZERO_SS_BULK_QLEN	1
+#define GZERO_SS_ISO_QLEN	8
+
+struct usb_zero_options {
+	unsigned pattern;
+	unsigned isoc_interval;
+	unsigned isoc_maxpacket;
+	unsigned isoc_mult;
+	unsigned isoc_maxburst;
+	unsigned bulk_buflen;
+	unsigned qlen;
+	unsigned ss_bulk_qlen;
+	unsigned ss_iso_qlen;
+};
+
+struct f_ss_opts {
+	struct usb_function_instance func_inst;
+	unsigned pattern;
+	unsigned isoc_interval;
+	unsigned isoc_maxpacket;
+	unsigned isoc_mult;
+	unsigned isoc_maxburst;
+	unsigned bulk_buflen;
+	unsigned bulk_qlen;
+	unsigned iso_qlen;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+struct f_lb_opts {
+	struct usb_function_instance func_inst;
+	unsigned bulk_buflen;
+	unsigned qlen;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+void lb_modexit(void);
+int lb_modinit(void);
+
+/* common utilities */
+void disable_endpoints(struct usb_composite_dev *cdev,
+		struct usb_ep *in, struct usb_ep *out,
+		struct usb_ep *iso_in, struct usb_ep *iso_out);
+
+#endif /* __G_ZERO_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/gadget_chips.h b/marvell/linux/drivers/usb/gadget/function/gadget_chips.h
new file mode 100644
index 0000000..bcd04bc
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/gadget_chips.h
@@ -0,0 +1,55 @@
+/*
+ * USB device controllers have lots of quirks.  Use these macros in
+ * gadget drivers or other code that needs to deal with them, and which
+ * autoconfigures instead of using early binding to the hardware.
+ *
+ * This SHOULD eventually work like the ARM mach_is_*() stuff, driven by
+ * some config file that gets updated as new hardware is supported.
+ * (And avoiding all runtime comparisons in typical one-choice configs!)
+ *
+ * NOTE:  some of these controller drivers may not be available yet.
+ * Some are available on 2.4 kernels; several are available, but not
+ * yet pushed in the 2.6 mainline tree.
+ */
+
+#ifndef __GADGET_CHIPS_H
+#define __GADGET_CHIPS_H
+
+#include <linux/usb/gadget.h>
+
+/*
+ * NOTICE: the entries below are alphabetical and should be kept
+ * that way.
+ *
+ * Always be sure to add new entries to the correct position or
+ * accept the bashing later.
+ *
+ * If you have forgotten the alphabetical order let VIM/EMACS
+ * do that for you.
+ */
+#define gadget_is_at91(g)		(!strcmp("at91_udc", (g)->name))
+#define gadget_is_goku(g)		(!strcmp("goku_udc", (g)->name))
+#define gadget_is_musbhdrc(g)		(!strcmp("musb-hdrc", (g)->name))
+#define gadget_is_net2280(g)		(!strcmp("net2280", (g)->name))
+#define gadget_is_pxa(g)		(!strcmp("pxa25x_udc", (g)->name))
+#define gadget_is_pxa27x(g)		(!strcmp("pxa27x_udc", (g)->name))
+
+/**
+ * gadget_supports_altsettings - return true if altsettings work
+ * @gadget: the gadget in question
+ */
+static inline bool gadget_supports_altsettings(struct usb_gadget *gadget)
+{
+	/* PXA 21x/25x/26x has no altsettings at all */
+	if (gadget_is_pxa(gadget))
+		return false;
+
+	/* PXA 27x and 3xx have *broken* altsetting support */
+	if (gadget_is_pxa27x(gadget))
+		return false;
+
+	/* Everything else is *presumably* fine ... */
+	return true;
+}
+
+#endif /* __GADGET_CHIPS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/mass_storage.c b/marvell/linux/drivers/usb/gadget/function/mass_storage.c
new file mode 100644
index 0000000..7186360
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/mass_storage.c
@@ -0,0 +1,204 @@
+/*
+ * mass_storage.c -- Mass Storage USB Gadget
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyright (C) 2009 Samsung Electronics
+ *                    Author: Michal Nazarewicz <mina86@mina86.com>
+ * 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+/*
+ * The Mass Storage Gadget acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive.  In
+ * addition to providing an example of a genuinely useful gadget
+ * driver for a USB device, it also illustrates a technique of
+ * double-buffering for increased throughput.  Last but not least, it
+ * gives an easy way to probe the behavior of the Mass Storage drivers
+ * in a USB host.
+ *
+ * Since this file serves only administrative purposes and all the
+ * business logic is implemented in f_mass_storage.* file.  Read
+ * comments in this file for more detailed description.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/usb/ch9.h>
+#include <linux/module.h>
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_DESC		"Mass Storage Gadget"
+#define DRIVER_VERSION		"2009/09/11"
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module.  So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "f_mass_storage.c"
+
+/*-------------------------------------------------------------------------*/
+USB_GADGET_COMPOSITE_OPTIONS();
+
+static struct usb_device_descriptor msg_device_desc = {
+	.bLength =		sizeof msg_device_desc,
+	.bDescriptorType =	USB_DT_DEVICE,
+
+	.bcdUSB =		cpu_to_le16(0x0200),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+
+	/* Vendor and product id can be overridden by module parameters.  */
+	.idVendor =		cpu_to_le16(FSG_VENDOR_ID),
+	.idProduct =		cpu_to_le16(FSG_PRODUCT_ID),
+	.bNumConfigurations =	1,
+};
+
+static struct usb_otg_descriptor otg_descriptor = {
+	.bLength =		sizeof otg_descriptor,
+	.bDescriptorType =	USB_DT_OTG,
+
+	/*
+	 * REVISIT SRP-only hardware is possible, although
+	 * it would not be called "OTG" ...
+	 */
+	.bmAttributes =		USB_OTG_SRP | USB_OTG_HNP,
+};
+
+static const struct usb_descriptor_header *otg_desc[] = {
+	(struct usb_descriptor_header *) &otg_descriptor,
+	NULL,
+};
+
+static struct usb_string strings_dev[] = {
+	[USB_GADGET_MANUFACTURER_IDX].s = "",
+	[USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC,
+	[USB_GADGET_SERIAL_IDX].s = "",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+	.language       = 0x0409,       /* en-us */
+	.strings        = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+	&stringtab_dev,
+	NULL,
+};
+
+/****************************** Configurations ******************************/
+
+static struct fsg_module_parameters mod_data = {
+	.stall = 1
+};
+FSG_MODULE_PARAMETERS(/* no prefix */, mod_data);
+
+static unsigned long msg_registered;
+static void msg_cleanup(void);
+
+static int msg_thread_exits(struct fsg_common *common)
+{
+	msg_cleanup();
+	return 0;
+}
+
+static int __init msg_do_config(struct usb_configuration *c)
+{
+	static const struct fsg_operations ops = {
+		.thread_exits = msg_thread_exits,
+	};
+	static struct fsg_common common;
+
+	struct fsg_common *retp;
+	struct fsg_config config;
+	int ret;
+
+	if (gadget_is_otg(c->cdev->gadget)) {
+		c->descriptors = otg_desc;
+		c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+	}
+
+	fsg_config_from_params(&config, &mod_data);
+	config.ops = &ops;
+
+	retp = fsg_common_init(&common, c->cdev, &config);
+	if (IS_ERR(retp))
+		return PTR_ERR(retp);
+
+	ret = fsg_bind_config(c->cdev, c, &common);
+	fsg_common_put(&common);
+	return ret;
+}
+
+static struct usb_configuration msg_config_driver = {
+	.label			= "Linux File-Backed Storage",
+	.bConfigurationValue	= 1,
+	.bmAttributes		= USB_CONFIG_ATT_SELFPOWER,
+};
+
+
+/****************************** Gadget Bind ******************************/
+
+static int __init msg_bind(struct usb_composite_dev *cdev)
+{
+	int status;
+
+	if (strings_dev[0].id == 0) {
+		status = usb_string_ids_tab(cdev, strings_dev);
+		if (status < 0) {
+			dev_err(&cdev->gadget->dev, "%s usb_string_ids_tab %d\n", status);
+			return status;
+		}
+	}
+
+	msg_device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
+
+	status = usb_add_config(cdev, &msg_config_driver, msg_do_config);
+	if (status < 0)
+		return status;
+	usb_composite_overwrite_options(cdev, &coverwrite);
+	dev_info(&cdev->gadget->dev,
+		 DRIVER_DESC ", version: " DRIVER_VERSION "\n");
+	set_bit(0, &msg_registered);
+	return 0;
+}
+
+
+/****************************** Some noise ******************************/
+
+static __refdata struct usb_composite_driver msg_driver = {
+	.name		= "g_mass_storage",
+	.dev		= &msg_device_desc,
+	.max_speed	= USB_SPEED_SUPER,
+	.needs_serial	= 1,
+	.strings	= dev_strings,
+	.bind		= msg_bind,
+};
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Michal Nazarewicz");
+MODULE_LICENSE("GPL");
+
+static int __init msg_init(void)
+{
+	return usb_composite_probe(&msg_driver);
+}
+module_init(msg_init);
+
+static void msg_cleanup(void)
+{
+	if (test_and_clear_bit(0, &msg_registered))
+		usb_composite_unregister(&msg_driver);
+}
+module_exit(msg_cleanup);
diff --git a/marvell/linux/drivers/usb/gadget/function/ndis.h b/marvell/linux/drivers/usb/gadget/function/ndis.h
new file mode 100644
index 0000000..a19f72d
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/ndis.h
@@ -0,0 +1,47 @@
+/*
+ * ndis.h
+ *
+ * ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de>
+ *
+ * Thanks to the cygwin development team,
+ * espacially to Casper S. Hornstrup <chorns@users.sourceforge.net>
+ *
+ * THIS SOFTWARE IS NOT COPYRIGHTED
+ *
+ * This source code is offered for use in the public domain. You may
+ * use, modify or distribute it freely.
+ */
+
+#ifndef _LINUX_NDIS_H
+#define _LINUX_NDIS_H
+
+enum NDIS_DEVICE_POWER_STATE {
+	NdisDeviceStateUnspecified = 0,
+	NdisDeviceStateD0,
+	NdisDeviceStateD1,
+	NdisDeviceStateD2,
+	NdisDeviceStateD3,
+	NdisDeviceStateMaximum
+};
+
+struct NDIS_PM_WAKE_UP_CAPABILITIES {
+	enum NDIS_DEVICE_POWER_STATE  MinMagicPacketWakeUp;
+	enum NDIS_DEVICE_POWER_STATE  MinPatternWakeUp;
+	enum NDIS_DEVICE_POWER_STATE  MinLinkChangeWakeUp;
+};
+
+struct NDIS_PNP_CAPABILITIES {
+	__le32					Flags;
+	struct NDIS_PM_WAKE_UP_CAPABILITIES	WakeUpCapabilities;
+};
+
+struct NDIS_PM_PACKET_PATTERN {
+	__le32	Priority;
+	__le32	Reserved;
+	__le32	MaskSize;
+	__le32	PatternOffset;
+	__le32	PatternSize;
+	__le32	PatternFlags;
+};
+
+#endif /* _LINUX_NDIS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa182x_acm_debug.c b/marvell/linux/drivers/usb/gadget/function/pxa182x_acm_debug.c
new file mode 100644
index 0000000..9f63557
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa182x_acm_debug.c
@@ -0,0 +1,904 @@
+/*
+ * pxa182x_acm_debug.c -- USB CDC serial (ACM) function driver
+ * Based on pxa910_f_diag.c
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 2009 by Samsung Electronics
+ * Author: Michal Nazarewicz (mina86@mina86.com)
+ *
+ * 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.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/usb/composite.h>
+#include <linux/workqueue.h>
+#include <linux/cputype.h>
+
+#include "pxa182x_serial_debug.h"
+#include "gadget_chips.h"
+
+/*
+ * This CDC ACM function support just wraps control functions and
+ * notifications around the generic serial-over-usb code.
+ *
+ * Because CDC ACM is standardized by the USB-IF, many host operating
+ * systems have drivers for it.  Accordingly, ACM is the preferred
+ * interop solution for serial-port type connections.  The control
+ * models are often not necessary, and in any case don't do much in
+ * this bare-bones implementation.
+ *
+ * Note that even MS-Windows has some support for ACM.  However, that
+ * support is somewhat broken because when you use ACM in a composite
+ * device, having multiple interfaces confuses the poor OS.  It doesn't
+ * seem to understand CDC Union descriptors.  The new "association"
+ * descriptors (roughly equivalent to CDC Unions) may sometimes help.
+ */
+#define GS_DEBUG_PORT_BASE 1
+
+struct pxa182x_ports {
+	struct pxa182x_gserial		port;
+	u8				data_id;
+	u8				port_num;
+};
+
+static inline struct pxa182x_ports *pxa182x_func_to_debug(struct usb_function *f)
+{
+	return container_of(f, struct pxa182x_ports, port.func);
+}
+
+static inline struct
+pxa182x_ports *pxa182x_port_to_debug(struct pxa182x_gserial *p)
+{
+	return container_of(p, struct pxa182x_ports, port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* interface descriptor: */
+static struct usb_interface_descriptor pxa182x_debug_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0xff,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor pxa182x_debug_fs_in_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor pxa182x_debug_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *pxa182x_debug_fs_function[] = {
+	(struct usb_descriptor_header *)&pxa182x_debug_interface_desc,
+	(struct usb_descriptor_header *)&pxa182x_debug_fs_in_desc,
+	(struct usb_descriptor_header *)&pxa182x_debug_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor pxa182x_debug_hs_in_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor pxa182x_debug_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *pxa182x_debug_hs_function[] = {
+	(struct usb_descriptor_header *)&pxa182x_debug_interface_desc,
+	(struct usb_descriptor_header *)&pxa182x_debug_hs_in_desc,
+	(struct usb_descriptor_header *)&pxa182x_debug_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor pxa182x_debug_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor pxa182x_debug_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor pxa182x_debug_ss_bulk_comp_desc = {
+	.bLength =              sizeof pxa182x_debug_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *pxa182x_debug_ss_function[] = {
+	(struct usb_descriptor_header *) &pxa182x_debug_interface_desc,
+	(struct usb_descriptor_header *) &pxa182x_debug_ss_in_desc,
+	(struct usb_descriptor_header *) &pxa182x_debug_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &pxa182x_debug_ss_out_desc,
+	(struct usb_descriptor_header *) &pxa182x_debug_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+/* static strings, in UTF-8 */
+static struct usb_string pxa182x_debug_string_defs[] = {
+	[0].s = "Marvell DEBUG",
+	{  /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings pxa182x_debug_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		pxa182x_debug_string_defs,
+};
+
+static struct usb_gadget_strings *pxa182x_debug_strings[] = {
+	&pxa182x_debug_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int
+pxa182x_debug_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct pxa182x_ports *m_debug = pxa182x_func_to_debug(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (m_debug->port.in->driver_data) {
+		DBG(cdev, "reset marvell_debug tty_debug%d\n", m_debug->port_num);
+		pxa182x_gserial_disconnect(&m_debug->port);
+	} else {
+		DBG(cdev, "activate generic tty_debug%d\n", m_debug->port_num);
+	}
+	if (!m_debug->port.in->desc || !m_debug->port.out->desc) {
+		DBG(cdev, "activate marvell_debug tty_debug%d\n", m_debug->port_num);
+		if (config_ep_by_speed(cdev->gadget, f, m_debug->port.in) ||
+			config_ep_by_speed(cdev->gadget, f, m_debug->port.out)) {
+			m_debug->port.in->desc = NULL;
+			m_debug->port.out->desc = NULL;
+			return -EINVAL;
+		}
+	}
+
+	pxa182x_gserial_connect(&m_debug->port, m_debug->port_num);
+
+	return 0;
+}
+
+static void pxa182x_debug_disable(struct usb_function *f)
+{
+	struct pxa182x_ports	*debug = pxa182x_func_to_debug(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "debug tty_debug%d deactivated\n", debug->port_num);
+
+	pxa182x_gserial_disconnect(&debug->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* DEBUG function driver setup/binding */
+static int
+pxa182x_debug_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct pxa182x_ports	*m_debug = pxa182x_func_to_debug(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	m_debug->data_id = status;
+
+	pxa182x_debug_interface_desc.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa182x_debug_fs_in_desc);
+	if (!ep)
+		goto fail;
+	m_debug->port.in = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa182x_debug_fs_out_desc);
+	if (!ep)
+		goto fail;
+	m_debug->port.out = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	/* copy descriptors */
+	f->fs_descriptors = usb_copy_descriptors(pxa182x_debug_fs_function);
+	if (!f->fs_descriptors)
+		goto fail;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		pxa182x_debug_hs_in_desc.bEndpointAddress =
+				pxa182x_debug_fs_in_desc.bEndpointAddress;
+		pxa182x_debug_hs_out_desc.bEndpointAddress =
+				pxa182x_debug_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors */
+		f->hs_descriptors =
+			usb_copy_descriptors(pxa182x_debug_hs_function);
+	}
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		pxa182x_debug_ss_in_desc.bEndpointAddress =
+			pxa182x_debug_fs_in_desc.bEndpointAddress;
+		pxa182x_debug_ss_out_desc.bEndpointAddress =
+			pxa182x_debug_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ss_descriptors =
+			usb_copy_descriptors(pxa182x_debug_ss_function);
+		if (!f->ss_descriptors)
+			goto fail;
+	}
+	if (gadget_is_superspeed_plus(c->cdev->gadget)) {
+		pxa182x_debug_ss_in_desc.bEndpointAddress =
+			pxa182x_debug_fs_in_desc.bEndpointAddress;
+		pxa182x_debug_ss_out_desc.bEndpointAddress =
+			pxa182x_debug_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ssp_descriptors =
+			usb_copy_descriptors(pxa182x_debug_ss_function);
+		if (!f->ssp_descriptors)
+			goto fail;
+	}
+	DBG(cdev, "marvell_debug tty_debug%d: %s speed IN/%s OUT/%s\n",
+			m_debug->port_num,
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			m_debug->port.in->name, m_debug->port.out->name);
+	return 0;
+
+fail:
+	/* we might as well release our claims on endpoints */
+	if (m_debug->port.out)
+		m_debug->port.out->driver_data = NULL;
+	if (m_debug->port.in)
+		m_debug->port.in->driver_data = NULL;
+
+	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+	return status;
+}
+
+static void
+pxa182x_debug_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct pxa182x_ports	*m_debug = pxa182x_func_to_debug(f);
+
+	if (gadget_is_dualspeed(c->cdev->gadget))
+		usb_free_descriptors(f->hs_descriptors);
+	if (gadget_is_superspeed(c->cdev->gadget))
+		usb_free_descriptors(f->ss_descriptors);
+	usb_free_descriptors(f->fs_descriptors);
+	kfree(m_debug);
+
+	#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		pxa182x_debug_string_defs[0].id = 0;
+	#endif
+
+}
+
+/**
+ * acm_bind_config - add a CDC ACM function to a configuration
+ * @c: the configuration to support the CDC ACM instance
+ * @port_num: /dev/tty_debug* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds.  Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int pxa182x_debug_bind_config(struct usb_configuration *c, u8 port_num)
+{
+	struct pxa182x_ports	*m_debug;
+	int		status;
+
+	/* REVISIT might want instance-specific strings to help
+	 * distinguish instances ...
+	 */
+
+	/* maybe allocate device-global string IDs, and patch descriptors */
+	if (pxa182x_debug_string_defs[0].id == 0) {
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+
+		pxa182x_debug_string_defs[0].id = status;
+
+		pxa182x_debug_interface_desc.iInterface = status;
+	}
+
+	/* allocate and initialize one new instance */
+	m_debug = kzalloc(sizeof *m_debug, GFP_KERNEL);
+	if (!m_debug)
+		return -ENOMEM;
+
+	m_debug->port_num = port_num;
+
+	m_debug->port.func.name = "m_debug";
+	m_debug->port.func.strings = pxa182x_debug_strings;
+	/* descriptors are per-instance copies */
+	m_debug->port.func.bind = pxa182x_debug_bind;
+	m_debug->port.func.unbind = pxa182x_debug_unbind;
+	m_debug->port.func.set_alt = pxa182x_debug_set_alt;
+	m_debug->port.func.disable = pxa182x_debug_disable;
+
+	init_waitqueue_head(&m_debug->port.port_send);
+	status = usb_add_function(c, &m_debug->port.func);
+	if (status)
+		kfree(m_debug);
+	return status;
+}
+
+static int gs_marvell_debug_write(struct tty_struct *tty,
+				 const unsigned char *buf, int count)
+{
+	struct pxa182x_gs_port *port = tty->driver_data;
+	unsigned long flags;
+	int status;
+
+	pr_debug("gs_marvell_debug_write: tty_debug%d (%p) writing %d bytes\n",
+		  port->port_num, tty, count);
+
+	if (port == NULL)
+		goto out;
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (count)
+		count = pxa182x_gs_buf_put(&port->port_write_buf, buf, count);
+
+	/* treat count == 0 as flush_chars() */
+	if (port->port_usb)
+		status = pxa182x_gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+out:
+	if (count == 0)
+		return -EAGAIN;
+	return count;
+}
+
+int gs_marvell_debug_send(const unsigned char *buf, int count)
+{
+	struct tty_struct *tty;
+	struct pxa182x_gs_port *port;
+	int c, retval = 0;
+	const unsigned char *b = buf;
+	unsigned long flags;
+	wait_queue_head_t *p_port_send;
+
+	/* Assume that we have only one port */
+	port = pxa182x_ports[GS_DEBUG_PORT_BASE].port;
+	tty = port->port.tty;
+
+	if (tty == NULL || tty->driver_data == NULL || port->port_usb == NULL)
+		return count;
+	while (count > 0) {
+		c = gs_marvell_debug_write(tty, b, count);
+
+		if (c == count) {
+			b += c;
+			break;	/* send all bytes successfully */
+		} else if (c >= 0) {
+			b += c;
+			count -= c;
+			continue;
+		} else if (c < 0) {
+			retval = c;
+			if (c != -EAGAIN)
+				goto break_out;
+		}
+
+		/* retry to wait enough buffer to send the rest bytes. */
+		spin_lock_irqsave(&port->port_lock, flags);
+
+		if (port->port_usb) {
+			p_port_send = &port->port_usb->port_send;
+		} else {
+			p_port_send = NULL;
+			printk(KERN_INFO "%s: usb port is released.\n",
+					__func__);
+		}
+
+		spin_unlock_irqrestore(&port->port_lock, flags);
+
+		if (p_port_send) {
+			//interruptible_sleep_on_timeout(p_port_send,
+						       //10 * HZ / 1000);
+			wait_event_interruptible_timeout(*p_port_send, 1, 10 * HZ / 1000);
+		}
+
+		port = pxa182x_ports[GS_DEBUG_PORT_BASE].port;
+		tty = port->port.tty;
+		if (tty == NULL || tty->driver_data == NULL
+		    || port->port_usb == NULL)
+			return count;
+	}
+break_out:
+	return (b - buf) ? b - buf : retval;
+}
+EXPORT_SYMBOL(gs_marvell_debug_send);
+
+typedef int (*marvell_debug_rx_callback) (char *packet, int len);
+
+marvell_debug_rx_callback gs_marvell_debug_rx_callback =
+		(marvell_debug_rx_callback) NULL;
+EXPORT_SYMBOL(gs_marvell_debug_rx_callback);
+
+typedef int (*marvell_debug_ioctl) (unsigned int cmd, unsigned long arg);
+
+marvell_debug_ioctl gs_marvell_debug_ioctl = (marvell_debug_ioctl) NULL;
+EXPORT_SYMBOL(gs_marvell_debug_ioctl);
+
+extern void dwc3_set_burst1(bool set);
+extern void dwc3_hwsulog_on(bool is_on);
+#ifdef CONFIG_USB_DWC3
+#ifdef CONFIG_DWC3_HWSULOG
+static u8 sulog_st_cmd[4] 	= {0x35, 0x20, 0x30, 0x0a};
+static u8 sulog_stop_cmd [4]	= {0x36, 0x20, 0x46, 0x0a};
+#endif
+#endif
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void gs_marvell_debug_rx_push(unsigned long _port)
+{
+	struct pxa182x_gs_port *port = (void *)_port;
+	struct tty_struct *tty;
+	struct list_head *queue = &port->read_queue;
+	bool disconnect = false;
+	bool do_push = false;
+	struct timespec64 now;
+
+	/* hand any queued data to the tty */
+	spin_lock_irq(&port->port_lock);
+	tty = port->port.tty;
+	while (!list_empty(queue)) {
+		struct usb_request *req;
+
+		//now = current_time();
+		ktime_get_real_ts64(&now);
+		req = list_first_entry(queue, struct usb_request, list);
+
+		/* discard data if tty was closed */
+		if (!tty)
+			goto recycle;
+
+		/* leave data queued if tty was rx throttled */
+		if (test_bit(TTY_THROTTLED, &tty->flags))
+			break;
+
+		switch (req->status) {
+		case -ESHUTDOWN:
+			disconnect = true;
+			pr_debug(PREFIX "%d: shutdown\n", port->port_num);
+			break;
+
+		default:
+			/* presumably a transient fault */
+			pr_warning(PREFIX "%d: unexpected RX status %d\n",
+				   port->port_num, req->status);
+			/* FALLTHROUGH */
+		case 0:
+			/* normal completion */
+			break;
+		}
+
+		/* push data to (open) tty */
+		if (req->actual) {
+			char *packet = req->buf;
+			unsigned size = req->actual;
+			unsigned n;
+			int count;
+
+			/* we may have pushed part of this packet already... */
+			n = port->n_read;
+			if (n) {
+				packet += n;
+				size -= n;
+			}
+#ifdef CONFIG_USB_DWC3
+#ifdef CONFIG_DWC3_HWSULOG
+			if (size == 4 && (memcmp(sulog_st_cmd, req->buf, 4) == 0)) {
+				dwc3_set_burst1(true);
+				dwc3_hwsulog_on(true);
+			} else if (size == 4 && (memcmp(sulog_stop_cmd, req->buf, 4) == 0)) {
+				dwc3_set_burst1(false);
+				dwc3_hwsulog_on(false);
+			}
+#endif
+#endif
+
+			if (gs_marvell_debug_rx_callback !=
+			    (marvell_debug_rx_callback) NULL) {
+				int filtered;
+				filtered =
+				    gs_marvell_debug_rx_callback(packet, size);
+				if (filtered)
+					goto recycle;
+			}
+
+			count = tty_insert_flip_string(tty->port, packet, size);
+			if (count)
+				do_push = true;
+			if (count != size) {
+				/* stop pushing; TTY layer can't handle more */
+				port->n_read += count;
+				pr_debug(PREFIX "%d: rx block %d/%d\n",
+					  port->port_num, count, req->actual);
+				break;
+			}
+			port->n_read = 0;
+		}
+recycle:
+		list_move(&req->list, &port->read_pool);
+		port->read_started--;
+	}
+
+	/* Push from tty to ldisc; this is immediate with low_latency, and
+	 * may trigger callbacks to this driver ... so drop the spinlock.
+	 */
+	if (tty && do_push) {
+		spin_unlock_irq(&port->port_lock);
+		tty_flip_buffer_push(tty->port);
+		wake_up_interruptible(&tty->read_wait);
+		spin_lock_irq(&port->port_lock);
+
+		/* tty may have been closed */
+		tty = port->port.tty;
+	}
+
+	/* We want our data queue to become empty ASAP, keeping data
+	 * in the tty and ldisc (not here).  If we couldn't push any
+	 * this time around, there may be trouble unless there's an
+	 * implicit tty_unthrottle() call on its way...
+	 *
+	 * REVISIT we should probably add a timer to keep the tasklet
+	 * from starving ... but it's not clear that case ever happens.
+	 */
+	if (!list_empty(queue) && tty) {
+		if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+			if (do_push)
+				tasklet_schedule(&port->push);
+			else
+				pr_warning(PREFIX "%d: RX not scheduled?\n",
+					   port->port_num);
+		}
+	}
+
+	/* If we're still connected, refill the USB RX queue. */
+	if (!disconnect && port->port_usb)
+		px182x_gs_start_rx(port);
+
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int gs_marvell_debug_port_alloc(unsigned port_num,
+			   struct usb_cdc_line_coding *coding)
+{
+	struct pxa182x_gs_port *port;
+
+	port = kzalloc(sizeof(struct pxa182x_gs_port), GFP_KERNEL);
+	if (port == NULL)
+		return -ENOMEM;
+
+	tty_port_init(&port->port);
+	spin_lock_init(&port->port_lock);
+	init_waitqueue_head(&port->drain_wait);
+	init_waitqueue_head(&port->close_wait);
+
+	tasklet_init(&port->push, gs_marvell_debug_rx_push, (unsigned long)port);
+
+	INIT_LIST_HEAD(&port->read_pool);
+	INIT_LIST_HEAD(&port->read_queue);
+	INIT_LIST_HEAD(&port->write_pool);
+
+	port->port_num = port_num;
+	port->port_line_coding = *coding;
+	port->tx_wq = create_singlethread_workqueue("debug_gadget_tx");
+	INIT_WORK(&port->tx_work, pxa182x_gs_tx_worker);
+	pxa182x_ports[port_num].port = port;
+
+
+	return 0;
+}
+
+#ifdef DEBUG_USB_TEST
+static void debugUsbTest3()
+{
+	int i;
+	int ret;
+	unsigned long start, end, timeuse;
+
+	memset(testbuf, 'b', sizeof(testbuf));
+	printk(KERN_INFO "marvell_debug usb test start...\n");
+	msleep_interruptible(1000);
+
+	start = jiffies;
+	for (i = 0; i < lpcunt; i++) {
+		ret = gs_usb_write(gs_tty_debug, testbuf, sizeof(testbuf));
+		if (ret != sizeof(testbuf))
+			printk(KERN_ERR "usb write error!ret=%d\n", ret);
+
+		while (!writecomplete) {
+			if (wait_event_interruptible(wcwaitQ, \
+						writecomplete == 1))
+				continue;
+		}
+		writecomplete = 0;
+	}
+	end = jiffies;
+	timeuse = (end - start) * 1000 / HZ;
+	printk(KERN_INFO "sending %d Kbytes to use take %d ms\n",
+	       lpcunt * sizeof(testbuf) / 1024, timeuse);
+}
+
+static void diagUsbTest(int caseNo)
+{
+
+	init_waitqueue_head(&wcwaitQ);
+	switch (caseNo) {
+	case 1:
+		/* diagUsbTest1(); */
+		break;
+	case 2:
+		/* diagUsbTest2(); */
+		break;
+	case 3:
+		debugUsbTest3();
+		break;
+	}
+}
+#endif
+
+/*
+ * gs_ioctl
+ */
+static int pxa182x_debug_ioctl(struct tty_struct *tty, unsigned int cmd,
+		    unsigned long arg)
+{
+	struct pxa182x_gs_port *port;
+	struct pxa182x_ports *diag;
+	int    rc = -ENOIOCTLCMD;
+
+	if (tty == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	port = tty->driver_data;
+	if (port == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty->driver_data is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	diag = pxa182x_port_to_debug(port->port_usb);
+	if (diag == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: diag is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+
+	/* handle ioctls */
+	if (gs_marvell_debug_ioctl)
+		rc = gs_marvell_debug_ioctl(cmd, arg);
+
+	return rc;
+}
+
+static const struct tty_operations gs_marvell_debug_tty_ops = {
+	.open = pxa182x_gs_open,
+	.close = pxa182x_gs_close,
+	.write = gs_marvell_debug_write,
+	.put_char = pxa182x_gs_put_char,
+	.flush_chars = pxa182x_gs_flush_chars,
+	.write_room = pxa182x_gs_write_room,
+	.chars_in_buffer = pxa182x_gs_chars_in_buffer,
+	.ioctl = pxa182x_debug_ioctl,
+	.unthrottle = pxa182x_gs_unthrottle,
+	.break_ctl = pxa182x_gs_break_ctl,
+};
+
+int pxa182x_debug_gserial_setup(struct usb_gadget *g, unsigned count)
+{
+	unsigned i;
+	struct usb_cdc_line_coding coding;
+	int status;
+
+	if (count == 0 || count > PXA182X_N_PORTS)
+		return -EINVAL;
+
+	gs_debug_tty_driver = alloc_tty_driver(count);
+	if (!gs_debug_tty_driver)
+		return -ENOMEM;
+
+	gs_debug_tty_driver->owner = THIS_MODULE;
+	gs_debug_tty_driver->driver_name = "g_serial_debug";
+	gs_debug_tty_driver->name = "tty_debug";
+	/* uses dynamically assigned dev_t values */
+
+	gs_debug_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	gs_debug_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	gs_debug_tty_driver->flags =
+			TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	gs_debug_tty_driver->init_termios = tty_std_termios;
+
+	/* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+	 * MS-Windows.  Otherwise, most of these flags shouldn't affect
+	 * anything unless we were to actually hook up to a serial line.
+	 */
+	gs_debug_tty_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_DWC3_HWSULOG)
+	/* disable tty echo for hwsulog */
+	if (cpu_is_asr1803())
+		gs_debug_tty_driver->init_termios.c_lflag = 0x0;
+#ifdef CONFIG_DWC3_HWSULOG
+	else if (cpu_is_asr1828() || cpu_is_asr1901() || cpu_is_asr1906() || cpu_is_asr1903()) {
+		pr_info("disable echo for dwc3 hwsulog\n");
+		gs_debug_tty_driver->init_termios.c_lflag = 0x0;
+	}
+#endif
+#endif
+
+	gs_debug_tty_driver->init_termios.c_ispeed = 9600;
+	gs_debug_tty_driver->init_termios.c_ospeed = 9600;
+	/* Not setting major/minor number. These will be allocated dynamically */
+	gs_debug_tty_driver->major = 0;
+	gs_debug_tty_driver->minor_start = 0;
+
+	coding.dwDTERate = cpu_to_le32(9600);
+	coding.bCharFormat = 8;
+	coding.bParityType = USB_CDC_NO_PARITY;
+	coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+	tty_set_operations(gs_debug_tty_driver, &gs_marvell_debug_tty_ops);
+
+	/* make devices be openable */
+	for (i = 0; i < count; i++) {
+		mutex_init(&pxa182x_ports[GS_DEBUG_PORT_BASE + i].lock);
+		status =
+		  gs_marvell_debug_port_alloc(GS_DEBUG_PORT_BASE + i, &coding);
+		if (status) {
+			count = i;
+			goto fail;
+		}
+	}
+	n_debug_ports = count;
+	/* export the driver ... */
+	status = tty_register_driver(gs_debug_tty_driver);
+	if (status) {
+		pr_err("%s: cannot register, err %d\n", __func__, status);
+		goto fail;
+	}
+
+
+	/* ... and sysfs class devices, so mdev/udev make /dev/tty_debug* */
+	for (i = 0; i < count; i++) {
+		struct device *tty_dev;
+		struct pxa182x_gs_port *port =
+			pxa182x_ports[GS_DEBUG_PORT_BASE + i].port;
+
+		tty_dev = tty_port_register_device(&port->port,
+			gs_debug_tty_driver, i, &g->dev);
+		if (IS_ERR(tty_dev))
+			pr_warning("%s: no classdev for port %d, err %ld\n",
+				   __func__, i, PTR_ERR(tty_dev));
+	}
+
+	/*open registeration for users */
+	debug_serial_ready = true;
+
+	pr_debug("%s: registered %d tty_debug* device%s\n", __func__,
+		 count, (count == 1) ? "" : "s");
+
+	return status;
+fail:
+	while (count--)
+		kfree(pxa182x_ports[GS_DEBUG_PORT_BASE + count].port);
+	put_tty_driver(gs_debug_tty_driver);
+	gs_debug_tty_driver = NULL;
+	return status;
+}
+
+/**
+ * gserial_cleanup - remove TTY-over-USB driver and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gserial_setup().
+ * Accordingly, it may need to wait until some open /dev/ files have
+ * closed.
+ *
+ * The caller must have issued @gserial_disconnect() for any ports
+ * that had previously been connected, so that there is never any
+ * I/O pending when it's called.
+ */
+void pxa182x_debug_gserial_cleanup(void)
+{
+	unsigned	i;
+	struct pxa182x_gs_port *port;
+
+	if (!gs_debug_tty_driver)
+		return;
+
+	for (i = 0; i < n_debug_ports; i++) {
+		/* prevent new opens */
+		mutex_lock(&pxa182x_ports[GS_DEBUG_PORT_BASE + i].lock);
+		port = pxa182x_ports[GS_DEBUG_PORT_BASE + i].port;
+		pxa182x_ports[GS_DEBUG_PORT_BASE + i].port = NULL;
+		mutex_unlock(&pxa182x_ports[GS_DEBUG_PORT_BASE + i].lock);
+
+		tasklet_kill(&port->push);
+		destroy_workqueue(port->tx_wq);
+		/* wait for old opens to finish */
+		wait_event_interruptible(port->close_wait, pxa182x_gs_closed(port));
+		WARN_ON(port->port_usb != NULL);
+
+		while (!work_data_is_clear(&port->port.buf.work))
+			schedule();
+		tty_port_destroy(&port->port);
+		kfree(port);
+
+		tty_unregister_device(gs_debug_tty_driver, i);
+	}
+	n_debug_ports = 0;
+
+	tty_unregister_driver(gs_debug_tty_driver);
+	put_tty_driver(gs_debug_tty_driver);
+	gs_debug_tty_driver = NULL;
+
+	pr_debug("%s: cleaned up tty_debug* support\n", __func__);
+}
+
+
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.c b/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.c
new file mode 100644
index 0000000..6cda4f6
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.c
@@ -0,0 +1,1254 @@
+/*
+ * pxa182x_serial_debug.c - utilities for USB gadget "serial port"/TTY support
+ * Based on pxa910_u_serial.c
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This code also borrows from usbserial.c, which is
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com)
+ *
+ * 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.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/usb.h>
+
+#include "pxa182x_serial_debug.h"
+
+
+/*
+ * This component encapsulates the TTY layer glue needed to provide basic
+ * "serial port" functionality through the USB gadget stack.  Each such
+ * port is exposed through a /dev/tty_debug* node.
+ *
+ * After initialization (gserial_setup), these TTY port devices stay
+ * available until they are removed (gserial_cleanup).  Each one may be
+ * connected to a USB function (gserial_connect), or disconnected (with
+ * gserial_disconnect) when the USB host issues a config change event.
+ * Data can only flow when the port is connected to the host.
+ *
+ * A given TTY port can be made available in multiple configurations.
+ * For example, each one might expose a tty_debug0 node which provides a
+ * login application.  In one case that might use CDC ACM interface 0,
+ * while another configuration might use interface 3 for that.  The
+ * work to handle that (including descriptor management) is not part
+ * of this component.
+ *
+ * Configurations may expose more than one TTY port.  For example, if
+ * tty_debug0 provides login service, then tty_debug1 might provide dialer access
+ * for a telephone or fax link.  And tty_debug2 might be something that just
+ * needs a simple byte stream interface for some messaging protocol that
+ * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
+ */
+
+#define PREFIX_TTY	"tty_debug"
+
+/*
+ * gserial is the lifecycle interface, used by USB functions
+ * gs_port is the I/O nexus, used by the tty driver
+ * tty_struct links to the tty/filesystem framework
+ *
+ * gserial <---> gs_port ... links will be null when the USB link is
+ * inactive; managed by gserial_{connect,disconnect}().  each gserial
+ * instance can wrap its own USB control protocol.
+ *	gserial->ioport == usb_ep->driver_data ... gs_port
+ *	gs_port->port_usb ... gserial
+ *
+ * gs_port <---> tty_struct ... links will be null when the TTY file
+ * isn't opened; managed by gs_open()/gs_close()
+ *	gserial->port.tty ... tty_struct
+ *	tty_struct->driver_data ... gserial
+ */
+
+/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the
+ * next layer of buffering.  For TX that's a circular buffer; for RX
+ * consider it a NOP.  A third layer is provided by the TTY code.
+ */
+#define QUEUE_SIZE		16
+#define PXA182X_WRITE_BUF_SIZE	(8192*2)		/* TX only */
+
+/* circular buffer */
+struct pxa182x_gs_buf {
+	unsigned		buf_size;
+	char			*buf_buf;
+	char			*buf_get;
+	char			*buf_put;
+};
+
+/*
+ * The port structure holds info for each port, one for each minor number
+ * (and thus for each /dev/ node).
+ */
+struct pxa182x_gs_port {
+	struct tty_port		port;
+	spinlock_t		port_lock;	/* guard port_* access */
+
+	struct pxa182x_gserial	*port_usb;
+
+	bool			openclose;	/* open/close in progress */
+	u8			port_num;
+
+	struct list_head	read_pool;
+	int read_started;
+	int read_allocated;
+	struct list_head	read_queue;
+	unsigned		n_read;
+	struct tasklet_struct	push;
+	struct workqueue_struct *tx_wq; /* tx workqueue */
+	struct work_struct tx_work; /* tx work */
+
+	struct list_head	write_pool;
+	int write_started;
+	int write_allocated;
+	struct pxa182x_gs_buf		port_write_buf;
+	wait_queue_head_t	drain_wait;	/* wait while writes drain */
+	wait_queue_head_t	close_wait;
+
+	/* REVISIT this state ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 8-N-1 etc */
+};
+
+/* increase N_PORTS if you need more */
+#define PXA182X_N_PORTS		4
+static struct pxa182x_portmaster {
+	struct mutex	lock;			/* protect open/close */
+	struct pxa182x_gs_port	*port;
+} pxa182x_ports[PXA182X_N_PORTS];
+
+static unsigned n_modem_ports;
+static unsigned n_debug_ports;
+
+#define PXA182X_GS_CLOSE_TIMEOUT		2		/* seconds */
+
+
+#ifndef pr_vdebug
+#ifdef VERBOSE_DEBUG
+#define pr_vdebug(fmt, arg...) \
+	pr_debug(fmt, ##arg)
+#else
+#define pr_vdebug(fmt, arg...) \
+	({ if (0) pr_debug(fmt, ##arg); })
+#endif
+#endif
+
+/*-------------------------------------------------------------------------*/
+/* Sulog uses these function to output straight to USB (instead of through userspace) */
+
+static struct debug_serial_direct_callbacks debug_serial_cb_func;
+static bool debug_serial_ready;
+static bool register_debug_serial_available = true;
+struct usb_request *req_debug;
+
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_DWC3_HWSULOG)
+static int (*hwsulog_udc_func) (int ep_num, u32 flag);
+void register_hwsulog_udc_func(int (*func) (int ep_num, u32 flag))
+{
+	pr_info("register hwsulog callback\n");
+	hwsulog_udc_func = func;
+}
+void unregister_hwsulog_udc_func(void)
+{
+	pr_info("unregister hwsulog callback\n");
+	hwsulog_udc_func = NULL;
+}
+#endif
+
+int debug_data_tx_callback_tracer(const unsigned char *buf, int count);
+static void debug_read_complete_tracer(struct usb_ep *ep,
+				       struct usb_request *req);
+
+struct debug_serial_direct_callbacks *pxa_register_debug_serial_streaming(void)
+{
+	if (register_debug_serial_available) {
+		debug_serial_cb_func.send_buff_cb =
+		    debug_data_tx_callback_tracer;
+		register_debug_serial_available = false;
+		return &debug_serial_cb_func;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL(pxa_register_debug_serial_streaming);
+
+void pxa_unregister_debug_serial_streaming(void)
+{
+	register_debug_serial_available = true;
+	debug_serial_cb_func.send_buff_cb = NULL;
+}
+EXPORT_SYMBOL(pxa_unregister_debug_serial_streaming);
+
+static void debug_read_complete_tracer(struct usb_ep *ep,
+				       struct usb_request *req)
+{
+	if (req->status) {
+		pr_err("free req %d %d %d", req->actual, req->length,
+		       req->status);
+	}
+	debug_serial_ready = true;
+}
+
+int debug_data_tx_callback_tracer(const unsigned char *buf, int count)
+{
+	int status = 0;
+	int port_num = 1;
+	struct pxa182x_gs_port *port;
+	struct usb_ep *in;
+	unsigned long flags;
+
+	port = pxa182x_ports[port_num].port;
+	if (!port || !port->port_usb) {
+		pr_err_ratelimited("%s line %d: error! port usb uninitialized\n",
+							__func__, __LINE__);
+		return -1;
+	}
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	in = port->port_usb->in;
+
+	if (!in) {
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return 0;
+	}
+
+	if (!debug_serial_ready) {
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return -EBUSY;
+	}
+
+	debug_serial_ready = false;
+	spin_unlock_irqrestore(&port->port_lock, flags);
+	if (!req_debug)
+		req_debug = usb_ep_alloc_request(in, GFP_ATOMIC);
+
+	if (!req_debug) {
+		pr_err("goto fail\n");
+		goto fail;
+	}
+
+	req_debug->length = count;
+
+	req_debug->buf = (void *)buf;
+	req_debug->zero = 0;
+	req_debug->complete = &debug_read_complete_tracer;
+
+	req_debug->status = 0;
+	status = usb_ep_queue(in, req_debug, GFP_ATOMIC);
+	if (status) {
+fail:
+		pr_err("%s:%d: error to send status=%d\n",
+		__func__, __LINE__, status);
+		debug_serial_ready = true;
+		return -1;
+	}
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Circular Buffer */
+
+/*
+ * gs_buf_alloc
+ *
+ * Allocate a circular buffer and all associated memory.
+ */
+static int pxa182x_gs_buf_alloc(struct pxa182x_gs_buf *gb, unsigned size)
+{
+	gb->buf_buf = kmalloc(size, GFP_KERNEL);
+	if (gb->buf_buf == NULL)
+		return -ENOMEM;
+
+	gb->buf_size = size;
+	gb->buf_put = gb->buf_buf;
+	gb->buf_get = gb->buf_buf;
+
+	return 0;
+}
+
+/*
+ * gs_buf_free
+ *
+ * Free the buffer and all associated memory.
+ */
+static void pxa182x_gs_buf_free(struct pxa182x_gs_buf *gb)
+{
+	kfree(gb->buf_buf);
+	gb->buf_buf = NULL;
+}
+
+/*
+ * gs_buf_clear
+ *
+ * Clear out all data in the circular buffer.
+ */
+static void pxa182x_gs_buf_clear(struct pxa182x_gs_buf *gb)
+{
+	gb->buf_get = gb->buf_put;
+	/* equivalent to a get of all data available */
+}
+
+/*
+ * gs_buf_data_avail
+ *
+ * Return the number of bytes of data written into the circular
+ * buffer.
+ */
+static unsigned pxa182x_gs_buf_data_avail(struct pxa182x_gs_buf *gb)
+{
+	return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size;
+}
+
+/*
+ * gs_buf_space_avail
+ *
+ * Return the number of bytes of space available in the circular
+ * buffer.
+ */
+static unsigned pxa182x_gs_buf_space_avail(struct pxa182x_gs_buf *gb)
+{
+	return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size;
+}
+
+/*
+ * gs_buf_put
+ *
+ * Copy data data from a user buffer and put it into the circular buffer.
+ * Restrict to the amount of space available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+pxa182x_gs_buf_put(struct pxa182x_gs_buf *gb, const char *buf, unsigned count)
+{
+	unsigned len;
+
+	len  = pxa182x_gs_buf_space_avail(gb);
+	if (count > len)
+		count = len;
+
+	if (count == 0)
+		return 0;
+
+	len = gb->buf_buf + gb->buf_size - gb->buf_put;
+	if (count > len) {
+		memcpy(gb->buf_put, buf, len);
+		memcpy(gb->buf_buf, buf+len, count - len);
+		gb->buf_put = gb->buf_buf + count - len;
+	} else {
+		memcpy(gb->buf_put, buf, count);
+		if (count < len)
+			gb->buf_put += count;
+		else /* count == len */
+			gb->buf_put = gb->buf_buf;
+	}
+
+	return count;
+}
+
+/*
+ * gs_buf_get
+ *
+ * Get data from the circular buffer and copy to the given buffer.
+ * Restrict to the amount of data available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+pxa182x_gs_buf_get(struct pxa182x_gs_buf *gb, char *buf, unsigned count)
+{
+	unsigned len;
+
+	len = pxa182x_gs_buf_data_avail(gb);
+	if (count > len)
+		count = len;
+
+	if (count == 0)
+		return 0;
+
+	len = gb->buf_buf + gb->buf_size - gb->buf_get;
+	if (count > len) {
+		memcpy(buf, gb->buf_get, len);
+		memcpy(buf+len, gb->buf_buf, count - len);
+		gb->buf_get = gb->buf_buf + count - len;
+	} else {
+		memcpy(buf, gb->buf_get, count);
+		if (count < len)
+			gb->buf_get += count;
+		else /* count == len */
+			gb->buf_get = gb->buf_buf;
+	}
+
+	return count;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* I/O glue between TTY (upper) and USB function (lower) driver layers */
+
+/*
+ * gs_alloc_req
+ *
+ * Allocate a usb_request and its buffer.  Returns a pointer to the
+ * usb_request or NULL if there is an error.
+ */
+struct usb_request *
+pxa182x_gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+	if (req != NULL) {
+		req->length = len;
+		req->buf = kmalloc(len, kmalloc_flags);
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			return NULL;
+		}
+	}
+
+	return req;
+}
+
+/*
+ * gs_free_req
+ *
+ * Free a usb_request and its buffer.
+ */
+void pxa182x_gs_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	kfree(req->buf);
+	usb_ep_free_request(ep, req);
+}
+
+/*
+ * gs_send_packet
+ *
+ * If there is data to send, a packet is built in the given
+ * buffer and the size is returned.  If there is no data to
+ * send, 0 is returned.
+ *
+ * Called with port_lock held.
+ */
+static unsigned
+pxa182x_gs_send_packet(struct pxa182x_gs_port *port, char *packet, unsigned size)
+{
+	unsigned len;
+
+	len = pxa182x_gs_buf_data_avail(&port->port_write_buf);
+	if (len < size)
+		size = len;
+	if (size != 0)
+		size = pxa182x_gs_buf_get(&port->port_write_buf, packet, size);
+	return size;
+}
+
+static int pxa182x_gs_start_tx(struct pxa182x_gs_port *port)
+{
+	return queue_work(port->tx_wq, &port->tx_work);
+}
+
+/*
+ * pxa182x_gs_tx_worker
+ *
+ * This function finds available write requests, calls
+ * gs_send_packet to fill these packets with data, and
+ * continues until either there are no more write requests
+ * available or no more data to send.  This function is
+ * run whenever data arrives or write requests are available.
+ *
+ * Context: port_usb is non-null.
+ */
+void pxa182x_gs_tx_worker(struct work_struct *work)
+{
+	struct pxa182x_gs_port *port = (struct pxa182x_gs_port *)
+		container_of(work, struct pxa182x_gs_port, tx_work);
+	struct list_head	*pool;
+	struct usb_ep		*in = NULL;
+	int			status = 0;
+	bool			do_tty_wake = false;
+	unsigned long		flags;
+
+	if (NULL == port)
+		return;
+
+	pool = &port->write_pool;
+	if (port->port_usb)
+		in = port->port_usb->in;
+
+	if (!in)
+		return;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	while (!list_empty(pool)) {
+		struct usb_request	*req;
+		int			len;
+
+		if (port->write_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		len = pxa182x_gs_send_packet(port, req->buf, in->maxpacket);
+		if (len == 0) {
+			wake_up_interruptible(&port->drain_wait);
+			break;
+		}
+		do_tty_wake = true;
+
+		req->length = len;
+		list_del(&req->list);
+		req->zero =
+			(pxa182x_gs_buf_data_avail(&port->port_write_buf) == 0);
+
+		pr_vdebug(PREFIX_TTY "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
+				port->port_num, len, *((u8 *)req->buf),
+				*((u8 *)req->buf+1), *((u8 *)req->buf+2));
+
+		/* Drop lock while we call out of driver; completions
+		 * could be issued while we do so.  Disconnection may
+		 * happen too; maybe immediately before we queue this!
+		 *
+		 * NOTE that we may keep sending data for a while after
+		 * the TTY closed (dev->ioport->port.tty is NULL).
+		 */
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		status = usb_ep_queue(in, req, GFP_ATOMIC);
+		spin_lock_irqsave(&port->port_lock, flags);
+
+		if (status) {
+			pr_debug("%s: %s %s err %d\n",
+					__func__, "queue", in->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+
+		port->write_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+
+	if (do_tty_wake && port->port_usb)
+		wake_up_interruptible(&port->port_usb->port_send);
+
+	if (do_tty_wake && port->port.tty)
+		tty_wakeup(port->port.tty);
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return;
+}
+
+/*
+ * Context: caller owns port_lock, and port_usb is set
+ */
+static unsigned px182x_gs_start_rx(struct pxa182x_gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+	struct list_head	*pool;
+	struct usb_ep		*out = NULL;
+
+	if (NULL == port)
+		return 0;
+
+	pool = &port->read_pool;
+	if (port->port_usb)
+		out = port->port_usb->out;
+
+	if (!out)
+		return 0;
+
+	while (!list_empty(pool)) {
+		struct usb_request	*req;
+		int			status;
+		struct tty_struct	*tty;
+
+		/* no more rx if closed */
+		tty = port->port.tty;
+		if (!tty)
+			break;
+
+		if (port->read_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		list_del(&req->list);
+		req->length = out->maxpacket;
+
+		/* drop lock while we call out; the controller driver
+		 * may need to call us back (e.g. for disconnect)
+		 */
+		spin_unlock(&port->port_lock);
+		status = usb_ep_queue(out, req, GFP_ATOMIC);
+		spin_lock(&port->port_lock);
+
+		if (status) {
+			pr_debug("%s: %s %s err %d\n",
+					__func__, "queue", out->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+		port->read_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+	return port->read_started;
+}
+
+static void pxa182x_gs_read_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct pxa182x_gs_port	*port = ep->driver_data;
+
+	/* Queue all received data until the tty layer is ready for it. */
+	spin_lock(&port->port_lock);
+	list_add_tail(&req->list, &port->read_queue);
+	tasklet_schedule(&port->push);
+	spin_unlock(&port->port_lock);
+}
+
+static void
+pxa182x_gs_write_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct pxa182x_gs_port	*port = ep->driver_data;
+
+	spin_lock(&port->port_lock);
+	list_add(&req->list, &port->write_pool);
+	port->write_started--;
+
+	switch (req->status) {
+	default:
+		/* presumably a transient fault */
+		pr_warning("%s: unexpected %s status %d\n",
+				__func__, ep->name, req->status);
+		/* FALL THROUGH */
+	case 0:
+		/* normal completion */
+		pxa182x_gs_start_tx(port);
+		break;
+
+	case -ESHUTDOWN:
+		/* disconnect */
+		pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
+		break;
+	}
+
+	spin_unlock(&port->port_lock);
+}
+
+static void pxa182x_gs_free_requests(struct usb_ep *ep, struct list_head *head,
+							 int *allocated)
+{
+	struct usb_request	*req;
+
+	while (!list_empty(head)) {
+		req = list_entry(head->next, struct usb_request, list);
+		list_del(&req->list);
+		pxa182x_gs_free_req(ep, req);
+		if (allocated)
+			(*allocated)--;
+	}
+}
+
+static int pxa182x_gs_alloc_requests(struct usb_ep *ep, struct list_head *head,
+		void (*fn)(struct usb_ep *, struct usb_request *),
+		int *allocated)
+{
+	int			i;
+	struct usb_request	*req;
+	int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE;
+
+	/* Pre-allocate up to QUEUE_SIZE transfers, but if we can't
+	 * do quite that many this time, don't fail ... we just won't
+	 * be as speedy as we might otherwise be.
+	 */
+	for (i = 0; i < n; i++) {
+		req = pxa182x_gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
+		if (!req)
+			return list_empty(head) ? -ENOMEM : 0;
+		req->complete = fn;
+		list_add_tail(&req->list, head);
+		if (allocated)
+			(*allocated)++;
+	}
+	return 0;
+}
+
+/**
+ * gs_start_io - start USB I/O streams
+ * @dev: encapsulates endpoints to use
+ * Context: holding port_lock; port.tty and port_usb are non-null
+ *
+ * We only start I/O when something is connected to both sides of
+ * this port.  If nothing is listening on the host side, we may
+ * be pointlessly filling up our TX buffers and FIFO.
+ */
+static int pxa182x_gs_start_io(struct pxa182x_gs_port *port)
+{
+	struct list_head	*head = &port->read_pool;
+	struct usb_ep		*ep = port->port_usb->out;
+	int			status;
+	unsigned		started;
+
+	/* Allocate RX and TX I/O buffers.  We can't easily do this much
+	 * earlier (with GFP_KERNEL) because the requests are coupled to
+	 * endpoints, as are the packet sizes we'll be using.  Different
+	 * configurations may use different endpoints with a given port;
+	 * and high speed vs full speed changes packet sizes too.
+	 */
+	status = pxa182x_gs_alloc_requests(ep, head, pxa182x_gs_read_complete,
+		&port->read_allocated);
+	if (status)
+		return status;
+
+	status = pxa182x_gs_alloc_requests(port->port_usb->in, &port->write_pool,
+			pxa182x_gs_write_complete, &port->write_allocated);
+	if (status) {
+		pxa182x_gs_free_requests(ep, head, &port->read_allocated);
+		return status;
+	}
+
+	/* queue read requests */
+	port->n_read = 0;
+	started = px182x_gs_start_rx(port);
+
+	/* unblock any pending writes into our circular buffer */
+	if (started) {
+		tty_wakeup(port->port.tty);
+	} else {
+		pxa182x_gs_free_requests(ep, head, &port->read_allocated);
+		pxa182x_gs_free_requests(port->port_usb->in, &port->write_pool,
+			&port->write_allocated);
+		status = -EIO;
+	}
+
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* TTY Driver */
+
+/*
+ * gs_open sets up the link between a gs_port and its associated TTY.
+ * That link is broken *only* by TTY close(), and all driver methods
+ * know that.
+ */
+static int pxa182x_gs_open(struct tty_struct *tty, struct file *file)
+{
+	int port_num;
+	struct pxa182x_gs_port *port;
+	int status;
+	char *ttydebug = "tty_debug";
+
+	if (!strcmp(ttydebug, tty->driver->name))
+		port_num = 1;
+
+	if ((port_num < 0) || (port_num >= ((int)n_modem_ports + (int)n_modem_ports)))
+		return -ENXIO;
+
+	do {
+		mutex_lock(&pxa182x_ports[port_num].lock);
+		port = pxa182x_ports[port_num].port;
+		if (!port)
+			status = -ENODEV;
+		else {
+			spin_lock_irq(&port->port_lock);
+
+			/* already open?  Great. */
+			if (port->port.count) {
+				status = 0;
+				port->port.count++;
+
+			/* currently opening/closing? wait ... */
+			} else if (port->openclose) {
+				status = -EBUSY;
+
+			/* ... else we do the work */
+			} else {
+				status = -EAGAIN;
+				port->openclose = true;
+			}
+			spin_unlock_irq(&port->port_lock);
+		}
+		mutex_unlock(&pxa182x_ports[port_num].lock);
+
+		switch (status) {
+		default:
+			/* fully handled */
+			return status;
+		case -EAGAIN:
+			/* must do the work */
+			break;
+		case -EBUSY:
+			/* wait for EAGAIN task to finish */
+			usleep_range(1000, 1500);
+			/* REVISIT could have a waitchannel here, if
+			 * concurrent open performance is important
+			 */
+			break;
+		}
+	} while (status != -EAGAIN);
+
+	/* Do the "real open" */
+	spin_lock_irq(&port->port_lock);
+
+	/* allocate circular buffer on first open */
+	if (port->port_write_buf.buf_buf == NULL) {
+
+		spin_unlock_irq(&port->port_lock);
+		status = pxa182x_gs_buf_alloc(&port->port_write_buf,
+						PXA182X_WRITE_BUF_SIZE);
+		spin_lock_irq(&port->port_lock);
+
+		if (status) {
+			pr_debug("gs_open: tty_debug%d (%p,%p) no buffer\n",
+				port->port_num, tty, file);
+			port->openclose = false;
+			goto exit_unlock_port;
+		}
+	}
+
+	/* REVISIT if REMOVED (ports[].port NULL), abort the open
+	 * to let rmmod work faster (but this way isn't wrong).
+	 */
+
+	/* REVISIT maybe wait for "carrier detect" */
+
+	tty->driver_data = port;
+	port->port.tty = tty;
+
+	port->port.count = 1;
+	port->openclose = false;
+
+	/* if connected, start the I/O stream */
+	if (port->port_usb) {
+		struct pxa182x_gserial	*gser = port->port_usb;
+
+		pr_debug("gs_open: start tty_debug%d\n", port->port_num);
+		pxa182x_gs_start_io(port);
+
+		if (gser->connect)
+			gser->connect(gser);
+	}
+
+	pr_debug("gs_open: tty_debug%d (%p,%p)\n", port->port_num, tty, file);
+
+	status = 0;
+
+exit_unlock_port:
+	spin_unlock_irq(&port->port_lock);
+	return status;
+}
+
+static int pxa182x_gs_writes_finished(struct pxa182x_gs_port *p)
+{
+	int cond;
+
+	/* return true on disconnect or empty buffer */
+	spin_lock_irq(&p->port_lock);
+	cond = (p->port_usb == NULL) ||
+		!pxa182x_gs_buf_data_avail(&p->port_write_buf);
+	spin_unlock_irq(&p->port_lock);
+
+	return cond;
+}
+
+static void pxa182x_gs_close(struct tty_struct *tty, struct file *file)
+{
+	struct pxa182x_gs_port *port = tty->driver_data;
+	struct pxa182x_gserial	*gser;
+
+	spin_lock_irq(&port->port_lock);
+
+	if (port->port.count != 1) {
+		if (port->port.count == 0)
+			WARN_ON(1);
+		else
+			--port->port.count;
+		goto exit;
+	}
+
+	pr_debug("gs_close: tty_debug%d (%p,%p) ...\n", port->port_num, tty, file);
+
+	/* mark port as closing but in use; we can drop port lock
+	 * and sleep if necessary
+	 */
+	port->openclose = true;
+	port->port.count = 0;
+
+	gser = port->port_usb;
+	if (gser && gser->disconnect)
+		gser->disconnect(gser);
+
+	/* wait for circular write buffer to drain, disconnect, or at
+	 * most PXA182X_GS_CLOSE_TIMEOUT seconds; then discard the rest
+	 */
+	if (pxa182x_gs_buf_data_avail(&port->port_write_buf) > 0 && gser) {
+		spin_unlock_irq(&port->port_lock);
+		wait_event_interruptible_timeout(port->drain_wait,
+					pxa182x_gs_writes_finished(port),
+					PXA182X_GS_CLOSE_TIMEOUT * HZ);
+		spin_lock_irq(&port->port_lock);
+		gser = port->port_usb;
+	}
+
+	/* Iff we're disconnected, there can be no I/O in flight so it's
+	 * ok to free the circular buffer; else just scrub it.  And don't
+	 * let the push tasklet fire again until we're re-opened.
+	 */
+	if (gser == NULL)
+		pxa182x_gs_buf_free(&port->port_write_buf);
+	else
+		pxa182x_gs_buf_clear(&port->port_write_buf);
+
+	tty->driver_data = NULL;
+	port->port.tty = NULL;
+
+	port->openclose = false;
+
+	pr_debug("gs_close: tty_debug%d (%p,%p) done!\n",
+			port->port_num, tty, file);
+
+	wake_up_interruptible(&port->close_wait);
+exit:
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int
+pxa182x_gs_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		status;
+
+	pr_vdebug("gs_write: tty_debug%d (%p) writing %d bytes\n",
+			port->port_num, tty, count);
+
+	if (port == NULL) {
+		pr_err("pxa182x_gs_write: port is NULL!\n");
+		return -EPERM;
+	}
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (count)
+		count = pxa182x_gs_buf_put(&port->port_write_buf, buf, count);
+	/* treat count == 0 as flush_chars() */
+	if (port->port_usb)
+		status = pxa182x_gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	if (!count) {
+		pr_vdebug("%s: buffer full!\n", __func__);
+		return -EAGAIN;
+	}
+	return count;
+}
+
+static int pxa182x_gs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		status;
+
+	pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %p\n",
+		port->port_num, tty, ch, __builtin_return_address(0));
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	status = pxa182x_gs_buf_put(&port->port_write_buf, &ch, 1);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return status;
+}
+
+static void pxa182x_gs_flush_chars(struct tty_struct *tty)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+
+	if (unlikely(!port)) {
+		pr_err("%s: port closed\n", __func__);
+		return;
+	}
+	pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		pxa182x_gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int pxa182x_gs_write_room(struct tty_struct *tty)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		room = 0;
+
+	if (unlikely(port == NULL))
+		return 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		room = pxa182x_gs_buf_space_avail(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_write_room: (%d,%p) room=%d\n",
+		port->port_num, tty, room);
+
+	return room;
+}
+
+static int pxa182x_gs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		chars = 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	chars = pxa182x_gs_buf_data_avail(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n",
+		port->port_num, tty, chars);
+
+	return chars;
+}
+
+/* undo side effects of setting TTY_THROTTLED */
+static void pxa182x_gs_unthrottle(struct tty_struct *tty)
+{
+	struct pxa182x_gs_port		*port = tty->driver_data;
+	unsigned long		flags;
+	if (!port)
+		return;
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb) {
+		/* Kickstart read queue processing.  We don't do xon/xoff,
+		 * rts/cts, or other handshaking with the host, but if the
+		 * read queue backs up enough we'll be NAKing OUT packets.
+		 */
+		tasklet_schedule(&port->push);
+		pr_vdebug(PREFIX_TTY "%d: unthrottle\n", port->port_num);
+	}
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int pxa182x_gs_break_ctl(struct tty_struct *tty, int duration)
+{
+	struct pxa182x_gs_port	*port = tty->driver_data;
+	int		status = 0;
+	struct pxa182x_gserial	*gser;
+
+	pr_vdebug("gs_break_ctl: tty_debug%d, send break (%d)\n",
+			port->port_num, duration);
+
+	spin_lock_irq(&port->port_lock);
+	gser = port->port_usb;
+	if (gser && gser->send_break)
+		status = gser->send_break(gser, duration);
+	spin_unlock_irq(&port->port_lock);
+
+	return status;
+}
+
+static const struct tty_operations pxa182x_gs_tty_ops = {
+	.open =			pxa182x_gs_open,
+	.close =		pxa182x_gs_close,
+	.write =		pxa182x_gs_write,
+	.put_char =		pxa182x_gs_put_char,
+	.flush_chars =		pxa182x_gs_flush_chars,
+	.write_room =		pxa182x_gs_write_room,
+	.chars_in_buffer =	pxa182x_gs_chars_in_buffer,
+	.unthrottle =		pxa182x_gs_unthrottle,
+	.break_ctl =		pxa182x_gs_break_ctl,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct tty_driver *gs_debug_tty_driver;
+
+static int pxa182x_gs_closed(struct pxa182x_gs_port *port)
+{
+	int cond;
+
+	spin_lock_irq(&port->port_lock);
+	cond = (port->port.count == 0) && !port->openclose;
+	spin_unlock_irq(&port->port_lock);
+	return cond;
+}
+
+/**
+ * gserial_connect - notify TTY I/O glue that USB link is active
+ * @gser: the function, set up with endpoints and descriptors
+ * @port_num: which port is active
+ * Context: any (usually from irq)
+ *
+ * This is called activate endpoints and let the TTY layer know that
+ * the connection is active ... not unlike "carrier detect".  It won't
+ * necessarily start I/O queues; unless the TTY is held open by any
+ * task, there would be no point.  However, the endpoints will be
+ * activated so the USB host can perform I/O, subject to basic USB
+ * hardware flow control.
+ *
+ * Caller needs to have set up the endpoints and USB function in @dev
+ * before calling this, as well as the appropriate (speed-specific)
+ * endpoint descriptors, and also have set up the TTY driver by calling
+ * @gserial_setup().
+ *
+ * Returns negative errno or zero.
+ * On success, ep->driver_data will be overwritten.
+ */
+#ifdef CONFIG_DWC3_HWSULOG
+u32 sulog_ep_num;
+#endif
+int pxa182x_gserial_connect(struct pxa182x_gserial *gser, u8 port_num)
+{
+	struct pxa182x_gs_port	*port;
+	unsigned long	flags;
+	int		status;
+
+	if (port_num >= (n_modem_ports + n_debug_ports))
+		return -ENXIO;
+
+	if (!gs_debug_tty_driver)
+		return -ENXIO;
+
+	/* we "know" gserial_cleanup() hasn't been called */
+	port = pxa182x_ports[port_num].port;
+
+#ifdef CONFIG_DWC3_HWSULOG
+	sulog_ep_num = gser->in->desc->bEndpointAddress & 0xf;
+#endif
+	/* activate the endpoints */
+	status = usb_ep_enable(gser->in);
+	if (status < 0)
+		return status;
+	gser->in->driver_data = port;
+
+	status = usb_ep_enable(gser->out);
+	if (status < 0)
+		goto fail_out;
+	gser->out->driver_data = port;
+
+	/* then tell the tty glue that I/O can work */
+	spin_lock_irqsave(&port->port_lock, flags);
+	gser->ioport = port;
+	port->port_usb = gser;
+
+	/* REVISIT unclear how best to handle this state...
+	 * we don't really couple it with the Linux TTY.
+	 */
+	gser->port_line_coding = port->port_line_coding;
+
+	/* REVISIT if waiting on "carrier detect", signal. */
+
+	/* if it's already open, start I/O ... and notify the serial
+	 * protocol about open/close status (connect/disconnect).
+	 */
+	if (port->port.count) {
+		pr_debug("gserial_connect: start tty_debug%d\n", port->port_num);
+		pxa182x_gs_start_io(port);
+		if (gser->connect)
+			gser->connect(gser);
+	} else {
+		if (gser->disconnect)
+			gser->disconnect(gser);
+	}
+
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_DWC3_HWSULOG)
+	/* enable hw sulog */
+	if (hwsulog_udc_func)
+		hwsulog_udc_func(gser->in->desc->bEndpointAddress & 0xf, 1);
+#endif
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return status;
+
+fail_out:
+	usb_ep_disable(gser->in);
+	gser->in->driver_data = NULL;
+	return status;
+}
+
+/**
+ * gserial_disconnect - notify TTY I/O glue that USB link is inactive
+ * @gser: the function, on which gserial_connect() was called
+ * Context: any (usually from irq)
+ *
+ * This is called to deactivate endpoints and let the TTY layer know
+ * that the connection went inactive ... not unlike "hangup".
+ *
+ * On return, the state is as if gserial_connect() had never been called;
+ * there is no active USB I/O on these endpoints.
+ */
+void pxa182x_gserial_disconnect(struct pxa182x_gserial *gser)
+{
+	struct pxa182x_gs_port	*port = gser->ioport;
+	unsigned long	flags;
+
+	if (!port)
+		return;
+
+	/* tell the TTY glue not to do I/O here any more */
+	spin_lock_irqsave(&port->port_lock, flags);
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_DWC3_HWSULOG)
+	/* disable hw sulog */
+	if (hwsulog_udc_func)
+		hwsulog_udc_func(0, 0);
+#endif
+
+	/* REVISIT as above: how best to track this? */
+	port->port_line_coding = gser->port_line_coding;
+
+	port->port_usb = NULL;
+	gser->ioport = NULL;
+	if (port->port.count > 0 || port->openclose) {
+		wake_up_interruptible(&port->drain_wait);
+		if (port->port.tty)
+			tty_hangup(port->port.tty);
+	}
+	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;
+
+	/* finally, free any unused/unusable I/O buffers */
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port.count == 0 && !port->openclose)
+		pxa182x_gs_buf_free(&port->port_write_buf);
+	pxa182x_gs_free_requests(gser->out, &port->read_pool, NULL);
+	pxa182x_gs_free_requests(gser->out, &port->read_queue, NULL);
+	pxa182x_gs_free_requests(gser->in, &port->write_pool, NULL);
+
+	port->read_allocated = port->read_started =
+		port->write_allocated = port->write_started = 0;
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.h b/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.h
new file mode 100644
index 0000000..cdee389
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa182x_serial_debug.h
@@ -0,0 +1,71 @@
+/*
+ * pxa182x_serial_debug.h - interface to USB gadget "serial port"/TTY utilities
+ * Based on u_serial.h
+ *
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifndef __PXA182X_SERIAL_DEBUG_H
+#define __PXA182X_SERIAL_DEBUG_H
+
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+/*
+ * One non-multiplexed "serial" I/O port ... there can be several of these
+ * on any given USB peripheral device, if it provides enough endpoints.
+ *
+ * The "u_serial" utility component exists to do one thing:  manage TTY
+ * style I/O using the USB peripheral endpoints listed here, including
+ * hookups to sysfs and /dev for each logical "tty" device.
+ *
+ * REVISIT at least ACM could support tiocmget() if needed.
+ *
+ * REVISIT someday, allow multiplexing several TTYs over these endpoints.
+ */
+struct pxa182x_gserial {
+	struct usb_function		func;
+
+	/* port is managed by gserial_{connect,disconnect} */
+	struct pxa182x_gs_port			*ioport;
+
+	struct usb_ep			*in;
+	struct usb_ep			*out;
+
+	/* REVISIT avoid this CDC-ACM support harder ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 9600-8-N-1 etc */
+	wait_queue_head_t port_send;
+
+	/* notification callbacks */
+	void (*connect)(struct pxa182x_gserial *p);
+	void (*disconnect)(struct pxa182x_gserial *p);
+	int (*send_break)(struct pxa182x_gserial *p, int duration);
+};
+
+/* utilities to allocate/free request and buffer */
+struct usb_request *
+pxa182x_gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
+void pxa182x_gs_free_req(struct usb_ep *, struct usb_request *req);
+void pxa182x_gs_tx_worker(struct work_struct *work);
+/* port setup/teardown is handled by gadget driver */
+int pxa182x_gserial_setup(struct usb_gadget *g, unsigned n_ports);
+void pxa182x_gserial_cleanup(void);
+
+/* connect/disconnect is handled by individual functions */
+int pxa182x_gserial_connect(struct pxa182x_gserial *, u8 port_num);
+void pxa182x_gserial_disconnect(struct pxa182x_gserial *);
+
+/* functions are bound to configurations by a config or gadget driver */
+int pxa182x_acm_bind_config(struct usb_configuration *c, u8 port_num);
+int pxa182x_diag_bind_config(struct usb_configuration *c, u8 port_num);
+
+/* register/unregister functions */
+struct debug_serial_direct_callbacks *pxa_register_debug_serial_streaming(void);
+void pxa_unregister_debug_serial_streaming(void);
+
+#endif /* __PXA182X_SERIAL_DEBUG_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa910_f_diag.c b/marvell/linux/drivers/usb/gadget/function/pxa910_f_diag.c
new file mode 100644
index 0000000..74d9baa
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa910_f_diag.c
@@ -0,0 +1,1207 @@
+/*
+ * f_acm.c -- USB CDC serial (ACM) function driver
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 2009 by Samsung Electronics
+ * Author: Michal Nazarewicz (mina86@mina86.com)
+ *
+ * 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.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/usb/composite.h>
+#include <linux/workqueue.h>
+
+#include "pxa910_u_serial.h"
+#include "gadget_chips.h"
+
+/*
+ * This CDC ACM function support just wraps control functions and
+ * notifications around the generic serial-over-usb code.
+ *
+ * Because CDC ACM is standardized by the USB-IF, many host operating
+ * systems have drivers for it.  Accordingly, ACM is the preferred
+ * interop solution for serial-port type connections.  The control
+ * models are often not necessary, and in any case don't do much in
+ * this bare-bones implementation.
+ *
+ * Note that even MS-Windows has some support for ACM.  However, that
+ * support is somewhat broken because when you use ACM in a composite
+ * device, having multiple interfaces confuses the poor OS.  It doesn't
+ * seem to understand CDC Union descriptors.  The new "association"
+ * descriptors (roughly equivalent to CDC Unions) may sometimes help.
+ */
+#define GS_DIAG_PORT_BASE 1
+#define DIAG_INTERFACE_NUMBER	(4)
+
+struct pxa910_f_diag {
+	struct pxa910_gserial		port;
+	u8				data_id;
+	u8				port_num;
+};
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+int nr_diag_dummy_interface = 0;
+#endif
+
+static inline struct pxa910_f_diag *pxa910_func_to_diag(struct usb_function *f)
+{
+	return container_of(f, struct pxa910_f_diag, port.func);
+}
+
+static inline struct
+pxa910_f_diag *pxa910_port_to_diag(struct pxa910_gserial *p)
+{
+	return container_of(p, struct pxa910_f_diag, port);
+}
+
+#ifdef CONFIG_USB_DIAG_STATS
+#define csprintf(buf, len, ...) \
+	(buf ? sprintf(buf + len, __VA_ARGS__) : pr_err(__VA_ARGS__))
+
+static const char TRext[] = "\0\0\0Ki\0Mi\0Gi\0Ti";
+static int sprintf_bytes_scaled(unsigned size, char *buf, int len)
+{
+	unsigned int_part;
+	const char *ext;
+	unsigned frac_part;
+	int i;
+
+	frac_part = 0;
+	ext = TRext;
+	int_part = size;
+	i = 4;
+	do {
+		if (int_part >= 1024) {
+			frac_part = ((((unsigned int) int_part) & (1024-1)) * 10) / 1024;
+			int_part /= 1024;
+			ext += 3;	/* KiB, MiB, GiB, TiB */
+		}
+		--i;
+	} while (i);
+
+	return csprintf(buf, len, "%u.%u %sB", int_part, frac_part, ext);
+}
+
+static int param_show_stats(char *buf, const struct kernel_param *kp)
+{
+	struct pxa910_gs_port *port = pxa910_ports[GS_DIAG_PORT_BASE].port;
+	unsigned long flags;
+	int len = 0;
+	if (!port || !port->port_usb || !port->port_write_buf.buf_buf)
+		return csprintf(buf, len, "device is unplugged\n");
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	len += csprintf(buf, len, "Diag usb port statistics:\n");
+	len += csprintf(buf, len, "RX bytes:\t%u\t(", port->stats.rx_bytes);
+	len += sprintf_bytes_scaled(port->stats.rx_bytes, buf, len);
+	len += csprintf(buf, len, ")\t");
+	len += csprintf(buf, len, "TX bytes:\t%u\t(", port->stats.tx_bytes);
+	len += sprintf_bytes_scaled(port->stats.tx_bytes, buf, len);
+	len += csprintf(buf, len, ")\n");
+
+	len += csprintf(buf, len, "RX reqs:\t%u\t\t\t", port->stats.rx_reqs);
+	len += csprintf(buf, len, "TX reqs:\t%u\n\n", port->stats.tx_reqs);
+
+	len += csprintf(buf, len, "catStudio is %s\n", port->stats.acat_connected ? "connected" : "disconnected");
+	len += csprintf(buf, len, "catStudio disconnected count %u\n", port->stats.acat_disconnected_cnt);
+	len += csprintf(buf, len, "stream lost sync count:%u\n", port->stats.out_of_sync_cnt);
+	len += csprintf(buf, len, "\n");
+
+	len += csprintf(buf, len, "%d/%d rx reqs are in use\n", port->read_started, port->read_allocated);
+	len += csprintf(buf, len, "%d/%d tx reqs are in use\n", port->write_started, port->write_allocated);
+	len += csprintf(buf, len, "\n");
+
+	len += csprintf(buf, len, "Intermediate cyclic buffer control:\n");
+	len += csprintf(buf, len, "Total allocated size:");
+	len += sprintf_bytes_scaled(port->port_write_buf.buf_size, buf, len);
+	len += csprintf(buf, len, "\nTotal free size:");
+	len += sprintf_bytes_scaled(pxa910_gs_buf_space_avail(&port->port_write_buf), buf, len);
+	len += csprintf(buf, len, "\nTotal used size:");
+	len += sprintf_bytes_scaled(pxa910_gs_buf_data_avail(&port->port_write_buf), buf, len);
+	len += csprintf(buf, len, "\nread:0x%p\t write:0x%p\n", port->port_write_buf.buf_get, port->port_write_buf.buf_put);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	if (port->stats.shmem_status_cb)
+		len += port->stats.shmem_status_cb(buf, len);
+
+	return len;
+}
+
+static int param_reset_stats(const char *val, const struct kernel_param *kp)
+{
+	struct pxa910_gs_port *port = pxa910_ports[GS_DIAG_PORT_BASE].port;
+	void *tmp;
+	int arg;
+
+	if (!port)
+		return 0;
+
+	if (kstrtoint(val, 10, &arg))
+		return 0;
+
+	if (arg == 0) {
+		param_show_stats(NULL, NULL);
+		return 0;
+	}
+
+	tmp = port->stats.shmem_status_cb;
+	memset(&port->stats, 0, sizeof(struct port_stats));
+	port->stats.shmem_status_cb = tmp;
+
+	return 0;
+}
+
+static const struct kernel_param_ops param_ops_stats = {
+	.set = param_reset_stats,
+	.get = param_show_stats,
+};
+module_param_cb(diag_port_stats, &param_ops_stats, param_show_stats, 0644);
+
+#endif
+
+void register_f_diag_stats_cb(int (*cb)(char *, int))
+{
+	if (!pxa910_ports[GS_DIAG_PORT_BASE].port)
+		return;
+
+	pxa910_ports[GS_DIAG_PORT_BASE].port->stats.shmem_status_cb = cb;
+}
+EXPORT_SYMBOL(register_f_diag_stats_cb);
+
+void unregister_f_diag_stats_cb(void)
+{
+	if (!pxa910_ports[GS_DIAG_PORT_BASE].port)
+		return;
+
+	pxa910_ports[GS_DIAG_PORT_BASE].port->stats.shmem_status_cb = NULL;
+}
+EXPORT_SYMBOL(unregister_f_diag_stats_cb);
+/*-------------------------------------------------------------------------*/
+
+/* interface descriptor: */
+static struct usb_interface_descriptor pxa910_diag_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0xff,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor pxa910_diag_fs_in_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor pxa910_diag_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *pxa910_diag_fs_function[] = {
+	(struct usb_descriptor_header *)&pxa910_diag_interface_desc,
+	(struct usb_descriptor_header *)&pxa910_diag_fs_in_desc,
+	(struct usb_descriptor_header *)&pxa910_diag_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor pxa910_diag_hs_in_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor pxa910_diag_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *pxa910_diag_hs_function[] = {
+	(struct usb_descriptor_header *)&pxa910_diag_interface_desc,
+	(struct usb_descriptor_header *)&pxa910_diag_hs_in_desc,
+	(struct usb_descriptor_header *)&pxa910_diag_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor pxa910_diag_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor pxa910_diag_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor pxa910_diag_ss_bulk_comp_desc = {
+	.bLength =              sizeof pxa910_diag_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *pxa910_diag_ss_function[] = {
+	(struct usb_descriptor_header *) &pxa910_diag_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_diag_ss_in_desc,
+	(struct usb_descriptor_header *) &pxa910_diag_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &pxa910_diag_ss_out_desc,
+	(struct usb_descriptor_header *) &pxa910_diag_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+/* static strings, in UTF-8 */
+static struct usb_string pxa910_diag_string_defs[] = {
+	[0].s = "Marvell DIAG",
+	{  /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings pxa910_diag_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		pxa910_diag_string_defs,
+};
+
+static struct usb_gadget_strings *pxa910_diag_strings[] = {
+	&pxa910_diag_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int
+pxa910_diag_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct pxa910_f_diag *diag = pxa910_func_to_diag(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (diag->port.in->driver_data) {
+		DBG(cdev, "reset diag ttyGS%d\n", diag->port_num);
+		pxa910_gserial_disconnect(&diag->port);
+	} else {
+		DBG(cdev, "activate generic ttyGS%d\n", diag->port_num);
+	}
+	if (!diag->port.in->desc || !diag->port.out->desc) {
+		DBG(cdev, "activate diag ttyGS%d\n", diag->port_num);
+		if (config_ep_by_speed(cdev->gadget, f, diag->port.in) ||
+			config_ep_by_speed(cdev->gadget, f, diag->port.out)) {
+			diag->port.in->desc = NULL;
+			diag->port.out->desc = NULL;
+			return -EINVAL;
+		}
+	}
+
+	pxa910_gserial_connect(&diag->port, diag->port_num);
+
+	return 0;
+}
+
+static void pxa910_diag_disable(struct usb_function *f)
+{
+	struct pxa910_f_diag	*diag = pxa910_func_to_diag(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "diag ttyGS%d deactivated\n", diag->port_num);
+
+	pxa910_gserial_disconnect(&diag->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* DIAG function driver setup/binding */
+static int
+pxa910_diag_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct pxa910_f_diag	*diag = pxa910_func_to_diag(f);
+	int			status;
+	struct usb_ep		*ep;
+#if defined(CONFIG_FIXED_USB_IFACE)
+	int i;
+#endif
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+	status = get_usb_interface_id(c);
+	if (status < 0 || status > DIAG_INTERFACE_NUMBER)
+		goto fail;
+	else
+		nr_diag_dummy_interface = DIAG_INTERFACE_NUMBER - status;
+
+	if (nr_diag_dummy_interface > 0) {
+		for(i = 0; i < nr_diag_dummy_interface; i++) {
+			status = usb_interface_id(c, NULL);
+			if (status < 0)
+				goto fail;
+			else
+				pr_info("%s: dummy interface: %d\n",
+					__func__, status);
+		}
+	}
+#endif
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	diag->data_id = status;
+
+	pxa910_diag_interface_desc.bInterfaceNumber = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa910_diag_fs_in_desc);
+	if (!ep)
+		goto fail;
+	diag->port.in = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa910_diag_fs_out_desc);
+	if (!ep)
+		goto fail;
+	diag->port.out = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	/* copy descriptors */
+	f->fs_descriptors = usb_copy_descriptors(pxa910_diag_fs_function);
+	if (!f->fs_descriptors)
+		goto fail;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		pxa910_diag_hs_in_desc.bEndpointAddress =
+				pxa910_diag_fs_in_desc.bEndpointAddress;
+		pxa910_diag_hs_out_desc.bEndpointAddress =
+				pxa910_diag_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors */
+		f->hs_descriptors =
+			usb_copy_descriptors(pxa910_diag_hs_function);
+	}
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		pxa910_diag_ss_in_desc.bEndpointAddress =
+			pxa910_diag_fs_in_desc.bEndpointAddress;
+		pxa910_diag_ss_out_desc.bEndpointAddress =
+			pxa910_diag_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ss_descriptors =
+			usb_copy_descriptors(pxa910_diag_ss_function);
+		if (!f->ss_descriptors)
+			goto fail;
+	}
+	if (gadget_is_superspeed_plus(c->cdev->gadget)) {
+		pxa910_diag_ss_in_desc.bEndpointAddress =
+			pxa910_diag_fs_in_desc.bEndpointAddress;
+		pxa910_diag_ss_out_desc.bEndpointAddress =
+			pxa910_diag_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ssp_descriptors =
+			usb_copy_descriptors(pxa910_diag_ss_function);
+		if (!f->ssp_descriptors)
+			goto fail;
+	}
+
+	DBG(cdev, "diag ttyGS%d: %s speed IN/%s OUT/%s\n",
+			diag->port_num,
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			diag->port.in->name, diag->port.out->name);
+	return 0;
+
+fail:
+	/* we might as well release our claims on endpoints */
+	if (diag->port.out)
+		diag->port.out->driver_data = NULL;
+	if (diag->port.in)
+		diag->port.in->driver_data = NULL;
+
+	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+	return status;
+}
+
+static void
+pxa910_diag_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct pxa910_f_diag	*diag = pxa910_func_to_diag(f);
+
+	if (gadget_is_dualspeed(c->cdev->gadget))
+		usb_free_descriptors(f->hs_descriptors);
+	if (gadget_is_superspeed(c->cdev->gadget))
+		usb_free_descriptors(f->ss_descriptors);
+	usb_free_descriptors(f->fs_descriptors);
+	kfree(diag);
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		pxa910_diag_string_defs[0].id = 0;
+#endif
+
+#if defined(CONFIG_FIXED_USB_IFACE)
+	nr_diag_dummy_interface = 0;
+#endif
+}
+
+/**
+ * acm_bind_config - add a CDC ACM function to a configuration
+ * @c: the configuration to support the CDC ACM instance
+ * @port_num: /dev/ttyGS* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds.  Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int pxa910_diag_bind_config(struct usb_configuration *c, u8 port_num)
+{
+	struct pxa910_f_diag	*diag;
+	int		status;
+
+	/* REVISIT might want instance-specific strings to help
+	 * distinguish instances ...
+	 */
+
+	/* maybe allocate device-global string IDs, and patch descriptors */
+	if (pxa910_diag_string_defs[0].id == 0) {
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+
+		pxa910_diag_string_defs[0].id = status;
+
+		pxa910_diag_interface_desc.iInterface = status;
+	}
+
+	/* allocate and initialize one new instance */
+	diag = kzalloc(sizeof *diag, GFP_KERNEL);
+	if (!diag)
+		return -ENOMEM;
+
+	diag->port_num = port_num;
+
+	diag->port.func.name = "diag";
+	diag->port.func.strings = pxa910_diag_strings;
+	/* descriptors are per-instance copies */
+	diag->port.func.bind = pxa910_diag_bind;
+	diag->port.func.unbind = pxa910_diag_unbind;
+	diag->port.func.set_alt = pxa910_diag_set_alt;
+	diag->port.func.disable = pxa910_diag_disable;
+
+	init_waitqueue_head(&diag->port.port_send);
+	status = usb_add_function(c, &diag->port.func);
+	if (status)
+		kfree(diag);
+	return status;
+}
+/*
+ * inspecting traces stream to catch it gets out of synchronization:
+ */
+#define MAX_TRACE_LEN (1 << 16) /* ~64KB */
+#define MIN_TRACE_LEN (16)
+static inline bool validate_trace_len(unsigned int len)
+{
+	if (len < MIN_TRACE_LEN || len > MAX_TRACE_LEN) {
+		pxa910_ports[GS_DIAG_PORT_BASE].port->stats.out_of_sync_cnt++;
+		pr_info("%s: %d: traces stream is out of sync!"
+				"(Invalidate trace len: %u)\n", __FILE__, __LINE__, len);
+		return false;
+	}
+	return true;
+}
+
+static inline __le32 get_len(const void *buf, unsigned int offset)
+{
+	return *((__le32 *)((char *)buf + offset));
+}
+/**
+ * Check whether the current data transfer ends in a middle of a trace,
+ * and calculate the expected len field offset for the next
+ * transfer.
+ *
+ * byte_offset & splitted_len are in use only when len field is
+ * splitted across 2 transfers.
+ *
+ * @param expected_offset
+ *  			 Offset of the first len field in the current
+ *  			 transfer.
+ * @param count  data transfer length
+ * @param buf
+ * @param byte_offset
+ *  			 byte_offset != 0 indicates last transfer ended
+ *  			 with splitted len scenario. It's value is the
+ *  			 offset of the first trace expected payload. It
+ *  			 couldn't be negative nor (>3)
+ *
+ * @param splitted_len
+ *  			 Least significant x bytes (x = 4 - byte_offset)
+ *  			 of the next trace len (extracted from last
+ *  			 transfer.
+ *
+ * @return >0: transfer ends in a middle of a trace.
+ *  			 0: transfer ends with an end of a trace.
+ */
+static inline int trap_splitted_trace(int *expected_offset, int count,
+									  const void *buf, int *byte_offset,
+									  char splitted_len[4])
+{
+	unsigned int offset = *expected_offset;
+	unsigned int len = 0;
+
+	/* last transfer ended with splitted len scenario: */
+	if (unlikely(*byte_offset)) {
+		BUG_ON(*byte_offset > 3);
+		/* extract len MSB */
+		memcpy(splitted_len + 4 - *byte_offset, buf, *byte_offset);
+		len = le32_to_cpu(get_len(splitted_len, 0));
+		if (!validate_trace_len(len)) {
+			*expected_offset = 0;
+			*byte_offset = 0;
+			return 0;
+		}
+		offset = *byte_offset - 4;
+		*byte_offset = 0;
+	}
+	while (likely(len + offset < count)) {
+		offset += len;
+		/* detect len field splitted across 2 slots */
+		if (unlikely(offset + 3 >= count)) {
+			pr_debug("%s: %d: Splitted len field detected\n",
+					__FILE__, __LINE__);
+			*byte_offset = offset + 4 - count;
+			memset(splitted_len, 0, 4);
+			memcpy(splitted_len, buf + offset, count - offset);
+			*expected_offset = 0;
+			BUG_ON(!*byte_offset);
+			return 1;
+		}
+
+		len = le32_to_cpu(get_len(buf, offset));
+		if (!validate_trace_len(len)) {
+			*expected_offset = 0;
+			*byte_offset = 0;
+			return 0;
+		}
+	}
+	/* calculate expected offset for next transfer */
+	*expected_offset = len + offset - count;
+	BUG_ON(*expected_offset < 0 || *expected_offset >= MAX_TRACE_LEN);
+	return *expected_offset;
+}
+
+static int gs_marvell_diag_write(struct tty_struct *tty,
+				 const unsigned char *buf, int count)
+{
+#ifdef USB_DIAG_SPLITTED_TRACE
+	static int expected_offset = 0;
+	static int byte_offset = 0;
+	static char splitted_len[4];
+#endif
+	struct pxa910_gs_port *port = tty->driver_data;
+	unsigned long flags;
+	int copied, total = 0, ret = 0;
+	wait_queue_head_t *p_port_send;
+
+	pr_vdebug("gs_marvell_diag_write: ttyGS%d (%p) writing %d bytes\n",
+		  port->port_num, tty, count);
+
+	if (unlikely(!port || !port->port_usb)) {
+		pr_err_ratelimited("diag_write: diag port NULL\n");
+		return -ENODEV;
+	}
+
+	while (count > 0) {
+		spin_lock_irqsave(&port->port_lock, flags);
+		copied = pxa910_gs_buf_put(&port->port_write_buf, buf + total, count);
+
+#ifdef USB_DIAG_SPLITTED_TRACE
+		if (copied > 0)
+			trap_splitted_trace(&expected_offset, copied, buf + total,
+								&byte_offset, splitted_len);
+#endif
+		if (likely(port->port_usb && copied))
+			pxa910_gs_start_tx(port);
+		spin_unlock_irqrestore(&port->port_lock, flags);
+
+		if (likely(copied == count)) {
+			total += copied;
+			break;	/* send all bytes successfully */
+		} else if (copied > 0) {
+			total += copied;
+			count -= copied;
+			continue;
+		}
+		BUG_ON(copied < 0);
+
+		spin_lock_irqsave(&port->port_lock, flags);
+		if (likely(port->port_usb))
+			p_port_send = &port->port_usb->port_send;
+		else {
+			spin_unlock_irqrestore(&port->port_lock, flags);
+			return -ENODEV;
+		}
+		spin_unlock_irqrestore(&port->port_lock, flags);
+
+		/* Timeout expiration indicates whether CatStudio is closed */
+		ret = wait_event_interruptible_timeout(*p_port_send,
+						!port->port_usb ||
+						pxa910_gs_buf_space_avail(&port->port_write_buf) >
+						(port->port_write_buf.buf_size >> 1), HZ);
+		port = pxa910_ports[GS_DIAG_PORT_BASE].port;
+		tty = port->port.tty;
+		if (unlikely(tty == NULL || tty->driver_data == NULL ||
+					 port == NULL || port->port_usb == NULL))
+			return -ENODEV;
+		if (unlikely(ret == 0)) {
+#ifdef CONFIG_USB_DIAG_STATS
+			if (port->stats.acat_connected)
+				pxa910_ports[GS_DIAG_PORT_BASE].port->stats.acat_disconnected_cnt++;
+			pxa910_ports[GS_DIAG_PORT_BASE].port->stats.acat_connected = false;
+#endif
+			pr_err_ratelimited("CatS closed?\n");
+			return -ENOSPC;
+		}
+	}
+
+	return total;
+}
+
+
+int gs_marvell_diag_send(const unsigned char *buf, int count, int xfer_type)
+{
+#ifdef USB_DIAG_SPLITTED_TRACE
+	static int expected_offset = 0;
+	static int byte_offset = 0;
+	static char splitted_len[4];
+#endif
+	static int ongoing_packet = 0;
+	struct tty_struct *tty;
+	struct pxa910_gs_port *port;
+	int copied, retval = -ENODEV;
+
+	/* Assume that we have only one port */
+	mutex_lock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+	port = pxa910_ports[GS_DIAG_PORT_BASE].port;
+	if (unlikely(!port)) {
+		ongoing_packet = 0;
+		mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+		return -ENODEV;
+	}
+	tty = port->port.tty;
+
+	if (unlikely(tty == NULL || tty->driver_data == NULL
+		|| port->port_usb == NULL)) {
+		pr_err_ratelimited("diag_send_fail: tty null: %d port_usb null: %d\n",
+			(tty == NULL),
+			(port->port_usb == NULL));
+		goto error;
+	}
+
+	if (likely(!ongoing_packet)) {
+		retval = tty_write_lock(tty, 0);
+		if (retval < 0) {
+			pr_err_ratelimited("diag_send_fail: tty lock return error:%d\n",
+				retval);
+			goto error;
+		}
+	}
+	BUG_ON(!tty_is_writelocked(tty));
+
+	copied = gs_marvell_diag_write(tty, buf, count);
+
+	if (unlikely(copied < 0)) {
+		ongoing_packet = 0;
+		/* skip tty_write_unlock to avoid panic if tty is already changed to NULL */
+		if (unlikely(NULL == pxa910_ports[GS_DIAG_PORT_BASE].port->port.tty)) {
+			mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+			pr_err("diag_send_fail: tty 0x%x closed\n", (u32)tty);
+			return copied;
+		}
+		tty_write_unlock(tty);
+		mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+		return copied;
+	}
+
+#ifdef USB_DIAG_SPLITTED_TRACE
+	if (unlikely(trap_splitted_trace(&expected_offset, count, buf, &byte_offset,
+									  splitted_len)))
+	{
+		ongoing_packet = 1;
+	}
+	else
+#endif
+	{
+		ongoing_packet = 0;
+		tty_write_unlock(tty);
+	}
+	mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+	return copied;
+
+error:
+	pr_err_ratelimited("diag_send_fail ongoing_packet: %d tty null: %d\n",
+		ongoing_packet,
+		tty == NULL);
+
+	if (tty != NULL)
+		pr_err_ratelimited("diag_send_fail: ttylocked: %d\n", tty_is_writelocked(tty));
+
+	if (ongoing_packet) {
+		ongoing_packet = 0;
+		if (tty)
+			tty_write_unlock(tty);
+	}
+
+	if (unlikely(tty && tty_is_writelocked(tty))){
+		pr_err("tty is writelocked\n");
+	}
+
+	mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE].lock);
+	return retval;
+}
+EXPORT_SYMBOL(gs_marvell_diag_send);
+
+typedef int (*marvell_diag_rx_callback) (char *packet, int len);
+
+marvell_diag_rx_callback gs_marvell_diag_rx_callback =
+		(marvell_diag_rx_callback) NULL;
+EXPORT_SYMBOL(gs_marvell_diag_rx_callback);
+
+typedef int (*marvell_diag_ioctl) (unsigned int cmd, unsigned long arg);
+
+marvell_diag_ioctl gs_marvell_diag_ioctl = (marvell_diag_ioctl) NULL;
+EXPORT_SYMBOL(gs_marvell_diag_ioctl);
+
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void gs_marvell_diag_rx_push(unsigned long _port)
+{
+	struct pxa910_gs_port *port = (void *)_port;
+	struct tty_struct *tty;
+	struct list_head *queue = &port->read_queue;
+	bool disconnect = false;
+	bool do_push = false;
+	struct timespec64 now;
+
+	/* hand any queued data to the tty */
+	spin_lock_irq(&port->port_lock);
+	tty = port->port.tty;
+	while (!list_empty(queue)) {
+		struct usb_request *req;
+
+		//now = current_time();
+		ktime_get_real_ts64(&now);
+		req = list_first_entry(queue, struct usb_request, list);
+
+		/* discard data if tty was closed */
+		if (!tty)
+			goto recycle;
+
+		/* leave data queued if tty was rx throttled */
+		if (test_bit(TTY_THROTTLED, &tty->flags))
+			break;
+
+		switch (req->status) {
+		case -ESHUTDOWN:
+			disconnect = true;
+			pr_vdebug(PREFIX "%d: shutdown\n", port->port_num);
+			break;
+
+		default:
+			/* presumably a transient fault */
+			pr_warning(PREFIX "%d: unexpected RX status %d\n",
+				   port->port_num, req->status);
+			/* FALLTHROUGH */
+		case 0:
+			/* normal completion */
+			break;
+		}
+
+		/* push data to (open) tty */
+		if (req->actual) {
+			char *packet = req->buf;
+			unsigned size = req->actual;
+			unsigned n;
+			int count;
+
+			/* we may have pushed part of this packet already... */
+			n = port->n_read;
+			if (n) {
+				packet += n;
+				size -= n;
+			}
+
+			if (gs_marvell_diag_rx_callback !=
+			    (marvell_diag_rx_callback) NULL) {
+				int filtered;
+				filtered =
+				    gs_marvell_diag_rx_callback(packet, size);
+				if (filtered)
+					goto recycle;
+			}
+
+			count = tty_insert_flip_string(tty->port, packet, size);
+			if (count)
+				do_push = true;
+			if (count != size) {
+				/* stop pushing; TTY layer can't handle more */
+				port->n_read += count;
+				pr_vdebug(PREFIX "%d: rx block %d/%d\n",
+					  port->port_num, count, req->actual);
+				break;
+			}
+			port->n_read = 0;
+		}
+recycle:
+		list_move(&req->list, &port->read_pool);
+		port->read_started--;
+	}
+
+	/* Push from tty to ldisc; this is immediate with low_latency, and
+	 * may trigger callbacks to this driver ... so drop the spinlock.
+	 */
+	if (tty && do_push) {
+		spin_unlock_irq(&port->port_lock);
+		tty_flip_buffer_push(tty->port);
+		wake_up_interruptible(&tty->read_wait);
+		spin_lock_irq(&port->port_lock);
+
+		/* tty may have been closed */
+		tty = port->port.tty;
+	}
+
+	/* We want our data queue to become empty ASAP, keeping data
+	 * in the tty and ldisc (not here).  If we couldn't push any
+	 * this time around, there may be trouble unless there's an
+	 * implicit tty_unthrottle() call on its way...
+	 *
+	 * REVISIT we should probably add a timer to keep the tasklet
+	 * from starving ... but it's not clear that case ever happens.
+	 */
+	if (!list_empty(queue) && tty) {
+		if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+			if (do_push)
+				tasklet_schedule(&port->push);
+			else
+				pr_warning(PREFIX "%d: RX not scheduled?\n",
+					   port->port_num);
+		}
+	}
+
+	/* If we're still connected, refill the USB RX queue. */
+	if (!disconnect && port->port_usb)
+		pxa910_gs_start_rx(port);
+
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int gs_marvell_diag_port_alloc(unsigned port_num,
+			   struct usb_cdc_line_coding *coding)
+{
+	struct pxa910_gs_port *port;
+
+	port = kzalloc(sizeof(struct pxa910_gs_port), GFP_KERNEL);
+	if (port == NULL)
+		return -ENOMEM;
+
+	tty_port_init(&port->port);
+	spin_lock_init(&port->port_lock);
+	init_waitqueue_head(&port->drain_wait);
+	init_waitqueue_head(&port->close_wait);
+
+	tasklet_init(&port->push, gs_marvell_diag_rx_push, (unsigned long)port);
+
+	INIT_LIST_HEAD(&port->read_pool);
+	INIT_LIST_HEAD(&port->read_queue);
+	INIT_LIST_HEAD(&port->write_pool);
+
+	port->port_num = port_num;
+	port->port_line_coding = *coding;
+	port->tx_wq = create_singlethread_workqueue("diag_gadget_tx");
+	INIT_WORK(&port->tx_work, pxa910_gs_tx_worker);
+	pxa910_ports[port_num].port = port;
+
+	memset(&port->stats, 0, sizeof(struct port_stats));
+
+	return 0;
+}
+
+#ifdef DIAG_USB_TEST
+static void diagUsbTest3()
+{
+	int i;
+	int ret;
+	unsigned long start, end, timeuse;
+
+	memset(testbuf, 'b', sizeof(testbuf));
+	printk(KERN_INFO "diag usb test start...\n");
+	msleep_interruptible(1000);
+
+	start = jiffies;
+	for (i = 0; i < lpcunt; i++) {
+		ret = gs_usb_write(gs_diag_tty, testbuf, sizeof(testbuf));
+		if (ret != sizeof(testbuf))
+			printk(KERN_ERR "usb write error!ret=%d\n", ret);
+
+		while (!writecomplete) {
+			if (wait_event_interruptible(wcwaitQ, \
+						writecomplete == 1))
+				continue;
+		}
+		writecomplete = 0;
+	}
+	end = jiffies;
+	timeuse = (end - start) * 1000 / HZ;
+	printk(KERN_INFO "sending %d Kbytes to use take %d ms\n",
+	       lpcunt * sizeof(testbuf) / 1024, timeuse);
+}
+
+static void diagUsbTest(int caseNo)
+{
+
+	init_waitqueue_head(&wcwaitQ);
+	switch (caseNo) {
+	case 1:
+		/* diagUsbTest1(); */
+		break;
+	case 2:
+		/* diagUsbTest2(); */
+		break;
+	case 3:
+		diagUsbTest3();
+		break;
+	}
+}
+#endif
+
+/*
+ * gs_ioctl
+ */
+static int pxa910_diag_ioctl(struct tty_struct *tty, unsigned int cmd,
+		    unsigned long arg)
+{
+	struct pxa910_gs_port *port;
+	struct pxa910_f_diag *diag;
+	int    rc = -ENOIOCTLCMD;
+
+	if (tty == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	port = tty->driver_data;
+	if (port == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty->driver_data is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	diag = pxa910_port_to_diag(port->port_usb);
+	if (diag == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: diag is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+
+	/* handle ioctls */
+	if (gs_marvell_diag_ioctl)
+		rc = gs_marvell_diag_ioctl(cmd, arg);
+
+	return rc;
+}
+
+static const struct tty_operations gs_marvell_diag_tty_ops = {
+	.open = pxa910_gs_open,
+	.close = pxa910_gs_close,
+	.write = gs_marvell_diag_write,
+	.put_char = pxa910_gs_put_char,
+	.flush_chars = pxa910_gs_flush_chars,
+	.write_room = pxa910_gs_write_room,
+	.chars_in_buffer = pxa910_gs_chars_in_buffer,
+	.ioctl = pxa910_diag_ioctl,
+	.unthrottle = pxa910_gs_unthrottle,
+	.break_ctl = pxa910_gs_break_ctl,
+};
+
+int pxa910_diag_gserial_setup(struct usb_gadget *g, unsigned count)
+{
+
+#define GS_MARVELL_DIAG_MAJOR		126
+#define GS_MARVELL_DIAG_MINOR_START	16
+
+	unsigned i;
+	struct usb_cdc_line_coding coding;
+	int status;
+
+	if (count == 0 || ((count + GS_DIAG_PORT_BASE) > PXA910_N_PORTS)) {
+		pr_err("%s: count err %d\n", __func__, count);
+		return -EINVAL;
+	}
+
+	gs_diag_tty_driver = alloc_tty_driver(count);
+	if (!gs_diag_tty_driver)
+		return -ENOMEM;
+
+	gs_diag_tty_driver->owner = THIS_MODULE;
+	gs_diag_tty_driver->driver_name = "g_serial_diag";
+	gs_diag_tty_driver->name = "ttydiag";
+	/* uses dynamically assigned dev_t values */
+
+	gs_diag_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	gs_diag_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	gs_diag_tty_driver->flags =
+			TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	gs_diag_tty_driver->init_termios = tty_std_termios;
+
+	/* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+	 * MS-Windows.  Otherwise, most of these flags shouldn't affect
+	 * anything unless we were to actually hook up to a serial line.
+	 */
+	gs_diag_tty_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	gs_diag_tty_driver->init_termios.c_ispeed = 9600;
+	gs_diag_tty_driver->init_termios.c_ospeed = 9600;
+
+	gs_diag_tty_driver->major = GS_MARVELL_DIAG_MAJOR;
+	gs_diag_tty_driver->minor_start = GS_MARVELL_DIAG_MINOR_START;
+
+	coding.dwDTERate = cpu_to_le32(9600);
+	coding.bCharFormat = 8;
+	coding.bParityType = USB_CDC_NO_PARITY;
+	coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+	tty_set_operations(gs_diag_tty_driver, &gs_marvell_diag_tty_ops);
+
+	/* make devices be openable */
+	for (i = 0; i < count; i++) {
+		mutex_init(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock);
+		status =
+		  gs_marvell_diag_port_alloc(GS_DIAG_PORT_BASE + i, &coding);
+		if (status) {
+			count = i;
+			goto fail;
+		}
+	}
+	n_diag_ports = count;
+
+	/* export the driver ... */
+	status = tty_register_driver(gs_diag_tty_driver);
+	if (status) {
+		pr_err("%s: cannot register, err %d\n", __func__, status);
+		goto fail;
+	}
+
+	/* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
+	for (i = 0; i < count; i++) {
+		struct device *tty_dev;
+		struct pxa910_gs_port *port =
+			pxa910_ports[GS_DIAG_PORT_BASE + i].port;
+
+		tty_dev = tty_port_register_device(&port->port,
+			gs_diag_tty_driver, i, &g->dev);
+		if (IS_ERR(tty_dev))
+			pr_warning("%s: no classdev for port %d, err %ld\n",
+				   __func__, i, PTR_ERR(tty_dev));
+	}
+
+	pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+		 count, (count == 1) ? "" : "s");
+
+	return status;
+fail:
+	while (count--)
+		kfree(pxa910_ports[GS_DIAG_PORT_BASE + count].port);
+	put_tty_driver(gs_diag_tty_driver);
+	gs_diag_tty_driver = NULL;
+	return status;
+}
+
+/**
+ * gserial_cleanup - remove TTY-over-USB driver and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gserial_setup().
+ * Accordingly, it may need to wait until some open /dev/ files have
+ * closed.
+ *
+ * The caller must have issued @gserial_disconnect() for any ports
+ * that had previously been connected, so that there is never any
+ * I/O pending when it's called.
+ */
+void pxa910_diag_gserial_cleanup(void)
+{
+	unsigned	i;
+	struct pxa910_gs_port *port;
+
+	if (!gs_diag_tty_driver)
+		return;
+
+	for (i = 0; i < n_diag_ports; i++) {
+		/* prevent new opens */
+		mutex_lock(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock);
+		port = pxa910_ports[GS_DIAG_PORT_BASE + i].port;
+		pxa910_ports[GS_DIAG_PORT_BASE + i].port = NULL;
+		mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock);
+
+		tasklet_kill(&port->push);
+		destroy_workqueue(port->tx_wq);
+		/* wait for old opens to finish */
+		wait_event_interruptible(port->close_wait, pxa910_gs_closed(port));
+		WARN_ON(port->port_usb != NULL);
+
+		while (!work_data_is_clear(&port->port.buf.work))
+			schedule();
+
+		tty_port_destroy(&port->port);
+		kfree(port);
+
+		tty_unregister_device(gs_diag_tty_driver, i);
+	}
+	n_diag_ports = 0;
+
+	tty_unregister_driver(gs_diag_tty_driver);
+	put_tty_driver(gs_diag_tty_driver);
+	gs_diag_tty_driver = NULL;
+
+	pr_debug("%s: cleaned up ttyGS* support\n", __func__);
+}
+
+
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa910_f_modem.c b/marvell/linux/drivers/usb/gadget/function/pxa910_f_modem.c
new file mode 100644
index 0000000..93f9e75
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa910_f_modem.c
@@ -0,0 +1,1499 @@
+/*
+ * f_acm.c -- USB CDC serial (ACM) function driver
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 2009 by Samsung Electronics
+ * Author: Michal Nazarewicz (mina86@mina86.com)
+ *
+ * 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.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/usb/composite.h>
+#include <linux/workqueue.h>
+
+#include "pxa910_u_serial.h"
+#include "gadget_chips.h"
+
+/*
+ * This CDC ACM function support just wraps control functions and
+ * notifications around the generic serial-over-usb code.
+ *
+ * Because CDC ACM is standardized by the USB-IF, many host operating
+ * systems have drivers for it.  Accordingly, ACM is the preferred
+ * interop solution for serial-port type connections.  The control
+ * models are often not necessary, and in any case don't do much in
+ * this bare-bones implementation.
+ *
+ * Note that even MS-Windows has some support for ACM.  However, that
+ * support is somewhat broken because when you use ACM in a composite
+ * device, having multiple interfaces confuses the poor OS.  It doesn't
+ * seem to understand CDC Union descriptors.  The new "association"
+ * descriptors (roughly equivalent to CDC Unions) may sometimes help.
+ */
+
+struct pxa910_f_acm {
+	struct pxa910_gserial		port;
+	u8				ctrl_id, data_id;
+	u8				port_num;
+
+	u8				pending;
+
+	/* lock is mostly for pending and notify_req ... they get accessed
+	 * by callbacks both from tty (open/close/break) under its spinlock,
+	 * and notify_req.complete() which can't use that lock.
+	 */
+	spinlock_t			lock;
+
+	struct usb_ep			*notify;
+	struct usb_request		*notify_req;
+
+	struct usb_cdc_line_coding	port_line_coding;	/* 8-N-1 etc */
+
+	/* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */
+	u16				port_handshake_bits;
+#define ACM_CTRL_RTS	(1 << 1)	/* unused with full duplex */
+#define ACM_CTRL_DTR	(1 << 0)	/* host is ready for data r/w */
+
+	/* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */
+	u16				serial_state;
+#define ACM_CTRL_OVERRUN	(1 << 6)
+#define ACM_CTRL_PARITY		(1 << 5)
+#define ACM_CTRL_FRAMING	(1 << 4)
+#define ACM_CTRL_RI		(1 << 3)
+#define ACM_CTRL_BRK		(1 << 2)
+#define ACM_CTRL_DSR		(1 << 1)
+#define ACM_CTRL_DCD		(1 << 0)
+	u32 modem_state;
+#define MODEM_DATA_MODE_OVER_PSD	0
+#define MODEM_DATA_MODE_OVER_CSD	1
+#define MODEM_CONTROL_MODE		2
+	u64 time_stamp;
+	u32 cid;
+#define MODEM_INVALID_CID		(0xff)
+};
+
+static inline struct pxa910_f_acm *pxa910_func_to_acm(struct usb_function *f)
+{
+	return container_of(f, struct pxa910_f_acm, port.func);
+}
+
+static inline struct
+pxa910_f_acm *pxa910_port_to_acm(struct pxa910_gserial *p)
+{
+	return container_of(p, struct pxa910_f_acm, port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* notification endpoint uses smallish and infrequent fixed-size messages */
+
+#define GS_LOG2_NOTIFY_INTERVAL		5	/* 1 << 5 == 32 msec */
+#define GS_NOTIFY_MAXPACKET		10	/* notification + 2 bytes */
+#define GS_MODEM_PORT_BASE		0
+
+/* interface and class descriptors: */
+
+static struct usb_interface_assoc_descriptor
+pxa910_acm_iad_descriptor = {
+	.bLength =		sizeof pxa910_acm_iad_descriptor,
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,
+	.bFunctionClass =	USB_CLASS_COMM,
+	.bFunctionSubClass =	USB_CDC_SUBCLASS_ACM,
+	.bFunctionProtocol =	USB_CDC_ACM_PROTO_AT_V25TER,
+	/* .iFunction =		DYNAMIC */
+};
+
+
+static struct usb_interface_descriptor
+pxa910_acm_control_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_ACM,
+	.bInterfaceProtocol =	USB_CDC_ACM_PROTO_AT_V25TER,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor pxa910_acm_data_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc pxa910_acm_header_desc = {
+	.bLength =		sizeof(pxa910_acm_header_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor
+pxa910_acm_call_mgmt_descriptor = {
+	.bLength =		sizeof(pxa910_acm_call_mgmt_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_CALL_MANAGEMENT_TYPE,
+	.bmCapabilities =	0,
+	/* .bDataInterface = DYNAMIC */
+};
+
+static struct usb_cdc_acm_descriptor pxa910_acm_descriptor = {
+	.bLength =		sizeof(pxa910_acm_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ACM_TYPE,
+	.bmCapabilities =	USB_CDC_CAP_LINE,
+};
+
+static struct usb_cdc_union_desc pxa910_acm_union_desc = {
+	.bLength =		sizeof(pxa910_acm_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor pxa910_acm_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET),
+	.bInterval =		1 << GS_LOG2_NOTIFY_INTERVAL,
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *pxa910_acm_fs_function[] = {
+	(struct usb_descriptor_header *) &pxa910_acm_iad_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_control_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_header_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_union_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_fs_notify_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_data_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_fs_in_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor pxa910_acm_hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(GS_NOTIFY_MAXPACKET),
+	.bInterval =		GS_LOG2_NOTIFY_INTERVAL+4,
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *pxa910_acm_hs_function[] = {
+	(struct usb_descriptor_header *) &pxa910_acm_iad_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_control_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_header_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_union_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_hs_notify_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_data_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_hs_in_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor pxa910_acm_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor pxa910_acm_ss_bulk_comp_desc = {
+	.bLength =              sizeof pxa910_acm_ss_bulk_comp_desc,
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *pxa910_acm_ss_function[] = {
+	(struct usb_descriptor_header *) &pxa910_acm_iad_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_control_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_header_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_descriptor,
+	(struct usb_descriptor_header *) &pxa910_acm_union_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_hs_notify_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_data_interface_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_ss_in_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_ss_out_desc,
+	(struct usb_descriptor_header *) &pxa910_acm_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+
+#define ACM_CTRL_IDX	0
+#define ACM_DATA_IDX	1
+#define ACM_IAD_IDX	2
+
+/* static strings, in UTF-8 */
+static struct usb_string pxa910_acm_string_defs[] = {
+	[ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",
+	[ACM_DATA_IDX].s = "CDC ACM Data",
+	[ACM_IAD_IDX].s = "CDC Serial",
+	{  /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings pxa910_acm_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		pxa910_acm_string_defs,
+};
+
+static struct usb_gadget_strings *pxa910_acm_strings[] = {
+	&pxa910_acm_string_table,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM control ... data handling is delegated to tty library code.
+ * The main task of this function is to activate and deactivate
+ * that code based on device state; track parameters like line
+ * speed, handshake state, and so on; and issue notifications.
+ */
+
+static void pxa910_acm_complete_set_line_coding(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct pxa910_f_acm	*acm = ep->driver_data;
+	struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+
+	if (req->status != 0) {
+		DBG(cdev, "acm ttyGS%d completion, err %d\n",
+				acm->port_num, req->status);
+		return;
+	}
+
+	/* normal completion */
+	if (req->actual != sizeof(acm->port_line_coding)) {
+		DBG(cdev, "acm ttyGS%d short resp, len %d\n",
+				acm->port_num, req->actual);
+		usb_ep_set_halt(ep);
+	} else {
+		struct usb_cdc_line_coding	*value = req->buf;
+
+		/* REVISIT:  we currently just remember this data.
+		 * If we change that, (a) validate it first, then
+		 * (b) update whatever hardware needs updating,
+		 * (c) worry about locking.  This is information on
+		 * the order of 9600-8-N-1 ... most of which means
+		 * nothing unless we control a real RS232 line.
+		 */
+		acm->port_line_coding = *value;
+	}
+}
+
+static int
+pxa910_acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct pxa910_f_acm	*acm = pxa910_func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = cdev->req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	/* composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 *
+	 * Note CDC spec table 4 lists the ACM request profile.  It requires
+	 * encapsulated command support ... we don't handle any, and respond
+	 * to them by stalling.  Options include get/set/clear comm features
+	 * (not that useful) and SEND_BREAK.
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+	/* SET_LINE_CODING ... just read and save what the host sends */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_LINE_CODING:
+		if (w_length != sizeof(struct usb_cdc_line_coding)
+				|| w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = w_length;
+		cdev->gadget->ep0->driver_data = acm;
+		req->complete = pxa910_acm_complete_set_line_coding;
+		break;
+
+	/* GET_LINE_CODING ... return what host sent, or initial value */
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_GET_LINE_CODING:
+		if (w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = min_t(unsigned, w_length,
+				sizeof(struct usb_cdc_line_coding));
+		memcpy(req->buf, &acm->port_line_coding, value);
+		break;
+
+	/* SET_CONTROL_LINE_STATE ... save what the host sent */
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+		if (w_index != acm->ctrl_id)
+			goto invalid;
+
+		value = 0;
+
+		/* FIXME we should not allow data to flow until the
+		 * host sets the ACM_CTRL_DTR bit; and when it clears
+		 * that bit, we should return to that no-flow state.
+		 */
+		acm->port_handshake_bits = w_value;
+		break;
+
+	default:
+invalid:
+		VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
+			acm->port_num, ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = 0;
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			ERROR(cdev, "acm response on ttyGS%d, err %d\n",
+					acm->port_num, value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static int
+pxa910_acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+	struct pxa910_f_acm	*acm = pxa910_func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt == 0, so this is an activation or a reset */
+
+	if (intf == acm->ctrl_id) {
+		if (acm->notify->driver_data) {
+			VDBG(cdev, "reset acm control interface %d\n", intf);
+			usb_ep_disable(acm->notify);
+		} else {
+			VDBG(cdev, "init acm ctrl interface %d\n", intf);
+			if (config_ep_by_speed(cdev->gadget, f, acm->notify))
+				return -EINVAL;
+		}
+		usb_ep_enable(acm->notify);
+		acm->notify->driver_data = acm;
+
+	} else if (intf == acm->data_id) {
+		if (acm->port.in->driver_data) {
+			DBG(cdev, "reset acm ttyGS%d\n", acm->port_num);
+			pxa910_gserial_disconnect(&acm->port);
+		}
+		if (!acm->port.in->desc || !acm->port.out->desc) {
+			DBG(cdev, "activate acm ttyGS%d\n", acm->port_num);
+			if (config_ep_by_speed(cdev->gadget, f,
+					       acm->port.in) ||
+			    config_ep_by_speed(cdev->gadget, f,
+					       acm->port.out)) {
+				acm->port.in->desc = NULL;
+				acm->port.out->desc = NULL;
+				return -EINVAL;
+			}
+		}
+		pxa910_gserial_connect(&acm->port, acm->port_num);
+
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+static void pxa910_acm_disable(struct usb_function *f)
+{
+	struct pxa910_f_acm	*acm = pxa910_func_to_acm(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num);
+	acm->modem_state = MODEM_CONTROL_MODE;
+
+	pxa910_gserial_disconnect(&acm->port);
+	usb_ep_disable(acm->notify);
+	acm->notify->driver_data = NULL;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * acm_cdc_notify - issue CDC notification to host
+ * @acm: wraps host to be notified
+ * @type: notification type
+ * @value: Refer to cdc specs, wValue field.
+ * @data: data to be sent
+ * @length: size of data
+ * Context: irqs blocked, acm->lock held, acm_notify_req non-null
+ *
+ * Returns zero on success or a negative errno.
+ *
+ * See section 6.3.5 of the CDC 1.1 specification for information
+ * about the only notification we issue:  SerialState change.
+ */
+static int pxa910_acm_cdc_notify(struct pxa910_f_acm *acm, u8 type, u16 value,
+		void *data, unsigned length)
+{
+	struct usb_ep			*ep = acm->notify;
+	struct usb_request		*req;
+	struct usb_cdc_notification	*notify;
+	const unsigned			len = sizeof(*notify) + length;
+	void				*buf;
+	int				status;
+
+	req = acm->notify_req;
+	acm->notify_req = NULL;
+	acm->pending = false;
+
+	req->length = len;
+	notify = req->buf;
+	buf = notify + 1;
+
+	notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+			| USB_RECIP_INTERFACE;
+	notify->bNotificationType = type;
+	notify->wValue = cpu_to_le16(value);
+	notify->wIndex = cpu_to_le16(acm->ctrl_id);
+	notify->wLength = cpu_to_le16(length);
+	memcpy(buf, data, length);
+
+	/* ep_queue() can complete immediately if it fills the fifo... */
+	spin_unlock(&acm->lock);
+	status = usb_ep_queue(ep, req, GFP_ATOMIC);
+	spin_lock(&acm->lock);
+
+	if (status < 0) {
+		ERROR(acm->port.func.config->cdev,
+				"acm ttyGS%d can't notify serial state, %d\n",
+				acm->port_num, status);
+		acm->notify_req = req;
+	}
+
+	return status;
+}
+
+static int pxa910_acm_notify_serial_state(struct pxa910_f_acm *acm)
+{
+	struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+	int			status;
+
+	spin_lock(&acm->lock);
+	if (acm->notify_req) {
+		DBG(cdev, "acm ttyGS%d serial state %04x\n",
+				acm->port_num, acm->serial_state);
+		status = pxa910_acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
+				0, &acm->serial_state,
+				sizeof(acm->serial_state));
+	} else {
+		acm->pending = true;
+		status = 0;
+	}
+	spin_unlock(&acm->lock);
+	return status;
+}
+
+static void
+pxa910_acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct pxa910_f_acm	*acm = req->context;
+	u8			doit = false;
+
+	/* on this call path we do NOT hold the port spinlock,
+	 * which is why ACM needs its own spinlock
+	 */
+	spin_lock(&acm->lock);
+	if (req->status != -ESHUTDOWN)
+		doit = acm->pending;
+	acm->notify_req = req;
+	spin_unlock(&acm->lock);
+
+	if (doit)
+		pxa910_acm_notify_serial_state(acm);
+}
+
+/* connect == the TTY link is open */
+
+static void pxa910_acm_connect(struct pxa910_gserial *port)
+{
+	struct pxa910_f_acm	*acm = pxa910_port_to_acm(port);
+
+	acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
+	pxa910_acm_notify_serial_state(acm);
+}
+
+static void pxa910_acm_disconnect(struct pxa910_gserial *port)
+{
+	struct pxa910_f_acm	*acm = pxa910_port_to_acm(port);
+
+	acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
+	pxa910_acm_notify_serial_state(acm);
+}
+
+static int pxa910_acm_send_break(struct pxa910_gserial *port, int duration)
+{
+	struct pxa910_f_acm	*acm = pxa910_port_to_acm(port);
+	u16			state;
+
+	state = acm->serial_state;
+	state &= ~ACM_CTRL_BRK;
+	if (duration)
+		state |= ACM_CTRL_BRK;
+
+	acm->serial_state = state;
+	return pxa910_acm_notify_serial_state(acm);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM function driver setup/binding */
+static int
+pxa910_acm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct pxa910_f_acm	*acm = pxa910_func_to_acm(f);
+	int			status;
+	struct usb_ep		*ep;
+
+	/* allocate instance-specific interface IDs, and patch descriptors */
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	acm->ctrl_id = status;
+	pxa910_acm_iad_descriptor.bFirstInterface = status;
+
+	pxa910_acm_control_interface_desc.bInterfaceNumber = status;
+	pxa910_acm_union_desc.bMasterInterface0 = status;
+
+	status = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+	acm->data_id = status;
+
+	pxa910_acm_data_interface_desc.bInterfaceNumber = status;
+	pxa910_acm_union_desc.bSlaveInterface0 = status;
+	pxa910_acm_call_mgmt_descriptor.bDataInterface = status;
+
+	status = -ENODEV;
+
+	/* allocate instance-specific endpoints */
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa910_acm_fs_in_desc);
+	if (!ep)
+		goto fail;
+	acm->port.in = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa910_acm_fs_out_desc);
+	if (!ep)
+		goto fail;
+	acm->port.out = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	ep = usb_ep_autoconfig(cdev->gadget, &pxa910_acm_fs_notify_desc);
+	if (!ep)
+		goto fail;
+	acm->notify = ep;
+	ep->driver_data = cdev;	/* claim */
+
+	/* allocate notification */
+	acm->notify_req = pxa910_gs_alloc_req(ep,
+			sizeof(struct usb_cdc_notification) + 2,
+			GFP_KERNEL);
+	if (!acm->notify_req)
+		goto fail;
+
+	acm->notify_req->complete = pxa910_acm_cdc_notify_complete;
+	acm->notify_req->context = acm;
+
+	/* copy descriptors */
+	f->fs_descriptors = usb_copy_descriptors(pxa910_acm_fs_function);
+	if (!f->fs_descriptors)
+		goto fail;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		pxa910_acm_hs_in_desc.bEndpointAddress =
+				pxa910_acm_fs_in_desc.bEndpointAddress;
+		pxa910_acm_hs_out_desc.bEndpointAddress =
+				pxa910_acm_fs_out_desc.bEndpointAddress;
+		pxa910_acm_hs_notify_desc.bEndpointAddress =
+				pxa910_acm_fs_notify_desc.bEndpointAddress;
+
+		/* copy descriptors */
+		f->hs_descriptors =
+			usb_copy_descriptors(pxa910_acm_hs_function);
+	}
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		pxa910_acm_ss_in_desc.bEndpointAddress =
+			pxa910_acm_fs_in_desc.bEndpointAddress;
+		pxa910_acm_ss_out_desc.bEndpointAddress =
+			pxa910_acm_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ss_descriptors =
+			usb_copy_descriptors(pxa910_acm_ss_function);
+		if (!f->ss_descriptors)
+			goto fail;
+	}
+	if (gadget_is_superspeed_plus(c->cdev->gadget)) {
+		pxa910_acm_ss_in_desc.bEndpointAddress =
+			pxa910_acm_fs_in_desc.bEndpointAddress;
+		pxa910_acm_ss_out_desc.bEndpointAddress =
+			pxa910_acm_fs_out_desc.bEndpointAddress;
+
+		/* copy descriptors, and track endpoint copies */
+		f->ssp_descriptors =
+			usb_copy_descriptors(pxa910_acm_ss_function);
+		if (!f->ssp_descriptors)
+			goto fail;
+	}
+
+	DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+			acm->port_num,
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			acm->port.in->name, acm->port.out->name,
+			acm->notify->name);
+	return 0;
+
+fail:
+	if (acm->notify_req)
+		pxa910_gs_free_req(acm->notify, acm->notify_req);
+
+	/* we might as well release our claims on endpoints */
+	if (acm->notify)
+		acm->notify->driver_data = NULL;
+	if (acm->port.out)
+		acm->port.out->driver_data = NULL;
+	if (acm->port.in)
+		acm->port.in->driver_data = NULL;
+
+	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+	return status;
+}
+
+static void
+pxa910_acm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct pxa910_f_acm	*acm = pxa910_func_to_acm(f);
+
+	if (gadget_is_dualspeed(c->cdev->gadget))
+		usb_free_descriptors(f->hs_descriptors);
+	if (gadget_is_superspeed(c->cdev->gadget))
+		usb_free_descriptors(f->ss_descriptors);
+	usb_free_descriptors(f->fs_descriptors);
+	pxa910_gs_free_req(acm->notify, acm->notify_req);
+
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MVC2)
+	if (gadget_current_is_dualspeed(c->cdev->gadget))
+		pxa910_acm_string_defs[0].id = 0;
+#endif
+
+	kfree(acm);
+}
+
+/* Some controllers can't support CDC ACM ... */
+static inline bool pxa910_can_support_cdc(struct usb_configuration *c)
+{
+	/* everything else is *probably* fine ... */
+	return true;
+}
+
+/**
+ * acm_bind_config - add a CDC ACM function to a configuration
+ * @c: the configuration to support the CDC ACM instance
+ * @port_num: /dev/ttyGS* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds.  Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int pxa910_acm_bind_config(struct usb_configuration *c, u8 port_num)
+{
+	struct pxa910_f_acm	*acm;
+	int		status;
+
+	if (!pxa910_can_support_cdc(c))
+		return -EINVAL;
+
+	/* REVISIT might want instance-specific strings to help
+	 * distinguish instances ...
+	 */
+
+	/* maybe allocate device-global string IDs, and patch descriptors */
+	if (pxa910_acm_string_defs[ACM_CTRL_IDX].id == 0) {
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		pxa910_acm_string_defs[ACM_CTRL_IDX].id = status;
+
+		pxa910_acm_control_interface_desc.iInterface = status;
+
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		pxa910_acm_string_defs[ACM_DATA_IDX].id = status;
+
+		pxa910_acm_data_interface_desc.iInterface = status;
+
+		status = usb_string_id(c->cdev);
+		if (status < 0)
+			return status;
+		pxa910_acm_string_defs[ACM_IAD_IDX].id = status;
+
+		pxa910_acm_iad_descriptor.iFunction = status;
+	}
+
+	/* allocate and initialize one new instance */
+	acm = kzalloc(sizeof *acm, GFP_KERNEL);
+	if (!acm)
+		return -ENOMEM;
+
+	spin_lock_init(&acm->lock);
+
+	acm->modem_state = MODEM_CONTROL_MODE;
+	acm->cid = MODEM_INVALID_CID;
+	acm->port_num = port_num;
+
+	acm->port.connect = pxa910_acm_connect;
+	acm->port.disconnect = pxa910_acm_disconnect;
+	acm->port.send_break = pxa910_acm_send_break;
+
+	acm->port.func.name = "acm";
+	acm->port.func.strings = pxa910_acm_strings;
+	/* descriptors are per-instance copies */
+	acm->port.func.bind = pxa910_acm_bind;
+	acm->port.func.unbind = pxa910_acm_unbind;
+	acm->port.func.set_alt = pxa910_acm_set_alt;
+	acm->port.func.setup = pxa910_acm_setup;
+	acm->port.func.disable = pxa910_acm_disable;
+
+	init_waitqueue_head(&acm->port.port_send);
+	status = usb_add_function(c, &acm->port.func);
+	if (status)
+		kfree(acm);
+	return status;
+}
+
+int gs_marvell_modem_send(const unsigned char *buf, \
+				int count, unsigned char cid)
+{
+	int buf_avail = 0;
+	struct pxa910_gs_port *port = NULL;
+	struct tty_struct *tty;
+	struct pxa910_f_acm *acm;
+	unsigned long flags;
+	wait_queue_head_t *p_port_send;
+	u32 modem_state;
+
+	/* Assume that we have only one port */
+	port = pxa910_ports[GS_MODEM_PORT_BASE].port;
+	if (NULL == port)
+		return count;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	tty = port->port.tty;
+
+	if (tty == NULL || tty->driver_data == NULL || port->port_usb == NULL) {
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return count;
+	}
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	/* sync with gs_ioctl, get the same f_acm!*/
+	acm = pxa910_port_to_acm(port->port_usb);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	modem_state = acm->modem_state;
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	if (modem_state != MODEM_CONTROL_MODE) {
+		int c, retval = 0;
+		const unsigned char *b = buf;
+		unsigned long start, end;
+		int log_count = 0;
+
+		start = jiffies;
+
+		while (count > 0) {
+			c = pxa910_gs_write(tty, b, count);
+
+			if (c == count) {
+				b += c;
+				count -= c;
+				break; /* send all bytes successfully */
+			} else if (c >= 0) {
+				b += c;
+				count -= c;
+				continue;
+			} else if (c < 0) {
+				retval = c;
+				if (c != -EAGAIN)
+					goto break_out;
+			}
+
+			/* retry to wait enough buffer to send the rest bytes.
+			 * timeout 100msec
+			 */
+			spin_lock_irqsave(&port->port_lock, flags);
+
+			if (port->port_usb) {
+				p_port_send = &port->port_usb->port_send;
+			} else {
+				p_port_send = NULL;
+				printk(KERN_INFO "%s: usb port is released.\n",
+						__func__);
+				spin_unlock_irqrestore(&port->port_lock, flags);
+				break;
+			}
+
+			spin_unlock_irqrestore(&port->port_lock, flags);
+
+			usleep_range(5000, 6000);
+			log_count++;
+
+			/* from enter sleep to wake up,
+			   usb connection could be changed */
+			if (!port->port_usb) {
+				printk(KERN_INFO "%s: usb port is released.\n",
+						__func__);
+				break;
+			}
+			if (log_count == 100 && port) {
+				buf_avail = pxa910_gs_buf_space_avail(
+						&port->port_write_buf);
+
+				printk(KERN_INFO "%s: cannot send the"\
+						"packet over 10s! (%d)\n",
+						 __func__, buf_avail);
+				log_count = 0;
+			}
+		}
+
+break_out:
+		end = jiffies;
+
+		return (b - buf) ? b - buf : retval;
+	} else
+		printk(KERN_ERR "Dropped data packet\n");
+	return 0;
+}
+EXPORT_SYMBOL(gs_marvell_modem_send);
+
+typedef void (*marvell_modem_rx_callback) (unsigned char cid, char *packet,
+					   int len);
+
+marvell_modem_rx_callback gs_marvell_modem_rx_psd_callback =
+	(marvell_modem_rx_callback) NULL;
+EXPORT_SYMBOL(gs_marvell_modem_rx_psd_callback);
+
+marvell_modem_rx_callback gs_marvell_modem_rx_csd_callback =
+	(marvell_modem_rx_callback) NULL;
+EXPORT_SYMBOL(gs_marvell_modem_rx_csd_callback);
+
+
+/*
+ * The following callback is used for notifying a client on the IOCTL
+ * event that was received by the modem.
+ * This notification is needed by the AP implementation of the
+ * PPP module.
+ */
+typedef int (*marvell_modem_ioctl_notify_callback)
+		(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
+
+marvell_modem_ioctl_notify_callback
+	gs_marvell_modem_ioctl_notify_callback =
+	(marvell_modem_ioctl_notify_callback) NULL;
+EXPORT_SYMBOL(gs_marvell_modem_ioctl_notify_callback);
+
+/*
+ * gs_marvell_modem_send_to_atcmdsrv
+ *
+ * This function allows for direct connection between
+ * the PPP server and the AT Command Server.
+ * This functionality is needed because during PPP handshake,
+ * it needs to send AT commands to the AT Command Server
+ * (mimicking the path from PC to AT Command Server)
+ */
+int gs_marvell_modem_send_to_atcmdsrv(char *buf, int count)
+{
+	struct pxa910_gs_port *port = NULL;
+	struct tty_struct *tty;
+	struct pxa910_f_acm *acm;
+	unsigned long flags, modem_state;
+
+	/* Assume that we have only one port */
+	port = pxa910_ports[GS_MODEM_PORT_BASE].port;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	tty = port->port.tty;
+
+	if (tty == NULL || tty->driver_data == NULL || port->port_usb == NULL) {
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return count;
+	}
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	/* sync with gs_ioctl, get the same f_acm!*/
+	acm = pxa910_port_to_acm(port->port_usb);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	modem_state = acm->modem_state;
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	if (modem_state != MODEM_CONTROL_MODE) {
+
+		count = tty_insert_flip_string(tty->port, buf, count);
+
+		if (count) {
+
+			tty_flip_buffer_push(tty->port);
+
+			wake_up_interruptible(&tty->read_wait);
+
+		}
+	} else
+		pr_err("%s: Message from TTY dropped\n", __func__);
+
+	return count;
+}
+EXPORT_SYMBOL(gs_marvell_modem_send_to_atcmdsrv);
+
+static int gs_marvell_modem_write(struct tty_struct *tty,
+				  const unsigned char *buf, int count)
+{
+	struct pxa910_gs_port *port = tty->driver_data;
+	struct pxa910_f_acm *acm = pxa910_port_to_acm(port->port_usb);
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (acm && acm->modem_state != MODEM_CONTROL_MODE) {
+		if (acm->modem_state == MODEM_DATA_MODE_OVER_PSD) {
+			if (gs_marvell_modem_rx_psd_callback !=
+				    (marvell_modem_rx_callback) NULL) {
+					gs_marvell_modem_rx_psd_callback(0xFF,
+								(char *)buf,
+								count);
+				}
+		}
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return count;
+	}
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return pxa910_gs_write(tty, buf, count);
+}
+
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void gs_marvell_modem_rx_push(unsigned long _port)
+{
+	struct pxa910_gs_port *port = (void *)_port;
+	struct pxa910_f_acm *acm = pxa910_port_to_acm(port->port_usb);
+	struct tty_struct *tty;
+	struct list_head *queue = &port->read_queue;
+	bool disconnect = false;
+	bool do_push = false;
+	struct timespec64 now;
+
+	/* hand any queued data to the tty */
+	spin_lock_irq(&port->port_lock);
+	tty = port->port.tty;
+	while (!list_empty(queue)) {
+		struct usb_request *req;
+
+		//now = current_time();
+		ktime_get_real_ts64(&now);
+		req = list_first_entry(queue, struct usb_request, list);
+
+		/* discard data if tty was closed */
+		if (!tty)
+			goto recycle;
+
+		/* leave data queued if tty was rx throttled */
+		if (test_bit(TTY_THROTTLED, &tty->flags))
+			break;
+
+		switch (req->status) {
+		case -ESHUTDOWN:
+			disconnect = true;
+			pr_vdebug(PREFIX "%d: shutdown\n", port->port_num);
+			break;
+
+		default:
+			/* presumably a transient fault */
+			pr_warning(PREFIX "%d: unexpected RX status %d\n",
+				   port->port_num, req->status);
+			/* FALLTHROUGH */
+		case 0:
+			/* normal completion */
+			break;
+		}
+
+		/* push data to (open) tty */
+		if (req->actual) {
+			char *packet = req->buf;
+			unsigned size = req->actual;
+			unsigned n;
+			int count;
+
+			/* we may have pushed part of this packet already... */
+			n = port->n_read;
+			if (n) {
+				packet += n;
+				size -= n;
+			}
+			/* for modem data filter */
+			if (size >= 3) {
+				if ((packet[0] == '+') && (packet[1] == '+')
+				    && (packet[2] == '+')) {
+					printk(KERN_ERR
+					    "received escape charaters +++\n");
+					if (acm->time_stamp -
+					    (now.tv_sec * 1000000000L +
+					     now.tv_nsec) >= 1000000000L) {
+						acm->modem_state =
+						    MODEM_CONTROL_MODE;
+						/* when receive software hangup,
+						     drop CD asap.
+						*/
+						spin_unlock_irq(
+							&port->port_lock);
+						pxa910_acm_disconnect(
+							&acm->port);
+						spin_lock_irq(&port->port_lock);
+					}
+				}
+			}
+			if (acm->modem_state == MODEM_DATA_MODE_OVER_PSD) {
+				if (gs_marvell_modem_rx_psd_callback !=
+				    (marvell_modem_rx_callback) NULL) {
+					gs_marvell_modem_rx_psd_callback(acm->
+									 cid,
+									 packet,
+									 size);
+					acm->time_stamp =
+					    now.tv_sec * 1000000000L +
+					    now.tv_nsec;
+				}
+				goto recycle;
+			} else if (acm->modem_state == \
+					MODEM_DATA_MODE_OVER_CSD) {
+				if (gs_marvell_modem_rx_csd_callback !=
+				    (marvell_modem_rx_callback) NULL) {
+					gs_marvell_modem_rx_csd_callback(acm->
+									 cid,
+									 packet,
+									 size);
+					acm->time_stamp =
+					    now.tv_sec * 1000000000L +
+					    now.tv_nsec;
+				}
+				goto recycle;
+			}
+			count = tty_insert_flip_string(tty->port, packet, size);
+			if (count)
+				do_push = true;
+			if (count != size) {
+				/* stop pushing; TTY layer can't handle more */
+				port->n_read += count;
+				pr_vdebug(PREFIX "%d: rx block %d/%d\n",
+					  port->port_num, count, req->actual);
+				break;
+			}
+			port->n_read = 0;
+		}
+recycle:
+		list_move(&req->list, &port->read_pool);
+		port->read_started--;
+	}
+
+	/* Push from tty to ldisc; this is immediate with low_latency, and
+	 * may trigger callbacks to this driver ... so drop the spinlock.
+	 */
+	if (tty && do_push) {
+		spin_unlock_irq(&port->port_lock);
+		tty_flip_buffer_push(tty->port);
+		wake_up_interruptible(&tty->read_wait);
+		spin_lock_irq(&port->port_lock);
+
+		/* tty may have been closed */
+		tty = port->port.tty;
+	}
+
+	/* We want our data queue to become empty ASAP, keeping data
+	 * in the tty and ldisc (not here).  If we couldn't push any
+	 * this time around, there may be trouble unless there's an
+	 * implicit tty_unthrottle() call on its way...
+	 *
+	 * REVISIT we should probably add a timer to keep the tasklet
+	 * from starving ... but it's not clear that case ever happens.
+	 */
+	if (!list_empty(queue) && tty) {
+		if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+			if (do_push)
+				tasklet_schedule(&port->push);
+			else
+				pr_warning(PREFIX "%d: RX not scheduled?\n",
+					   port->port_num);
+		}
+	}
+
+	/* If we're still connected, refill the USB RX queue. */
+	if (!disconnect && port->port_usb)
+		pxa910_gs_start_rx(port);
+
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int gs_marvell_modem_port_alloc(unsigned port_num,
+			    struct usb_cdc_line_coding *coding)
+{
+	struct pxa910_gs_port *port;
+
+	port = kzalloc(sizeof(struct pxa910_gs_port), GFP_KERNEL);
+	if (port == NULL)
+		return -ENOMEM;
+
+	tty_port_init(&port->port);
+	spin_lock_init(&port->port_lock);
+	init_waitqueue_head(&port->drain_wait);
+	init_waitqueue_head(&port->close_wait);
+
+	tasklet_init(&port->push, gs_marvell_modem_rx_push,
+		     (unsigned long)port);
+
+	INIT_LIST_HEAD(&port->read_pool);
+	INIT_LIST_HEAD(&port->read_queue);
+	INIT_LIST_HEAD(&port->write_pool);
+
+	port->port_num = port_num;
+	port->port_line_coding = *coding;
+	port->tx_wq = create_singlethread_workqueue("modem_gadget_tx");
+	INIT_WORK(&port->tx_work, pxa910_gs_tx_worker);
+	pxa910_ports[port_num].port = port;
+
+	return 0;
+}
+
+#define TIOENABLE       _IOW('T', 206, int)	/* enable data */
+#define TIODISABLE      _IOW('T', 207, int)	/* disable data */
+#define TIOPPPON        _IOW('T', 208, int)
+#define TIOPPPOFF       _IOW('T', 209, int)
+#define TIOPPPONCSD     _IOW('T', 211, int)
+/*
+ * gs_ioctl
+ */
+static int pxa910_gs_ioctl(struct tty_struct *tty, unsigned int cmd,
+		    unsigned long arg)
+{
+	struct pxa910_gs_port *port;
+	struct pxa910_f_acm *acm;
+	int    rc = 0;
+
+	if (tty == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	port = tty->driver_data;
+	if (port == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: tty->driver_data is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+	acm = pxa910_port_to_acm(port->port_usb);
+	if (acm == NULL) {
+		pr_err("%s cmd %u. call from context [%d:%d]: acm is NULL pointer\n",
+			   __func__, cmd, current->pid, current->tgid);
+		return -EIO;
+	}
+
+	switch (cmd) {
+	case TIOENABLE:
+		acm->cid = (unsigned char)arg;
+		break;
+	case TIODISABLE:
+		acm->cid = (unsigned char)arg;
+		break;
+	case TIOPPPON:
+		acm->cid = (unsigned char)arg;
+		acm->modem_state = MODEM_DATA_MODE_OVER_PSD;
+		acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
+		pxa910_acm_notify_serial_state(acm);
+		break;
+	case TIOPPPONCSD:
+		acm->cid = (unsigned char)arg;
+		acm->modem_state = MODEM_DATA_MODE_OVER_CSD;
+		break;
+	case TIOPPPOFF:
+		acm->cid = MODEM_INVALID_CID;
+		acm->modem_state = MODEM_CONTROL_MODE;
+		acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
+		pxa910_acm_notify_serial_state(acm);
+		break;
+	default:
+		pr_err("%s cmd %u. call from context [%d:%d]: Command not implemented\n",
+			   __func__, cmd, current->pid, current->tgid);
+		/* could not handle ioctl */
+		rc = -ENOIOCTLCMD;
+
+	}
+
+	/*
+	 * Check if notification callback is registered, and if so
+	 * call it.
+	 */
+	if (gs_marvell_modem_ioctl_notify_callback != NULL)
+		rc = gs_marvell_modem_ioctl_notify_callback(tty, cmd, arg);
+
+	return rc;
+}
+
+static const struct tty_operations gs_marvell_modem_tty_ops = {
+	.open = pxa910_gs_open,
+	.close = pxa910_gs_close,
+	.write = gs_marvell_modem_write,
+	.put_char = pxa910_gs_put_char,
+	.flush_chars = pxa910_gs_flush_chars,
+	.write_room = pxa910_gs_write_room,
+	.chars_in_buffer = pxa910_gs_chars_in_buffer,
+	.ioctl = pxa910_gs_ioctl,
+	.unthrottle = pxa910_gs_unthrottle,
+	.break_ctl = pxa910_gs_break_ctl,
+};
+
+int pxa910_modem_gserial_setup(struct usb_gadget *g, unsigned count)
+{
+
+#define GS_MARVELL_MODEM_MAJOR			126
+#define GS_MARVELL_MODEM_MINOR_START	32
+
+	unsigned i;
+	struct usb_cdc_line_coding coding;
+	int status;
+
+	if (count == 0 || ((count + GS_MODEM_PORT_BASE) > PXA910_N_PORTS)) {
+		pr_err("%s: count err %d\n", __func__, count);
+		return -EINVAL;
+	}
+
+	gs_modem_tty_driver = alloc_tty_driver(count);
+	if (!gs_modem_tty_driver)
+		return -ENOMEM;
+
+
+	gs_modem_tty_driver->owner = THIS_MODULE;
+	gs_modem_tty_driver->driver_name = "g_serial_modem";
+	gs_modem_tty_driver->name = "ttymodem";
+	/* uses dynamically assigned dev_t values */
+
+	gs_modem_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	gs_modem_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	gs_modem_tty_driver->flags =
+		TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	gs_modem_tty_driver->init_termios = tty_std_termios;
+
+	/* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+	 * MS-Windows.  Otherwise, most of these flags shouldn't affect
+	 * anything unless we were to actually hook up to a serial line.
+	 */
+	gs_modem_tty_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	gs_modem_tty_driver->init_termios.c_ispeed = 9600;
+	gs_modem_tty_driver->init_termios.c_ospeed = 9600;
+
+	gs_modem_tty_driver->major = GS_MARVELL_MODEM_MAJOR;
+	gs_modem_tty_driver->minor_start = GS_MARVELL_MODEM_MINOR_START;
+
+	coding.dwDTERate = cpu_to_le32(9600);
+	coding.bCharFormat = 8;
+	coding.bParityType = USB_CDC_NO_PARITY;
+	coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+	tty_set_operations(gs_modem_tty_driver, &gs_marvell_modem_tty_ops);
+
+	/* make devices be openable */
+	for (i = 0; i < count; i++) {
+		mutex_init(&pxa910_ports[GS_MODEM_PORT_BASE + i].lock);
+		status =
+		  gs_marvell_modem_port_alloc(GS_MODEM_PORT_BASE + i, &coding);
+		if (status) {
+			count = i;
+			goto fail;
+		}
+	}
+	n_modem_ports = count;
+
+	/* export the driver ... */
+	status = tty_register_driver(gs_modem_tty_driver);
+	if (status) {
+		pr_err("%s: cannot register, err %d\n", __func__, status);
+		goto fail;
+	}
+
+	/* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
+	for (i = 0; i < count; i++) {
+		struct device *tty_dev;
+		struct pxa910_gs_port *port =
+			pxa910_ports[GS_MODEM_PORT_BASE + i].port;
+
+		tty_dev = tty_port_register_device(&port->port,
+			gs_modem_tty_driver, i, &g->dev);
+		if (IS_ERR(tty_dev))
+			pr_warning("%s: no classdev for port %d, err %ld\n",
+				   __func__, i, PTR_ERR(tty_dev));
+	}
+
+	pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+		 count, (count == 1) ? "" : "s");
+
+	return status;
+fail:
+	while (count--)
+		kfree(pxa910_ports[GS_MODEM_PORT_BASE + count].port);
+	put_tty_driver(gs_modem_tty_driver);
+	gs_modem_tty_driver = NULL;
+	return status;
+}
+
+/**
+ * gserial_cleanup - remove TTY-over-USB driver and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gserial_setup().
+ * Accordingly, it may need to wait until some open /dev/ files have
+ * closed.
+ *
+ * The caller must have issued @gserial_disconnect() for any ports
+ * that had previously been connected, so that there is never any
+ * I/O pending when it's called.
+ */
+void pxa910_modem_gserial_cleanup(void)
+{
+	unsigned	i;
+	struct pxa910_gs_port *port;
+
+	if (!gs_modem_tty_driver)
+		return;
+
+	for (i = 0; i < n_modem_ports; i++) {
+		/* prevent new opens */
+		mutex_lock(&pxa910_ports[GS_MODEM_PORT_BASE + i].lock);
+		port = pxa910_ports[GS_MODEM_PORT_BASE + i].port;
+		pxa910_ports[GS_MODEM_PORT_BASE + i].port = NULL;
+		mutex_unlock(&pxa910_ports[GS_MODEM_PORT_BASE + i].lock);
+
+		tasklet_kill(&port->push);
+		destroy_workqueue(port->tx_wq);
+		/* wait for old opens to finish */
+		wait_event_interruptible(port->close_wait, pxa910_gs_closed(port));
+		WARN_ON(port->port_usb != NULL);
+
+		while (!work_data_is_clear(&port->port.buf.work))
+			schedule();
+		tty_port_destroy(&port->port);
+		kfree(port);
+
+		tty_unregister_device(gs_modem_tty_driver, i);
+	}
+	n_modem_ports = 0;
+
+	tty_unregister_driver(gs_modem_tty_driver);
+	put_tty_driver(gs_modem_tty_driver);
+	gs_modem_tty_driver = NULL;
+
+	pr_debug("%s: cleaned up ttyGS* support\n", __func__);
+}
+
+
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.c b/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.c
new file mode 100644
index 0000000..c03c1ef
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.c
@@ -0,0 +1,1637 @@
+/*
+ * u_serial.c - utilities for USB gadget "serial port"/TTY support
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This code also borrows from usbserial.c, which is
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com)
+ *
+ * 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.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+
+#include "pxa910_u_serial.h"
+
+
+/*
+ * This component encapsulates the TTY layer glue needed to provide basic
+ * "serial port" functionality through the USB gadget stack.  Each such
+ * port is exposed through a /dev/ttyGS* node.
+ *
+ * After initialization (gserial_setup), these TTY port devices stay
+ * available until they are removed (gserial_cleanup).  Each one may be
+ * connected to a USB function (gserial_connect), or disconnected (with
+ * gserial_disconnect) when the USB host issues a config change event.
+ * Data can only flow when the port is connected to the host.
+ *
+ * A given TTY port can be made available in multiple configurations.
+ * For example, each one might expose a ttyGS0 node which provides a
+ * login application.  In one case that might use CDC ACM interface 0,
+ * while another configuration might use interface 3 for that.  The
+ * work to handle that (including descriptor management) is not part
+ * of this component.
+ *
+ * Configurations may expose more than one TTY port.  For example, if
+ * ttyGS0 provides login service, then ttyGS1 might provide dialer access
+ * for a telephone or fax link.  And ttyGS2 might be something that just
+ * needs a simple byte stream interface for some messaging protocol that
+ * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
+ */
+
+#define PREFIX	"ttyGS"
+
+/*
+ * gserial is the lifecycle interface, used by USB functions
+ * gs_port is the I/O nexus, used by the tty driver
+ * tty_struct links to the tty/filesystem framework
+ *
+ * gserial <---> gs_port ... links will be null when the USB link is
+ * inactive; managed by gserial_{connect,disconnect}().  each gserial
+ * instance can wrap its own USB control protocol.
+ *	gserial->ioport == usb_ep->driver_data ... gs_port
+ *	gs_port->port_usb ... gserial
+ *
+ * gs_port <---> tty_struct ... links will be null when the TTY file
+ * isn't opened; managed by gs_open()/gs_close()
+ *	gserial->port.tty ... tty_struct
+ *	tty_struct->driver_data ... gserial
+ */
+
+/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the
+ * next layer of buffering.  For TX that's a circular buffer; for RX
+ * consider it a NOP.  A third layer is provided by the TTY code.
+ */
+#define QUEUE_SIZE		16
+#define PXA910_WRITE_BUF_SIZE	(8192*2)		/* TX only */
+
+/* circular buffer */
+struct pxa910_gs_buf {
+	unsigned		buf_size;
+	char			*buf_buf;
+	char			*buf_get;
+	char			*buf_put;
+};
+
+struct port_stats {
+	unsigned	rx_bytes;
+	unsigned	tx_bytes;
+	unsigned	rx_reqs;
+	unsigned	tx_reqs;
+
+	unsigned	out_of_sync_cnt;
+	unsigned	acat_disconnected_cnt;
+
+	bool		acat_connected;
+	/* direct_rb cb for shmem status */
+	int			(*shmem_status_cb)(char *buf, int offset);
+
+	bool		printkbuf;
+};
+/*
+ * The port structure holds info for each port, one for each minor number
+ * (and thus for each /dev/ node).
+ */
+struct pxa910_gs_port {
+	struct tty_port		port;
+	spinlock_t		port_lock;	/* guard port_* access */
+
+	struct pxa910_gserial	*port_usb;
+
+	bool			openclose;	/* open/close in progress */
+	u8			port_num;
+
+	struct list_head	read_pool;
+	int read_started;
+	int read_allocated;
+	struct list_head	read_queue;
+	unsigned		n_read;
+	struct tasklet_struct	push;
+	struct workqueue_struct *tx_wq; /* tx workqueue */
+	struct work_struct tx_work; /* tx work */
+
+	struct list_head	write_pool;
+	int write_started;
+	int write_allocated;
+	struct pxa910_gs_buf		port_write_buf;
+	wait_queue_head_t	drain_wait;	/* wait while writes drain */
+	wait_queue_head_t	close_wait;
+
+	struct port_stats stats;
+
+	u32 malloc_bufs[QUEUE_SIZE];
+	u32 free_bufs[QUEUE_SIZE];
+
+	/* REVISIT this state ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 8-N-1 etc */
+};
+
+/* increase N_PORTS if you need more */
+#define PXA910_N_PORTS		4
+static struct pxa910_portmaster {
+	struct mutex	lock;			/* protect open/close */
+	struct pxa910_gs_port	*port;
+} pxa910_ports[PXA910_N_PORTS];
+
+static unsigned n_modem_ports;
+static unsigned n_diag_ports;
+static u32 tmp_bufs[QUEUE_SIZE];
+#define PXA910_GS_CLOSE_TIMEOUT		2		/* seconds */
+
+
+#ifndef pr_vdebug
+#ifdef VERBOSE_DEBUG
+#define pr_vdebug(fmt, arg...) \
+	pr_debug(fmt, ##arg)
+#else
+#define pr_vdebug(fmt, arg...) \
+	({ if (0) pr_debug(fmt, ##arg); })
+#endif
+#endif
+
+/*-------------------------------------------------------------------------*/
+/* Sulog uses these function to output straight to USB (instead of through userspace) */
+
+static struct debug_serial_direct_callbacks diag_serial_cb_func;
+static bool diag_serial_ready;
+static bool register_diag_serial_available = true;
+struct usb_request *req_diag;
+static char *g_pre_diag_tx_buf;
+
+int diag_data_tx_callback_tracer(const unsigned char *buf, int count);
+static void diag_read_complete_tracer(struct usb_ep *ep,
+				       struct usb_request *req);
+
+struct debug_serial_direct_callbacks *pxa_register_diag_serial_streaming(void)
+{
+	if (register_diag_serial_available) {
+		diag_serial_cb_func.send_buff_cb =
+		    diag_data_tx_callback_tracer;
+		register_diag_serial_available = false;
+		return &diag_serial_cb_func;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL(pxa_register_diag_serial_streaming);
+
+void pxa_unregister_diag_serial_streaming(void)
+{
+	register_diag_serial_available = true;
+	diag_serial_cb_func.send_buff_cb = NULL;
+}
+EXPORT_SYMBOL(pxa_unregister_diag_serial_streaming);
+
+static void diag_read_complete_tracer(struct usb_ep *ep,
+				       struct usb_request *req)
+{
+	if (req->status) {
+		pr_err("free req %d %d %d", req->actual, req->length,
+		       req->status);
+	}
+	diag_serial_ready = true;
+}
+
+int diag_data_tx_callback_tracer(const unsigned char *buf, int count)
+{
+	int status = 0;
+	int port_num = 1;
+	struct pxa910_gs_port *port;
+	struct usb_ep *in;
+	unsigned long flags;
+
+	port = pxa910_ports[port_num].port;
+	if (!port || !port->port_usb) {
+		pr_err_ratelimited("%s line %d: error! port usb uninitialized\n",
+							__func__, __LINE__);
+		return -1;
+	}
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	in = port->port_usb->in;
+
+	if (!in) {
+		pr_err_ratelimited("%s line %d: usb port not ready\n",
+							__func__, __LINE__);
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return 0;
+	}
+
+	if (!diag_serial_ready) {
+		pr_err_ratelimited("%s line %d: error! diag_serial_ready = 0\n",
+							__func__, __LINE__);
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		return -EBUSY;
+	}
+
+	diag_serial_ready = false;
+	spin_unlock_irqrestore(&port->port_lock, flags);
+	if (!req_diag)
+		req_diag = usb_ep_alloc_request(in, GFP_ATOMIC);
+
+	if (!req_diag) {
+		pr_err("goto fail\n");
+		goto fail;
+	}
+
+	req_diag->length = count;
+
+	req_diag->buf = (void *)buf;
+	req_diag->zero = 0;
+	req_diag->complete = &diag_read_complete_tracer;
+
+	req_diag->status = 0;
+	status = usb_ep_queue(in, req_diag, GFP_ATOMIC);
+	if (status) {
+fail:
+		pr_err("%s:%d: error to send status=%d\n",
+		__func__, __LINE__, status);
+		diag_serial_ready = true;
+		return -1;
+	}
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Circular Buffer */
+
+/*
+ * gs_buf_alloc
+ *
+ * Allocate a circular buffer and all associated memory.
+ */
+static int pxa910_gs_buf_alloc(struct pxa910_gs_buf *gb, unsigned size)
+{
+	if (g_pre_diag_tx_buf) {
+		gb->buf_buf = g_pre_diag_tx_buf;
+	} else {
+		gb->buf_buf = vmalloc(size);
+		if (gb->buf_buf == NULL)
+			return -ENOMEM;
+	}
+
+	gb->buf_size = size;
+	gb->buf_put = gb->buf_buf;
+	gb->buf_get = gb->buf_buf;
+
+	return 0;
+}
+
+/*
+ * gs_buf_free
+ *
+ * Free the buffer and all associated memory.
+ */
+static void pxa910_gs_buf_free(struct pxa910_gs_buf *gb)
+{
+	gb->buf_get = gb->buf_put;
+
+	if (NULL == g_pre_diag_tx_buf)
+		vfree(gb->buf_buf);
+
+	gb->buf_buf = NULL;
+	gb->buf_size = 0;
+
+}
+
+/*
+ * gs_buf_clear
+ *
+ * Clear out all data in the circular buffer.
+ */
+static void pxa910_gs_buf_clear(struct pxa910_gs_buf *gb)
+{
+	gb->buf_get = gb->buf_put;
+	/* equivalent to a get of all data available */
+}
+
+/*
+ * gs_buf_data_avail
+ *
+ * Return the number of bytes of data written into the circular
+ * buffer.
+ */
+static unsigned pxa910_gs_buf_data_avail(struct pxa910_gs_buf *gb)
+{
+	return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size;
+}
+
+/*
+ * gs_buf_space_avail
+ *
+ * Return the number of bytes of space available in the circular
+ * buffer.
+ */
+static unsigned pxa910_gs_buf_space_avail(struct pxa910_gs_buf *gb)
+{
+	if (gb->buf_size == 0)
+		return 0;
+	return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size;
+}
+
+/*
+ * gs_buf_put
+ *
+ * Copy data data from a user buffer and put it into the circular buffer.
+ * Restrict to the amount of space available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+pxa910_gs_buf_put(struct pxa910_gs_buf *gb, const char *buf, unsigned count)
+{
+	unsigned len;
+
+	len  = pxa910_gs_buf_space_avail(gb);
+	if (count > len) {
+		count = len;
+	}
+
+	if (count == 0)
+		return 0;
+
+	len = gb->buf_buf + gb->buf_size - gb->buf_put;
+	if (count > len) {
+		memcpy(gb->buf_put, buf, len);
+		memcpy(gb->buf_buf, buf+len, count - len);
+		gb->buf_put = gb->buf_buf + count - len;
+	} else {
+		memcpy(gb->buf_put, buf, count);
+		if (count < len)
+			gb->buf_put += count;
+		else /* count == len */
+			gb->buf_put = gb->buf_buf;
+	}
+
+	return count;
+}
+
+/*
+ * gs_buf_get
+ *
+ * Get data from the circular buffer and copy to the given buffer.
+ * Restrict to the amount of data available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+pxa910_gs_buf_get(struct pxa910_gs_buf *gb, char *buf, unsigned count)
+{
+	unsigned len;
+
+	len = pxa910_gs_buf_data_avail(gb);
+	if (count > len)
+		count = len;
+
+	if (count == 0)
+		return 0;
+
+	len = gb->buf_buf + gb->buf_size - gb->buf_get;
+	if (count > len) {
+		memcpy(buf, gb->buf_get, len);
+		memcpy(buf+len, gb->buf_buf, count - len);
+		gb->buf_get = gb->buf_buf + count - len;
+	} else {
+		memcpy(buf, gb->buf_get, count);
+		if (count < len)
+			gb->buf_get += count;
+		else /* count == len */
+			gb->buf_get = gb->buf_buf;
+	}
+
+	return count;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* I/O glue between TTY (upper) and USB function (lower) driver layers */
+
+/*
+ * gs_alloc_req
+ *
+ * Allocate a usb_request and its buffer.  Returns a pointer to the
+ * usb_request or NULL if there is an error.
+ */
+struct usb_request *
+pxa910_gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+	if (req != NULL) {
+		req->length = len;
+#ifdef CONFIG_PXA910_1G_DDR_WORKAROUND
+		req->buf = kmalloc(len, kmalloc_flags | GFP_DMA);
+#else
+		req->buf = kmalloc(len, kmalloc_flags);
+#endif
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			return NULL;
+		}
+		req->buf_len = len;
+	}
+
+	return req;
+}
+
+/*
+ * gs_free_req
+ *
+ * Free a usb_request and its buffer.
+ */
+void pxa910_gs_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	if (!req->buf) {
+		pr_err("error diag req: 0x%x\n", (u32)req);
+		BUG();
+	}
+	kfree(req->buf);
+	req->buf = NULL;
+	usb_ep_free_request(ep, req);
+}
+
+/*
+ * gs_send_packet
+ *
+ * If there is data to send, a packet is built in the given
+ * buffer and the size is returned.  If there is no data to
+ * send, 0 is returned.
+ *
+ * Called with port_lock held.
+ */
+static unsigned
+pxa910_gs_send_packet(struct pxa910_gs_port *port, char *packet, unsigned size)
+{
+	unsigned len;
+
+	len = pxa910_gs_buf_data_avail(&port->port_write_buf);
+	if (len < size)
+		size = len;
+	if (size != 0)
+		size = pxa910_gs_buf_get(&port->port_write_buf, packet, size);
+	return size;
+}
+
+static int pxa910_gs_start_tx(struct pxa910_gs_port *port)
+{
+	return queue_work(port->tx_wq, &port->tx_work);
+}
+
+/*
+ * pxa910_gs_tx_worker
+ *
+ * This function finds available write requests, calls
+ * gs_send_packet to fill these packets with data, and
+ * continues until either there are no more write requests
+ * available or no more data to send.  This function is
+ * run whenever data arrives or write requests are available.
+ *
+ * Context: port_usb is non-null.
+ */
+void pxa910_gs_tx_worker(struct work_struct *work)
+{
+	struct pxa910_gs_port *port = (struct pxa910_gs_port *)
+		container_of(work, struct pxa910_gs_port, tx_work);
+	struct list_head	*pool;
+	struct usb_ep		*in = NULL;
+	int			status = 0;
+	bool			do_tty_wake = false;
+	unsigned long		flags;
+
+	if (NULL == port)
+		return;
+
+	pool = &port->write_pool;
+	if (port->port_usb)
+		in = port->port_usb->in;
+
+	if (!in)
+		return;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	while (!list_empty(pool)) {
+		struct usb_request	*req;
+		int			len;
+
+		if (port->write_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		if (in->maxpacket >= 1024 && in->maxpacket > req->buf_len) {
+			pr_err("diag free buf: 0x%x, diag len: %d, buf_len: %d\n", (u32)req->buf, len, req->buf_len);
+			if (req->buf)
+				kfree(req->buf);
+			else
+				pr_err("diag buff NULL\n");
+			req->buf = kmalloc(in->maxpacket, GFP_ATOMIC);
+			req->length = req->buf_len = in->maxpacket;
+		}
+		len = pxa910_gs_send_packet(port, req->buf, in->maxpacket);
+		if (len == 0) {
+			wake_up_interruptible(&port->drain_wait);
+			break;
+		}
+		if (pxa910_gs_buf_space_avail(&port->port_write_buf) > (port->port_write_buf.buf_size >> 1))
+			wake_up_interruptible(&port->port_usb->port_send);
+
+		do_tty_wake = true;
+
+		req->length = len;
+		if (len > req->buf_len) {
+			pr_err("diag len: %d, buf_len: %d\n", len, req->buf_len);
+			WARN_ON(1);
+		}
+
+		list_del(&req->list);
+		req->zero =
+			(pxa910_gs_buf_data_avail(&port->port_write_buf) == 0);
+
+		pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
+				port->port_num, len, *((u8 *)req->buf),
+				*((u8 *)req->buf+1), *((u8 *)req->buf+2));
+
+		/* Drop lock while we call out of driver; completions
+		 * could be issued while we do so.  Disconnection may
+		 * happen too; maybe immediately before we queue this!
+		 *
+		 * NOTE that we may keep sending data for a while after
+		 * the TTY closed (dev->ioport->port.tty is NULL).
+		 */
+		spin_unlock_irqrestore(&port->port_lock, flags);
+		status = usb_ep_queue(in, req, GFP_ATOMIC);
+		spin_lock_irqsave(&port->port_lock, flags);
+
+		if (status) {
+			pr_debug("%s: %s %s err %d\n",
+					__func__, "queue", in->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+
+		port->write_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+
+	if (do_tty_wake && port->port_usb)
+		wake_up_interruptible(&port->port_usb->port_send);
+
+	if (do_tty_wake && port->port.tty)
+		tty_wakeup(port->port.tty);
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return;
+}
+
+/*
+ * Context: caller owns port_lock, and port_usb is set
+ */
+static unsigned pxa910_gs_start_rx(struct pxa910_gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+	struct list_head	*pool;
+	struct usb_ep		*out = NULL;
+
+	if (NULL == port)
+		return 0;
+
+	pool = &port->read_pool;
+	if (port->port_usb)
+		out = port->port_usb->out;
+
+	if (!out)
+		return 0;
+
+	while (!list_empty(pool)) {
+		struct usb_request	*req;
+		int			status;
+		struct tty_struct	*tty;
+
+		/* no more rx if closed */
+		tty = port->port.tty;
+		if (!tty)
+			break;
+
+		if (port->read_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		list_del(&req->list);
+		req->length = out->maxpacket;
+
+		/* drop lock while we call out; the controller driver
+		 * may need to call us back (e.g. for disconnect)
+		 */
+		spin_unlock(&port->port_lock);
+		status = usb_ep_queue(out, req, GFP_ATOMIC);
+		spin_lock(&port->port_lock);
+
+		if (status) {
+			pr_debug("%s: %s %s err %d\n",
+					__func__, "queue", out->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+		port->read_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+	return port->read_started;
+}
+
+#if 0
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void pxa910_gs_rx_push(unsigned long _port)
+{
+	struct pxa910_gs_port		*port = (void *)_port;
+	struct tty_struct	*tty;
+	struct list_head	*queue = &port->read_queue;
+	bool			disconnect = false;
+	bool			do_push = false;
+
+	/* hand any queued data to the tty */
+	spin_lock_irq(&port->port_lock);
+	tty = port->port.tty;
+	while (!list_empty(queue)) {
+		struct usb_request	*req;
+
+		req = list_first_entry(queue, struct usb_request, list);
+
+		/* discard data if tty was closed */
+		if (!tty)
+			goto recycle;
+
+		/* leave data queued if tty was rx throttled */
+		if (test_bit(TTY_THROTTLED, &tty->flags))
+			break;
+
+		switch (req->status) {
+		case -ESHUTDOWN:
+			disconnect = true;
+			pr_vdebug(PREFIX "%d: shutdown\n", port->port_num);
+			break;
+
+		default:
+			/* presumably a transient fault */
+			pr_warning(PREFIX "%d: unexpected RX status %d\n",
+					port->port_num, req->status);
+			/* FALLTHROUGH */
+		case 0:
+			/* normal completion */
+			break;
+		}
+
+		/* push data to (open) tty */
+		if (req->actual) {
+			char		*packet = req->buf;
+			unsigned	size = req->actual;
+			unsigned	n;
+			int		count;
+
+			/* we may have pushed part of this packet already... */
+			n = port->n_read;
+			if (n) {
+				packet += n;
+				size -= n;
+			}
+
+			count = tty_insert_flip_string(tty->port, packet, size);
+			if (count)
+				do_push = true;
+			if (count != size) {
+				/* stop pushing; TTY layer can't handle more */
+				port->n_read += count;
+				pr_vdebug(PREFIX "%d: rx block %d/%d\n",
+						port->port_num,
+						count, req->actual);
+				break;
+			}
+			port->n_read = 0;
+		}
+recycle:
+		list_move(&req->list, &port->read_pool);
+		port->read_started--;
+	}
+
+	/* Push from tty to ldisc; without low_latency set this is handled by
+	 * a workqueue, so we won't get callbacks and can hold port_lock
+	 */
+	if (tty && do_push)
+		tty_flip_buffer_push(tty->port);
+
+
+	/* We want our data queue to become empty ASAP, keeping data
+	 * in the tty and ldisc (not here).  If we couldn't push any
+	 * this time around, there may be trouble unless there's an
+	 * implicit tty_unthrottle() call on its way...
+	 *
+	 * REVISIT we should probably add a timer to keep the tasklet
+	 * from starving ... but it's not clear that case ever happens.
+	 */
+	if (!list_empty(queue) && tty) {
+		if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+			if (do_push)
+				tasklet_schedule(&port->push);
+			else
+				pr_warning(PREFIX "%d: RX not scheduled?\n",
+					port->port_num);
+		}
+	}
+
+	/* If we're still connected, refill the USB RX queue. */
+	if (!disconnect && port->port_usb)
+		pxa910_gs_start_rx(port);
+
+	spin_unlock_irq(&port->port_lock);
+}
+#endif
+
+static void pxa910_gs_read_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct pxa910_gs_port	*port = ep->driver_data;
+#ifdef CONFIG_USB_DIAG_STATS
+	port->stats.rx_bytes += req->length;
+	port->stats.rx_reqs++;
+	port->stats.acat_connected = true;
+#endif
+	/* Queue all received data until the tty layer is ready for it. */
+	spin_lock(&port->port_lock);
+	list_add_tail(&req->list, &port->read_queue);
+	tasklet_schedule(&port->push);
+	spin_unlock(&port->port_lock);
+}
+
+static void
+pxa910_gs_write_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct pxa910_gs_port	*port = ep->driver_data;
+
+	spin_lock(&port->port_lock);
+	list_add(&req->list, &port->write_pool);
+	port->write_started--;
+#ifdef CONFIG_USB_DIAG_STATS
+	port->stats.tx_bytes += req->length;
+	port->stats.tx_reqs++;
+#endif
+
+	switch (req->status) {
+	default:
+		/* presumably a transient fault */
+		pr_warning("%s: unexpected %s status %d\n",
+				__func__, ep->name, req->status);
+		/* FALL THROUGH */
+	case 0:
+		/* normal completion */
+		if (pxa910_gs_buf_data_avail(&port->port_write_buf) > 0)
+			pxa910_gs_start_tx(port);
+		break;
+
+	case -ESHUTDOWN:
+		/* disconnect */
+		pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
+		break;
+	}
+
+	spin_unlock(&port->port_lock);
+}
+
+static void pxa910_gs_free_requests(struct usb_ep *ep, struct list_head *head,
+							 int *allocated)
+{
+	struct usb_request	*req;
+	int nr_free = 0;
+
+	memset(tmp_bufs, 0, sizeof(tmp_bufs));
+	while (!list_empty(head)) {
+		req = list_entry(head->next, struct usb_request, list);
+		list_del(&req->list);
+		if (nr_free < QUEUE_SIZE)
+			tmp_bufs[nr_free] = (u32)req->buf;
+		else
+			pr_err("diag free %d req 0x%x 0x%x\n", nr_free, (u32)req, (u32)req->buf);
+		nr_free++;
+
+		pxa910_gs_free_req(ep, req);
+		if (allocated)
+			(*allocated)--;
+	}
+}
+
+static int pxa910_gs_alloc_requests(struct usb_ep *ep, struct list_head *head,
+		void (*fn)(struct usb_ep *, struct usb_request *),
+		int *allocated)
+{
+	int			i;
+	struct usb_request	*req;
+	int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE;
+
+	/* Pre-allocate up to QUEUE_SIZE transfers, but if we can't
+	 * do quite that many this time, don't fail ... we just won't
+	 * be as speedy as we might otherwise be.
+	 */
+	memset(tmp_bufs, 0, sizeof(tmp_bufs));
+	for (i = 0; i < n; i++) {
+		req = pxa910_gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
+		if (!req)
+			return list_empty(head) ? -ENOMEM : 0;
+		req->complete = fn;
+		list_add_tail(&req->list, head);
+		if (allocated)
+			(*allocated)++;
+		if (i < QUEUE_SIZE)
+			tmp_bufs[i] = (u32)req->buf;
+	}
+	return 0;
+}
+
+/**
+ * gs_start_io - start USB I/O streams
+ * @dev: encapsulates endpoints to use
+ * Context: holding port_lock; port.tty and port_usb are non-null
+ *
+ * We only start I/O when something is connected to both sides of
+ * this port.  If nothing is listening on the host side, we may
+ * be pointlessly filling up our TX buffers and FIFO.
+ */
+static int pxa910_gs_start_io(struct pxa910_gs_port *port)
+{
+	struct list_head	*head = &port->read_pool;
+	struct usb_ep		*ep = port->port_usb->out;
+	struct pxa910_gserial	*gser = port->port_usb;
+	int			status;
+	unsigned		started;
+
+	/* Allocate RX and TX I/O buffers.  We can't easily do this much
+	 * earlier (with GFP_KERNEL) because the requests are coupled to
+	 * endpoints, as are the packet sizes we'll be using.  Different
+	 * configurations may use different endpoints with a given port;
+	 * and high speed vs full speed changes packet sizes too.
+	 */
+	status = pxa910_gs_alloc_requests(ep, head, pxa910_gs_read_complete,
+		&port->read_allocated);
+	if (status)
+		return status;
+
+	status = pxa910_gs_alloc_requests(gser->in, &port->write_pool,
+			pxa910_gs_write_complete, &port->write_allocated);
+	if (status) {
+		pr_err("%s alloc reqs failed %d\n", __func__, status);
+		pxa910_gs_free_requests(ep, head, &port->read_allocated);
+		return status;
+	}
+	memcpy(&port->malloc_bufs[0], tmp_bufs, sizeof(tmp_bufs));
+
+	/* queue read requests */
+	port->n_read = 0;
+	started = pxa910_gs_start_rx(port);
+
+	/* unblock any pending writes into our circular buffer */
+	if (started) {
+		tty_wakeup(port->port.tty);
+	} else {
+		pxa910_gs_free_requests(ep, head, &port->read_allocated);
+		pxa910_gs_free_requests(gser->in, &port->write_pool,
+			&port->write_allocated);
+		status = -EIO;
+	}
+
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* TTY Driver */
+
+/*
+ * gs_open sets up the link between a gs_port and its associated TTY.
+ * That link is broken *only* by TTY close(), and all driver methods
+ * know that.
+ */
+static int pxa910_gs_open(struct tty_struct *tty, struct file *file)
+{
+	int port_num;
+	struct pxa910_gs_port *port;
+	int status, pxa910_write_buf_size = PXA910_WRITE_BUF_SIZE;
+	char *ttydiag = "ttydiag";
+	char *ttymodem = "ttymodem";
+
+	if (!strcmp(ttymodem, tty->driver->name))
+		port_num = 0;
+
+	if (!strcmp(ttydiag, tty->driver->name)) {
+		pxa910_write_buf_size = PXA910_WRITE_BUF_SIZE * 16; /* tune this value after phase #3*/
+		port_num = 1;
+	}
+
+	if ((port_num < 0) || (port_num >= ((int)n_modem_ports + (int)n_modem_ports)))
+		return -ENXIO;
+
+	do {
+		mutex_lock(&pxa910_ports[port_num].lock);
+		port = pxa910_ports[port_num].port;
+		if (!port)
+			status = -ENODEV;
+		else {
+			spin_lock_irq(&port->port_lock);
+
+			/* already open?  Great. */
+			if (port->port.count) {
+				status = 0;
+				port->port.count++;
+
+			/* currently opening/closing? wait ... */
+			} else if (port->openclose) {
+				status = -EBUSY;
+
+			/* ... else we do the work */
+			} else {
+				status = -EAGAIN;
+				port->openclose = true;
+			}
+			spin_unlock_irq(&port->port_lock);
+		}
+		mutex_unlock(&pxa910_ports[port_num].lock);
+
+		switch (status) {
+		default:
+			/* fully handled */
+			return status;
+		case -EAGAIN:
+			/* must do the work */
+			break;
+		case -EBUSY:
+			/* wait for EAGAIN task to finish */
+			usleep_range(1000, 1500);
+			/* REVISIT could have a waitchannel here, if
+			 * concurrent open performance is important
+			 */
+			break;
+		}
+	} while (status != -EAGAIN);
+
+	/* Do the "real open" */
+	spin_lock_irq(&port->port_lock);
+
+	/* allocate circular buffer on first open */
+	if (port->port_write_buf.buf_buf == NULL) {
+
+		spin_unlock_irq(&port->port_lock);
+		status = pxa910_gs_buf_alloc(&port->port_write_buf,
+						pxa910_write_buf_size);
+		spin_lock_irq(&port->port_lock);
+
+		if (status) {
+			pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n",
+				port->port_num, tty, file);
+			port->openclose = false;
+			goto exit_unlock_port;
+		}
+	}
+
+	/* REVISIT if REMOVED (ports[].port NULL), abort the open
+	 * to let rmmod work faster (but this way isn't wrong).
+	 */
+
+	/* REVISIT maybe wait for "carrier detect" */
+
+	tty->driver_data = port;
+	port->port.tty = tty;
+
+	port->port.count = 1;
+	port->openclose = false;
+
+	/* if connected, start the I/O stream */
+	if (port->port_usb) {
+		struct pxa910_gserial	*gser = port->port_usb;
+
+		pr_debug("gs_open: start ttyGS%d\n", port->port_num);
+		pxa910_gs_start_io(port);
+
+		if (gser->connect)
+			gser->connect(gser);
+	}
+
+	pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
+
+	status = 0;
+
+exit_unlock_port:
+	spin_unlock_irq(&port->port_lock);
+	return status;
+}
+
+static int pxa910_gs_writes_finished(struct pxa910_gs_port *p)
+{
+	int cond;
+
+	/* return true on disconnect or empty buffer */
+	spin_lock_irq(&p->port_lock);
+	cond = (p->port_usb == NULL) ||
+		!pxa910_gs_buf_data_avail(&p->port_write_buf);
+	spin_unlock_irq(&p->port_lock);
+
+	return cond;
+}
+
+static void pxa910_gs_close(struct tty_struct *tty, struct file *file)
+{
+	struct pxa910_gs_port *port = tty->driver_data;
+	struct pxa910_gserial	*gser;
+
+    if (port == NULL) {
+		pr_err("gs_close: port is NULL!\n");
+		return;
+	}
+
+	spin_lock_irq(&port->port_lock);
+
+	if (port->port.count != 1) {
+		if (port->port.count == 0)
+			WARN_ON(1);
+		else
+			--port->port.count;
+		goto exit;
+	}
+
+	pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file);
+
+	/* mark port as closing but in use; we can drop port lock
+	 * and sleep if necessary
+	 */
+	port->openclose = true;
+	port->port.count = 0;
+
+	gser = port->port_usb;
+	if (gser && gser->disconnect)
+		gser->disconnect(gser);
+
+	/* wait for circular write buffer to drain, disconnect, or at
+	 * most PXA910_GS_CLOSE_TIMEOUT seconds; then discard the rest
+	 */
+	if (pxa910_gs_buf_data_avail(&port->port_write_buf) > 0 && gser) {
+		spin_unlock_irq(&port->port_lock);
+		wait_event_interruptible_timeout(port->drain_wait,
+					pxa910_gs_writes_finished(port),
+					PXA910_GS_CLOSE_TIMEOUT * HZ);
+		spin_lock_irq(&port->port_lock);
+		gser = port->port_usb;
+	}
+
+	/* Iff we're disconnected, there can be no I/O in flight so it's
+	 * ok to free the circular buffer; else just scrub it.  And don't
+	 * let the push tasklet fire again until we're re-opened.
+	 */
+	if (gser == NULL)
+		pxa910_gs_buf_free(&port->port_write_buf);
+	else
+		pxa910_gs_buf_clear(&port->port_write_buf);
+
+	tty->driver_data = NULL;
+	port->port.tty = NULL;
+
+	port->openclose = false;
+
+	pr_debug("gs_close: ttyGS%d (%p,%p) done!\n",
+			port->port_num, tty, file);
+
+	wake_up_interruptible(&port->close_wait);
+exit:
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int
+pxa910_gs_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		status;
+
+	if (port == NULL) {
+		pr_err("pxa910_gs_write: port is NULL!\n");
+		return -EPERM;
+	}
+
+	pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n",
+			port->port_num, tty, count);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (count)
+		count = pxa910_gs_buf_put(&port->port_write_buf, buf, count);
+	/* treat count == 0 as flush_chars() */
+	if (port->port_usb)
+		status = pxa910_gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	if (!count) {
+		pr_vdebug("%s: buffer full!\n", __func__);
+		return -EAGAIN;
+	}
+	return count;
+}
+
+static int pxa910_gs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		status;
+
+	pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %p\n",
+		port->port_num, tty, ch, __builtin_return_address(0));
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	status = pxa910_gs_buf_put(&port->port_write_buf, &ch, 1);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return status;
+}
+
+static void pxa910_gs_flush_chars(struct tty_struct *tty)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+
+	if (unlikely(!port)) {
+		pr_err("%s: port closed\n", __func__);
+		return;
+	}
+	pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		pxa910_gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int pxa910_gs_write_room(struct tty_struct *tty)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		room = 0;
+
+	if (unlikely(port == NULL))
+		return 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		room = pxa910_gs_buf_space_avail(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_write_room: (%d,%p) room=%d\n",
+		port->port_num, tty, room);
+
+	return room;
+}
+
+static int pxa910_gs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		chars = 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	chars = pxa910_gs_buf_data_avail(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n",
+		port->port_num, tty, chars);
+
+	return chars;
+}
+
+/* undo side effects of setting TTY_THROTTLED */
+static void pxa910_gs_unthrottle(struct tty_struct *tty)
+{
+	struct pxa910_gs_port		*port = tty->driver_data;
+	unsigned long		flags;
+	if (!port)
+		return;
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb) {
+		/* Kickstart read queue processing.  We don't do xon/xoff,
+		 * rts/cts, or other handshaking with the host, but if the
+		 * read queue backs up enough we'll be NAKing OUT packets.
+		 */
+		tasklet_schedule(&port->push);
+		pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num);
+	}
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int pxa910_gs_break_ctl(struct tty_struct *tty, int duration)
+{
+	struct pxa910_gs_port	*port = tty->driver_data;
+	int		status = 0;
+	struct pxa910_gserial	*gser;
+
+	pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d)\n",
+			port->port_num, duration);
+
+	spin_lock_irq(&port->port_lock);
+	gser = port->port_usb;
+	if (gser && gser->send_break)
+		status = gser->send_break(gser, duration);
+	spin_unlock_irq(&port->port_lock);
+
+	return status;
+}
+
+static const struct tty_operations pxa910_gs_tty_ops = {
+	.open =			pxa910_gs_open,
+	.close =		pxa910_gs_close,
+	.write =		pxa910_gs_write,
+	.put_char =		pxa910_gs_put_char,
+	.flush_chars =		pxa910_gs_flush_chars,
+	.write_room =		pxa910_gs_write_room,
+	.chars_in_buffer =	pxa910_gs_chars_in_buffer,
+	.unthrottle =		pxa910_gs_unthrottle,
+	.break_ctl =		pxa910_gs_break_ctl,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct tty_driver *gs_modem_tty_driver;
+static struct tty_driver *gs_diag_tty_driver;
+
+#if 0
+static int
+pxa910_gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
+{
+	struct pxa910_gs_port	*port;
+
+	port = kzalloc(sizeof(struct pxa910_gs_port), GFP_KERNEL);
+	if (port == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&port->port_lock);
+	init_waitqueue_head(&port->close_wait);
+	init_waitqueue_head(&port->drain_wait);
+
+	tasklet_init(&port->push, gs_rx_push, (unsigned long) port);
+
+	INIT_LIST_HEAD(&port->read_pool);
+	INIT_LIST_HEAD(&port->read_queue);
+	INIT_LIST_HEAD(&port->write_pool);
+
+	port->port_num = port_num;
+	port->port_line_coding = *coding;
+
+	pxa910_ports[port_num].port = port;
+
+	return 0;
+}
+
+/**
+ * gserial_setup - initialize TTY driver for one or more ports
+ * @g: gadget to associate with these ports
+ * @count: how many ports to support
+ * Context: may sleep
+ *
+ * The TTY stack needs to know in advance how many devices it should
+ * plan to manage.  Use this call to set up the ports you will be
+ * exporting through USB.  Later, connect them to functions based
+ * on what configuration is activated by the USB host; and disconnect
+ * them as appropriate.
+ *
+ * An example would be a two-configuration device in which both
+ * configurations expose port 0, but through different functions.
+ * One configuration could even expose port 1 while the other
+ * one doesn't.
+ *
+ * Returns negative errno or zero.
+ */
+int pxa910_gserial_setup(struct usb_gadget *g, unsigned count)
+{
+	unsigned			i;
+	struct usb_cdc_line_coding	coding;
+	int				status;
+
+	if (count == 0 || count > N_PORTS)
+		return -EINVAL;
+
+	gs_tty_driver = alloc_tty_driver(count);
+	if (!gs_tty_driver)
+		return -ENOMEM;
+
+	gs_tty_driver->driver_name = "g_serial";
+	gs_tty_driver->name = PREFIX;
+	/* uses dynamically assigned dev_t values */
+
+	gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	gs_tty_driver->init_termios = tty_std_termios;
+
+	/* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+	 * MS-Windows.  Otherwise, most of these flags shouldn't affect
+	 * anything unless we were to actually hook up to a serial line.
+	 */
+	gs_tty_driver->init_termios.c_cflag =
+			B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	gs_tty_driver->init_termios.c_ispeed = 9600;
+	gs_tty_driver->init_termios.c_ospeed = 9600;
+
+	coding.dwDTERate = cpu_to_le32(9600);
+	coding.bCharFormat = 8;
+	coding.bParityType = USB_CDC_NO_PARITY;
+	coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+	tty_set_operations(gs_tty_driver, &gs_tty_ops);
+
+	/* make devices be openable */
+	for (i = 0; i < count; i++) {
+		mutex_init(&ports[i].lock);
+		status = gs_port_alloc(i, &coding);
+		if (status) {
+			count = i;
+			goto fail;
+		}
+	}
+	n_ports = count;
+
+	/* export the driver ... */
+	status = tty_register_driver(gs_tty_driver);
+	if (status) {
+		pr_err("%s: cannot register, err %d\n",
+				__func__, status);
+		goto fail;
+	}
+
+	/* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
+	for (i = 0; i < count; i++) {
+		struct device	*tty_dev;
+
+		tty_dev = tty_register_device(gs_tty_driver, i, &g->dev);
+		if (IS_ERR(tty_dev))
+			pr_warning("%s: no classdev for port %d, err %ld\n",
+				__func__, i, PTR_ERR(tty_dev));
+	}
+
+	pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+			count, (count == 1) ? "" : "s");
+
+	return status;
+fail:
+	while (count--)
+		kfree(ports[count].port);
+	put_tty_driver(gs_tty_driver);
+	gs_tty_driver = NULL;
+	return status;
+}
+#endif
+
+static int pxa910_gs_closed(struct pxa910_gs_port *port)
+{
+	int cond;
+
+	spin_lock_irq(&port->port_lock);
+	cond = (port->port.count == 0) && !port->openclose;
+	spin_unlock_irq(&port->port_lock);
+	return cond;
+}
+
+#if 0
+/**
+ * gserial_cleanup - remove TTY-over-USB driver and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gserial_setup().
+ * Accordingly, it may need to wait until some open /dev/ files have
+ * closed.
+ *
+ * The caller must have issued @gserial_disconnect() for any ports
+ * that had previously been connected, so that there is never any
+ * I/O pending when it's called.
+ */
+void pxa910_gserial_cleanup(void)
+{
+	unsigned	i;
+	struct pxa910_gs_port	*port;
+
+	if (!gs_tty_driver)
+		return;
+
+	/* start sysfs and /dev/ttyGS* node removal */
+	for (i = 0; i < n_ports; i++)
+		tty_unregister_device(gs_tty_driver, i);
+
+	for (i = 0; i < n_ports; i++) {
+		/* prevent new opens */
+		mutex_lock(&ports[i].lock);
+		port = ports[i].port;
+		ports[i].port = NULL;
+		mutex_unlock(&ports[i].lock);
+
+		tasklet_kill(&port->push);
+
+		/* wait for old opens to finish */
+		wait_event(port->close_wait, gs_closed(port));
+
+		WARN_ON(port->port_usb != NULL);
+
+		kfree(port);
+	}
+	n_ports = 0;
+
+	tty_unregister_driver(pxa910_gs_tty_driver);
+	put_tty_driver(pxa910_gs_tty_driver);
+	pxa910_gs_tty_driver = NULL;
+
+	pr_debug("%s: cleaned up ttyGS* support\n", __func__);
+}
+#endif
+
+/**
+ * gserial_connect - notify TTY I/O glue that USB link is active
+ * @gser: the function, set up with endpoints and descriptors
+ * @port_num: which port is active
+ * Context: any (usually from irq)
+ *
+ * This is called activate endpoints and let the TTY layer know that
+ * the connection is active ... not unlike "carrier detect".  It won't
+ * necessarily start I/O queues; unless the TTY is held open by any
+ * task, there would be no point.  However, the endpoints will be
+ * activated so the USB host can perform I/O, subject to basic USB
+ * hardware flow control.
+ *
+ * Caller needs to have set up the endpoints and USB function in @dev
+ * before calling this, as well as the appropriate (speed-specific)
+ * endpoint descriptors, and also have set up the TTY driver by calling
+ * @gserial_setup().
+ *
+ * Returns negative errno or zero.
+ * On success, ep->driver_data will be overwritten.
+ */
+int pxa910_gserial_connect(struct pxa910_gserial *gser, u8 port_num)
+{
+	struct pxa910_gs_port	*port;
+	unsigned long	flags;
+	int		status;
+
+	if (port_num >= (n_modem_ports + n_diag_ports))
+		return -ENXIO;
+
+	if (!gs_modem_tty_driver || !gs_diag_tty_driver)
+		return -ENXIO;
+
+	/* we "know" gserial_cleanup() hasn't been called */
+	port = pxa910_ports[port_num].port;
+
+	/* activate the endpoints */
+	status = usb_ep_enable(gser->in);
+	if (status < 0)
+		return status;
+	gser->in->driver_data = port;
+
+	status = usb_ep_enable(gser->out);
+	if (status < 0)
+		goto fail_out;
+	gser->out->driver_data = port;
+
+	/* then tell the tty glue that I/O can work */
+	spin_lock_irqsave(&port->port_lock, flags);
+	gser->ioport = port;
+	port->port_usb = gser;
+
+	/* REVISIT unclear how best to handle this state...
+	 * we don't really couple it with the Linux TTY.
+	 */
+	gser->port_line_coding = port->port_line_coding;
+
+	/* REVISIT if waiting on "carrier detect", signal. */
+
+	/* if it's already open, start I/O ... and notify the serial
+	 * protocol about open/close status (connect/disconnect).
+	 */
+	if (port->port.count) {
+		pr_debug("gserial_connect: start ttyGS%d\n", port->port_num);
+		pxa910_gs_start_io(port);
+		if (gser->connect)
+			gser->connect(gser);
+	} else {
+		if (gser->disconnect)
+			gser->disconnect(gser);
+	}
+
+	spin_unlock_irqrestore(&port->port_lock, flags);
+	diag_serial_ready = true;
+	return status;
+
+fail_out:
+	usb_ep_disable(gser->in);
+	gser->in->driver_data = NULL;
+	return status;
+}
+
+/**
+ * gserial_disconnect - notify TTY I/O glue that USB link is inactive
+ * @gser: the function, on which gserial_connect() was called
+ * Context: any (usually from irq)
+ *
+ * This is called to deactivate endpoints and let the TTY layer know
+ * that the connection went inactive ... not unlike "hangup".
+ *
+ * On return, the state is as if gserial_connect() had never been called;
+ * there is no active USB I/O on these endpoints.
+ */
+void pxa910_gserial_disconnect(struct pxa910_gserial *gser)
+{
+	struct pxa910_gs_port	*port = gser->ioport;
+	unsigned long	flags;
+
+	if (!port)
+		return;
+
+	/* tell the TTY glue not to do I/O here any more */
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	/* REVISIT as above: how best to track this? */
+	port->port_line_coding = gser->port_line_coding;
+
+	port->port_usb = NULL;
+	wake_up_interruptible(&gser->port_send);
+
+	gser->ioport = NULL;
+	if (port->port.count > 0 || port->openclose) {
+		wake_up_interruptible(&port->drain_wait);
+		if (port->port.tty)
+			tty_hangup(port->port.tty);
+	}
+	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;
+
+	/* finally, free any unused/unusable I/O buffers */
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port.count == 0 && !port->openclose)
+		pxa910_gs_buf_free(&port->port_write_buf);
+	pxa910_gs_free_requests(gser->out, &port->read_pool, NULL);
+	pxa910_gs_free_requests(gser->out, &port->read_queue, NULL);
+	pxa910_gs_free_requests(gser->in, &port->write_pool, NULL);
+	memcpy(&port->free_bufs[0], tmp_bufs, sizeof(tmp_bufs));
+
+	port->read_allocated = port->read_started =
+		port->write_allocated = port->write_started = 0;
+	diag_serial_ready = false;
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static __init int diag_tx_buffer_prealloc(void)
+{
+	/*
+	* prealloc diag tx buffer is RDUP=1 is enable in cmdline
+	*/
+	if (rdup_mode_is_enabled()) {
+		g_pre_diag_tx_buf = kmalloc((PXA910_WRITE_BUF_SIZE * 16), GFP_KERNEL);
+		pr_info("diag tx prebuff: 0x%x\n", (u32)g_pre_diag_tx_buf);
+	} else {
+		g_pre_diag_tx_buf = NULL;
+	}
+
+	return 0;
+}
+core_initcall(diag_tx_buffer_prealloc);
diff --git a/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.h b/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.h
new file mode 100644
index 0000000..a9dc399
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/pxa910_u_serial.h
@@ -0,0 +1,75 @@
+/*
+ * u_serial.h - interface to USB gadget "serial port"/TTY utilities
+ *
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifndef __PXA910_U_SERIAL_H
+#define __PXA910_U_SERIAL_H
+
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+/*
+ * One non-multiplexed "serial" I/O port ... there can be several of these
+ * on any given USB peripheral device, if it provides enough endpoints.
+ *
+ * The "u_serial" utility component exists to do one thing:  manage TTY
+ * style I/O using the USB peripheral endpoints listed here, including
+ * hookups to sysfs and /dev for each logical "tty" device.
+ *
+ * REVISIT at least ACM could support tiocmget() if needed.
+ *
+ * REVISIT someday, allow multiplexing several TTYs over these endpoints.
+ */
+struct pxa910_gserial {
+	struct usb_function		func;
+
+	/* port is managed by gserial_{connect,disconnect} */
+	struct pxa910_gs_port			*ioport;
+
+	struct usb_ep			*in;
+	struct usb_ep			*out;
+
+	/* REVISIT avoid this CDC-ACM support harder ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 9600-8-N-1 etc */
+	wait_queue_head_t port_send;
+
+	/* notification callbacks */
+	void (*connect)(struct pxa910_gserial *p);
+	void (*disconnect)(struct pxa910_gserial *p);
+	int (*send_break)(struct pxa910_gserial *p, int duration);
+};
+
+/* utilities to allocate/free request and buffer */
+struct usb_request *
+pxa910_gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
+void pxa910_gs_free_req(struct usb_ep *, struct usb_request *req);
+void pxa910_gs_tx_worker(struct work_struct *work);
+/* port setup/teardown is handled by gadget driver */
+int pxa910_gserial_setup(struct usb_gadget *g, unsigned n_ports);
+void pxa910_gserial_cleanup(void);
+
+/* connect/disconnect is handled by individual functions */
+int pxa910_gserial_connect(struct pxa910_gserial *, u8 port_num);
+void pxa910_gserial_disconnect(struct pxa910_gserial *);
+
+/* functions are bound to configurations by a config or gadget driver */
+int pxa910_acm_bind_config(struct usb_configuration *c, u8 port_num);
+int pxa910_diag_bind_config(struct usb_configuration *c, u8 port_num);
+
+static inline int work_data_is_clear(struct work_struct *work)
+{
+	unsigned long data;
+	data = atomic_long_read(&work->data);
+	if (data == (unsigned long)WORK_STRUCT_NO_POOL)
+		return 1;
+	return 0;
+}
+
+#endif /* __PXA910_U_SERIAL_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/rndis.c b/marvell/linux/drivers/usb/gadget/function/rndis.c
new file mode 100644
index 0000000..1ebcd3b
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/rndis.c
@@ -0,0 +1,1306 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RNDIS MSG parser
+ *
+ * Authors:	Benedikt Spranger, Pengutronix
+ *		Robert Schwebel, Pengutronix
+ *
+ *		This software was originally developed in conformance with
+ *		Microsoft's Remote NDIS Specification License Agreement.
+ *
+ * 03/12/2004 Kai-Uwe Bloem <linux-development@auerswald.de>
+ *		Fixed message length bug in init_response
+ *
+ * 03/25/2004 Kai-Uwe Bloem <linux-development@auerswald.de>
+ *		Fixed rndis_rm_hdr length bug.
+ *
+ * Copyright (C) 2004 by David Brownell
+ *		updates to merge with Linux 2.6, better match RNDIS spec
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/netdevice.h>
+
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+
+#undef	VERBOSE_DEBUG
+
+#include "rndis.h"
+
+
+/* The driver for your USB chip needs to support ep0 OUT to work with
+ * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional).
+ *
+ * Windows hosts need an INF file like Documentation/usb/linux.inf
+ * and will be happier if you provide the host_addr module parameter.
+ */
+
+#if 0
+static int rndis_debug = 0;
+module_param (rndis_debug, int, 0);
+MODULE_PARM_DESC (rndis_debug, "enable debugging");
+#else
+#define rndis_debug		0
+#endif
+
+#define RNDIS_MAX_CONFIGS	1
+
+int rndis_ul_max_pkt_per_xfer_rcvd;
+module_param(rndis_ul_max_pkt_per_xfer_rcvd, int, S_IRUGO);
+MODULE_PARM_DESC(rndis_ul_max_pkt_per_xfer_rcvd,
+	"Max num of REMOTE_NDIS_PACKET_MSGs received in a single transfer");
+
+int rndis_ul_max_xfer_size_rcvd;
+module_param(rndis_ul_max_xfer_size_rcvd, int, S_IRUGO);
+MODULE_PARM_DESC(rndis_ul_max_xfer_size_rcvd,
+	"Max size of bus transfer received");
+
+
+static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS];
+
+/* Driver Version */
+static const __le32 rndis_driver_version = cpu_to_le32(1);
+
+/* Function Prototypes */
+static rndis_resp_t *rndis_add_response(int configNr, u32 length);
+
+
+/* supported OIDs */
+static const u32 oid_supported_list[] =
+{
+	/* the general stuff */
+	RNDIS_OID_GEN_SUPPORTED_LIST,
+	RNDIS_OID_GEN_HARDWARE_STATUS,
+	RNDIS_OID_GEN_MEDIA_SUPPORTED,
+	RNDIS_OID_GEN_MEDIA_IN_USE,
+	RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE,
+	RNDIS_OID_GEN_LINK_SPEED,
+	RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE,
+	RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE,
+	RNDIS_OID_GEN_VENDOR_ID,
+	RNDIS_OID_GEN_VENDOR_DESCRIPTION,
+	RNDIS_OID_GEN_VENDOR_DRIVER_VERSION,
+	RNDIS_OID_GEN_CURRENT_PACKET_FILTER,
+	RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE,
+	RNDIS_OID_GEN_MEDIA_CONNECT_STATUS,
+	RNDIS_OID_GEN_PHYSICAL_MEDIUM,
+
+	/* the statistical stuff */
+	RNDIS_OID_GEN_XMIT_OK,
+	RNDIS_OID_GEN_RCV_OK,
+	RNDIS_OID_GEN_XMIT_ERROR,
+	RNDIS_OID_GEN_RCV_ERROR,
+	RNDIS_OID_GEN_RCV_NO_BUFFER,
+#ifdef	RNDIS_OPTIONAL_STATS
+	RNDIS_OID_GEN_DIRECTED_BYTES_XMIT,
+	RNDIS_OID_GEN_DIRECTED_FRAMES_XMIT,
+	RNDIS_OID_GEN_MULTICAST_BYTES_XMIT,
+	RNDIS_OID_GEN_MULTICAST_FRAMES_XMIT,
+	RNDIS_OID_GEN_BROADCAST_BYTES_XMIT,
+	RNDIS_OID_GEN_BROADCAST_FRAMES_XMIT,
+	RNDIS_OID_GEN_DIRECTED_BYTES_RCV,
+	RNDIS_OID_GEN_DIRECTED_FRAMES_RCV,
+	RNDIS_OID_GEN_MULTICAST_BYTES_RCV,
+	RNDIS_OID_GEN_MULTICAST_FRAMES_RCV,
+	RNDIS_OID_GEN_BROADCAST_BYTES_RCV,
+	RNDIS_OID_GEN_BROADCAST_FRAMES_RCV,
+	RNDIS_OID_GEN_RCV_CRC_ERROR,
+	RNDIS_OID_GEN_TRANSMIT_QUEUE_LENGTH,
+#endif	/* RNDIS_OPTIONAL_STATS */
+
+	/* mandatory 802.3 */
+	/* the general stuff */
+	RNDIS_OID_802_3_PERMANENT_ADDRESS,
+	RNDIS_OID_802_3_CURRENT_ADDRESS,
+	RNDIS_OID_802_3_MULTICAST_LIST,
+	RNDIS_OID_802_3_MAC_OPTIONS,
+	RNDIS_OID_802_3_MAXIMUM_LIST_SIZE,
+
+	/* the statistical stuff */
+	RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT,
+	RNDIS_OID_802_3_XMIT_ONE_COLLISION,
+	RNDIS_OID_802_3_XMIT_MORE_COLLISIONS,
+#ifdef	RNDIS_OPTIONAL_STATS
+	RNDIS_OID_802_3_XMIT_DEFERRED,
+	RNDIS_OID_802_3_XMIT_MAX_COLLISIONS,
+	RNDIS_OID_802_3_RCV_OVERRUN,
+	RNDIS_OID_802_3_XMIT_UNDERRUN,
+	RNDIS_OID_802_3_XMIT_HEARTBEAT_FAILURE,
+	RNDIS_OID_802_3_XMIT_TIMES_CRS_LOST,
+	RNDIS_OID_802_3_XMIT_LATE_COLLISIONS,
+#endif	/* RNDIS_OPTIONAL_STATS */
+
+#ifdef	RNDIS_PM
+	/* PM and wakeup are "mandatory" for USB, but the RNDIS specs
+	 * don't say what they mean ... and the NDIS specs are often
+	 * confusing and/or ambiguous in this context.  (That is, more
+	 * so than their specs for the other OIDs.)
+	 *
+	 * FIXME someone who knows what these should do, please
+	 * implement them!
+	 */
+
+	/* power management */
+	OID_PNP_CAPABILITIES,
+	OID_PNP_QUERY_POWER,
+	OID_PNP_SET_POWER,
+
+#ifdef	RNDIS_WAKEUP
+	/* wake up host */
+	OID_PNP_ENABLE_WAKE_UP,
+	OID_PNP_ADD_WAKE_UP_PATTERN,
+	OID_PNP_REMOVE_WAKE_UP_PATTERN,
+#endif	/* RNDIS_WAKEUP */
+#endif	/* RNDIS_PM */
+};
+
+
+/* NDIS Functions */
+static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf,
+			       unsigned buf_len, rndis_resp_t *r)
+{
+	int retval = -ENOTSUPP;
+	u32 length = 4;	/* usually */
+	__le32 *outbuf;
+	int i, count;
+	rndis_query_cmplt_type *resp;
+	struct net_device *net;
+	struct rtnl_link_stats64 temp;
+	const struct rtnl_link_stats64 *stats;
+
+	if (!r) return -ENOMEM;
+	resp = (rndis_query_cmplt_type *)r->buf;
+
+	if (!resp) return -ENOMEM;
+
+	if (buf_len && rndis_debug > 1) {
+		pr_debug("query OID %08x value, len %d:\n", OID, buf_len);
+		for (i = 0; i < buf_len; i += 16) {
+			pr_debug("%03d: %08x %08x %08x %08x\n", i,
+				get_unaligned_le32(&buf[i]),
+				get_unaligned_le32(&buf[i + 4]),
+				get_unaligned_le32(&buf[i + 8]),
+				get_unaligned_le32(&buf[i + 12]));
+		}
+	}
+
+	/* response goes here, right after the header */
+	outbuf = (__le32 *)&resp[1];
+	resp->InformationBufferOffset = cpu_to_le32(16);
+
+	net = rndis_per_dev_params[configNr].dev;
+	stats = dev_get_stats(net, &temp);
+
+	switch (OID) {
+
+	/* general oids (table 4-1) */
+
+	/* mandatory */
+	case RNDIS_OID_GEN_SUPPORTED_LIST:
+		pr_debug("%s: RNDIS_OID_GEN_SUPPORTED_LIST\n", __func__);
+		length = sizeof(oid_supported_list);
+		count  = length / sizeof(u32);
+		for (i = 0; i < count; i++)
+			outbuf[i] = cpu_to_le32(oid_supported_list[i]);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_HARDWARE_STATUS:
+		pr_debug("%s: RNDIS_OID_GEN_HARDWARE_STATUS\n", __func__);
+		/* Bogus question!
+		 * Hardware must be ready to receive high level protocols.
+		 * BTW:
+		 * reddite ergo quae sunt Caesaris Caesari
+		 * et quae sunt Dei Deo!
+		 */
+		*outbuf = cpu_to_le32(0);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_MEDIA_SUPPORTED:
+		pr_debug("%s: RNDIS_OID_GEN_MEDIA_SUPPORTED\n", __func__);
+		*outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_MEDIA_IN_USE:
+		pr_debug("%s: RNDIS_OID_GEN_MEDIA_IN_USE\n", __func__);
+		/* one medium, one transport... (maybe you do it better) */
+		*outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE:
+		pr_debug("%s: RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__);
+		if (rndis_per_dev_params[configNr].dev) {
+			*outbuf = cpu_to_le32(
+				rndis_per_dev_params[configNr].dev->mtu);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_LINK_SPEED:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_LINK_SPEED\n", __func__);
+		/*
+		* the rndis spec doesn't require to respond zero speed if
+		* devices is disconnected.
+		* 
+		* keep the original code for issue analysis
+		*/
+#if 0
+		if (rndis_per_dev_params[configNr].media_state
+				== RNDIS_MEDIA_STATE_DISCONNECTED)
+			*outbuf = cpu_to_le32(0);
+		else
+#endif
+			*outbuf = cpu_to_le32(
+				rndis_per_dev_params[configNr].speed);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE:
+		pr_debug("%s: RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__);
+		if (rndis_per_dev_params[configNr].dev) {
+			*outbuf = cpu_to_le32(
+				rndis_per_dev_params[configNr].dev->mtu);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE:
+		pr_debug("%s: RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__);
+		if (rndis_per_dev_params[configNr].dev) {
+			*outbuf = cpu_to_le32(
+				rndis_per_dev_params[configNr].dev->mtu);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_VENDOR_ID:
+		pr_debug("%s: RNDIS_OID_GEN_VENDOR_ID\n", __func__);
+		*outbuf = cpu_to_le32(
+			rndis_per_dev_params[configNr].vendorID);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_VENDOR_DESCRIPTION:
+		pr_debug("%s: RNDIS_OID_GEN_VENDOR_DESCRIPTION\n", __func__);
+		if (rndis_per_dev_params[configNr].vendorDescr) {
+			length = strlen(rndis_per_dev_params[configNr].
+					vendorDescr);
+			memcpy(outbuf,
+				rndis_per_dev_params[configNr].vendorDescr,
+				length);
+		} else {
+			outbuf[0] = 0;
+		}
+		retval = 0;
+		break;
+
+	case RNDIS_OID_GEN_VENDOR_DRIVER_VERSION:
+		pr_debug("%s: RNDIS_OID_GEN_VENDOR_DRIVER_VERSION\n", __func__);
+		/* Created as LE */
+		*outbuf = rndis_driver_version;
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_CURRENT_PACKET_FILTER:
+		pr_debug("%s: RNDIS_OID_GEN_CURRENT_PACKET_FILTER\n", __func__);
+		*outbuf = cpu_to_le32(*rndis_per_dev_params[configNr].filter);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE:
+		pr_debug("%s: RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__);
+		*outbuf = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_MEDIA_CONNECT_STATUS:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_MEDIA_CONNECT_STATUS\n", __func__);
+		*outbuf = cpu_to_le32(rndis_per_dev_params[configNr]
+						.media_state);
+		retval = 0;
+		break;
+
+	case RNDIS_OID_GEN_PHYSICAL_MEDIUM:
+		pr_debug("%s: RNDIS_OID_GEN_PHYSICAL_MEDIUM\n", __func__);
+		*outbuf = cpu_to_le32(0);
+		retval = 0;
+		break;
+
+	/* The RNDIS specification is incomplete/wrong.   Some versions
+	 * of MS-Windows expect OIDs that aren't specified there.  Other
+	 * versions emit undefined RNDIS messages. DOCUMENT ALL THESE!
+	 */
+	case RNDIS_OID_GEN_MAC_OPTIONS:		/* from WinME */
+		pr_debug("%s: RNDIS_OID_GEN_MAC_OPTIONS\n", __func__);
+		*outbuf = cpu_to_le32(
+			  RNDIS_MAC_OPTION_RECEIVE_SERIALIZED
+			| RNDIS_MAC_OPTION_FULL_DUPLEX);
+		retval = 0;
+		break;
+
+	/* statistics OIDs (table 4-2) */
+
+	/* mandatory */
+	case RNDIS_OID_GEN_XMIT_OK:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_XMIT_OK\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->tx_packets
+				- stats->tx_errors - stats->tx_dropped);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_RCV_OK:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_RCV_OK\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->rx_packets
+				- stats->rx_errors - stats->rx_dropped);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_XMIT_ERROR:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_XMIT_ERROR\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->tx_errors);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_RCV_ERROR:
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_OID_GEN_RCV_ERROR\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->rx_errors);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_GEN_RCV_NO_BUFFER:
+		pr_debug("%s: RNDIS_OID_GEN_RCV_NO_BUFFER\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->rx_dropped);
+			retval = 0;
+		}
+		break;
+
+	/* ieee802.3 OIDs (table 4-3) */
+
+	/* mandatory */
+	case RNDIS_OID_802_3_PERMANENT_ADDRESS:
+		pr_debug("%s: RNDIS_OID_802_3_PERMANENT_ADDRESS\n", __func__);
+		if (rndis_per_dev_params[configNr].dev) {
+			length = ETH_ALEN;
+			memcpy(outbuf,
+				rndis_per_dev_params[configNr].host_mac,
+				length);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_802_3_CURRENT_ADDRESS:
+		pr_debug("%s: RNDIS_OID_802_3_CURRENT_ADDRESS\n", __func__);
+		if (rndis_per_dev_params[configNr].dev) {
+			length = ETH_ALEN;
+			memcpy(outbuf,
+				rndis_per_dev_params [configNr].host_mac,
+				length);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_802_3_MULTICAST_LIST:
+		pr_debug("%s: RNDIS_OID_802_3_MULTICAST_LIST\n", __func__);
+		/* Multicast base address only */
+		*outbuf = cpu_to_le32(0xE0000000);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_802_3_MAXIMUM_LIST_SIZE:
+		pr_debug("%s: RNDIS_OID_802_3_MAXIMUM_LIST_SIZE\n", __func__);
+		/* Multicast base address only */
+		*outbuf = cpu_to_le32(1);
+		retval = 0;
+		break;
+
+	case RNDIS_OID_802_3_MAC_OPTIONS:
+		pr_debug("%s: RNDIS_OID_802_3_MAC_OPTIONS\n", __func__);
+		*outbuf = cpu_to_le32(0);
+		retval = 0;
+		break;
+
+	/* ieee802.3 statistics OIDs (table 4-4) */
+
+	/* mandatory */
+	case RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT:
+		pr_debug("%s: RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__);
+		if (stats) {
+			*outbuf = cpu_to_le32(stats->rx_frame_errors);
+			retval = 0;
+		}
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_802_3_XMIT_ONE_COLLISION:
+		pr_debug("%s: RNDIS_OID_802_3_XMIT_ONE_COLLISION\n", __func__);
+		*outbuf = cpu_to_le32(0);
+		retval = 0;
+		break;
+
+	/* mandatory */
+	case RNDIS_OID_802_3_XMIT_MORE_COLLISIONS:
+		pr_debug("%s: RNDIS_OID_802_3_XMIT_MORE_COLLISIONS\n", __func__);
+		*outbuf = cpu_to_le32(0);
+		retval = 0;
+		break;
+
+	default:
+		pr_warning("%s: query unknown OID 0x%08X\n",
+			 __func__, OID);
+	}
+	if (retval < 0)
+		length = 0;
+
+	resp->InformationBufferLength = cpu_to_le32(length);
+	r->length = length + sizeof(*resp);
+	resp->MessageLength = cpu_to_le32(r->length);
+	return retval;
+}
+
+static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len,
+			     rndis_resp_t *r)
+{
+	rndis_set_cmplt_type *resp;
+	int i, retval = -ENOTSUPP;
+	struct rndis_params *params;
+
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_set_cmplt_type *)r->buf;
+	if (!resp)
+		return -ENOMEM;
+
+	if (buf_len && rndis_debug > 1) {
+		pr_debug("set OID %08x value, len %d:\n", OID, buf_len);
+		for (i = 0; i < buf_len; i += 16) {
+			pr_debug("%03d: %08x %08x %08x %08x\n", i,
+				get_unaligned_le32(&buf[i]),
+				get_unaligned_le32(&buf[i + 4]),
+				get_unaligned_le32(&buf[i + 8]),
+				get_unaligned_le32(&buf[i + 12]));
+		}
+	}
+
+	params = &rndis_per_dev_params[configNr];
+	switch (OID) {
+	case RNDIS_OID_GEN_CURRENT_PACKET_FILTER:
+
+		/* these NDIS_PACKET_TYPE_* bitflags are shared with
+		 * cdc_filter; it's not RNDIS-specific
+		 * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in:
+		 *	PROMISCUOUS, DIRECTED,
+		 *	MULTICAST, ALL_MULTICAST, BROADCAST
+		 */
+		*params->filter = (u16)get_unaligned_le32(buf);
+		pr_debug("%s: RNDIS_OID_GEN_CURRENT_PACKET_FILTER %08x\n",
+			__func__, *params->filter);
+
+		/* this call has a significant side effect:  it's
+		 * what makes the packet flow start and stop, like
+		 * activating the CDC Ethernet altsetting.
+		 */
+		retval = 0;
+		if (*params->filter) {
+			params->state = RNDIS_DATA_INITIALIZED;
+			netif_carrier_on(params->dev);
+			if (netif_running(params->dev))
+				netif_wake_queue(params->dev);
+		} else {
+			params->state = RNDIS_INITIALIZED;
+			netif_carrier_off(params->dev);
+			netif_stop_queue(params->dev);
+		}
+		break;
+
+	case RNDIS_OID_802_3_MULTICAST_LIST:
+		/* I think we can ignore this */
+		pr_debug("%s: RNDIS_OID_802_3_MULTICAST_LIST\n", __func__);
+		retval = 0;
+		break;
+
+	default:
+		pr_warning("%s: set unknown OID 0x%08X, size %d\n",
+			 __func__, OID, buf_len);
+	}
+
+	return retval;
+}
+
+/*
+ * Response Functions
+ */
+
+static int rndis_init_response(int configNr, rndis_init_msg_type *buf)
+{
+	rndis_init_cmplt_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+
+	if (!params->dev)
+		return -ENOTSUPP;
+
+	r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_init_cmplt_type *)r->buf;
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_INIT_C);
+	resp->MessageLength = cpu_to_le32(52);
+	resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+	resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+	resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION);
+	resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION);
+	resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS);
+	resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3);
+	resp->MaxPacketsPerTransfer = cpu_to_le32(params->max_pkt_per_xfer);
+	resp->MaxTransferSize = cpu_to_le32(params->max_pkt_per_xfer *
+		(params->dev->mtu
+		+ sizeof(struct ethhdr)
+		+ sizeof(struct rndis_packet_msg_type)
+		+ 22));
+	resp->PacketAlignmentFactor = cpu_to_le32(params->pkt_alignment_factor);
+	resp->AFListOffset = cpu_to_le32(0);
+	resp->AFListSize = cpu_to_le32(0);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+static int rndis_query_response(int configNr, rndis_query_msg_type *buf)
+{
+	rndis_query_cmplt_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+
+	/* pr_debug("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID)); */
+	if (!params->dev)
+		return -ENOTSUPP;
+
+	/*
+	 * we need more memory:
+	 * gen_ndis_query_resp expects enough space for
+	 * rndis_query_cmplt_type followed by data.
+	 * oid_supported_list is the largest data reply
+	 */
+	r = rndis_add_response(configNr,
+		sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_query_cmplt_type *)r->buf;
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_QUERY_C);
+	resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+
+	if (gen_ndis_query_resp(configNr, le32_to_cpu(buf->OID),
+			le32_to_cpu(buf->InformationBufferOffset)
+					+ 8 + (u8 *)buf,
+			le32_to_cpu(buf->InformationBufferLength),
+			r)) {
+		/* OID not supported */
+		resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+		resp->MessageLength = cpu_to_le32(sizeof *resp);
+		resp->InformationBufferLength = cpu_to_le32(0);
+		resp->InformationBufferOffset = cpu_to_le32(0);
+	} else
+		resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+static int rndis_set_response(int configNr, rndis_set_msg_type *buf)
+{
+	u32 BufLength, BufOffset;
+	rndis_set_cmplt_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+
+	BufLength = le32_to_cpu(buf->InformationBufferLength);
+	BufOffset = le32_to_cpu(buf->InformationBufferOffset);
+	if ((BufLength > RNDIS_MAX_TOTAL_SIZE) ||
+	    (BufOffset > RNDIS_MAX_TOTAL_SIZE) ||
+	    (BufOffset + 8 >= RNDIS_MAX_TOTAL_SIZE)) {
+		pr_err("%s error: BufLength: %d, BufOffset: %d\n",
+			__func__, BufLength, BufOffset);
+		return -EINVAL;
+	}
+	r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_set_cmplt_type *)r->buf;
+
+#ifdef	VERBOSE_DEBUG
+	pr_debug("%s: Length: %d\n", __func__, BufLength);
+	pr_debug("%s: Offset: %d\n", __func__, BufOffset);
+	pr_debug("%s: InfoBuffer: ", __func__);
+
+	for (i = 0; i < BufLength; i++) {
+		pr_debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset));
+	}
+
+	pr_debug("\n");
+#endif
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_SET_C);
+	resp->MessageLength = cpu_to_le32(16);
+	resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+	if (gen_ndis_set_resp(configNr, le32_to_cpu(buf->OID),
+			((u8 *)buf) + 8 + BufOffset, BufLength, r))
+		resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+	else
+		resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf)
+{
+	rndis_reset_cmplt_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+	u32 length;
+	u8 *xbuf;
+
+	/* drain the response queue */
+	while ((xbuf = rndis_get_next_response(configNr, &length)))
+		rndis_free_response(configNr, xbuf);
+
+	r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_reset_cmplt_type *)r->buf;
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_RESET_C);
+	resp->MessageLength = cpu_to_le32(16);
+	resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+	/* resent information */
+	resp->AddressingReset = cpu_to_le32(1);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+static int rndis_keepalive_response(int configNr,
+				    rndis_keepalive_msg_type *buf)
+{
+	rndis_keepalive_cmplt_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+
+	/* host "should" check only in RNDIS_DATA_INITIALIZED state */
+
+	r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_keepalive_cmplt_type *)r->buf;
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_KEEPALIVE_C);
+	resp->MessageLength = cpu_to_le32(16);
+	resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+	resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+
+/*
+ * Device to Host Comunication
+ */
+static int rndis_indicate_status_msg(int configNr, u32 status)
+{
+	rndis_indicate_status_msg_type *resp;
+	rndis_resp_t *r;
+	struct rndis_params *params = rndis_per_dev_params + configNr;
+
+	if (params->state == RNDIS_UNINITIALIZED)
+		return -ENOTSUPP;
+
+	r = rndis_add_response(configNr,
+				sizeof(rndis_indicate_status_msg_type));
+	if (!r)
+		return -ENOMEM;
+	resp = (rndis_indicate_status_msg_type *)r->buf;
+
+	resp->MessageType = cpu_to_le32(RNDIS_MSG_INDICATE);
+	resp->MessageLength = cpu_to_le32(20);
+	resp->Status = cpu_to_le32(status);
+	resp->StatusBufferLength = cpu_to_le32(0);
+	resp->StatusBufferOffset = cpu_to_le32(0);
+
+	params->resp_avail(params->v);
+	return 0;
+}
+
+int rndis_signal_connect(int configNr)
+{
+	rndis_per_dev_params[configNr].media_state
+			= RNDIS_MEDIA_STATE_CONNECTED;
+	return rndis_indicate_status_msg(configNr,
+					  RNDIS_STATUS_MEDIA_CONNECT);
+}
+
+int rndis_signal_disconnect(int configNr)
+{
+	rndis_per_dev_params[configNr].media_state
+			= RNDIS_MEDIA_STATE_DISCONNECTED;
+	return rndis_indicate_status_msg(configNr,
+					  RNDIS_STATUS_MEDIA_DISCONNECT);
+}
+
+void rndis_uninit(int configNr)
+{
+	u8 *buf;
+	u32 length;
+
+	if (configNr >= RNDIS_MAX_CONFIGS)
+		return;
+	rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED;
+
+	/* drain the response queue */
+	while ((buf = rndis_get_next_response(configNr, &length)))
+		rndis_free_response(configNr, buf);
+}
+
+void rndis_set_host_mac(int configNr, const u8 *addr)
+{
+	rndis_per_dev_params[configNr].host_mac = addr;
+}
+
+#ifdef CONFIG_USB_DWC2_PERIPHERAL 
+extern void dwc2_dump_ep0_profiling_data(void);
+#endif
+
+/*
+ * Message Parser
+ */
+int rndis_msg_parser(u8 configNr, u8 *buf)
+{
+	u32 MsgType, MsgLength;
+	__le32 *tmp;
+	struct rndis_params *params;
+
+	if (!buf)
+		return -ENOMEM;
+
+	tmp = (__le32 *)buf;
+	MsgType   = get_unaligned_le32(tmp++);
+	MsgLength = get_unaligned_le32(tmp++);
+
+	if (configNr >= RNDIS_MAX_CONFIGS)
+		return -ENOTSUPP;
+	params = &rndis_per_dev_params[configNr];
+
+	/* NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for
+	 * rx/tx statistics and link status, in addition to KEEPALIVE traffic
+	 * and normal HC level polling to see if there's any IN traffic.
+	 */
+
+	/* For USB: responses may take up to 10 seconds */
+	switch (MsgType) {
+	case RNDIS_MSG_INIT:
+		pr_info("%s: RNDIS_MSG_INIT\n",
+			__func__);
+		params->state = RNDIS_INITIALIZED;
+		return rndis_init_response(configNr,
+					(rndis_init_msg_type *)buf);
+
+	case RNDIS_MSG_HALT:
+		pr_info("%s: RNDIS_MSG_HALT\n",
+			__func__);
+		params->state = RNDIS_UNINITIALIZED;
+		if (params->dev) {
+			netif_carrier_off(params->dev);
+			netif_stop_queue(params->dev);
+		}
+		return 0;
+
+	case RNDIS_MSG_QUERY:
+		return rndis_query_response(configNr,
+					(rndis_query_msg_type *)buf);
+
+	case RNDIS_MSG_SET:
+		return rndis_set_response(configNr,
+					(rndis_set_msg_type *)buf);
+
+	case RNDIS_MSG_RESET:
+		pr_info("%s: RNDIS_MSG_RESET\n",
+			__func__);
+		return rndis_reset_response(configNr,
+					(rndis_reset_msg_type *)buf);
+
+	case RNDIS_MSG_KEEPALIVE:
+		/* For USB: host does this every 5 seconds */
+		if (rndis_debug > 1)
+			pr_debug("%s: RNDIS_MSG_KEEPALIVE\n",
+				__func__);
+		return rndis_keepalive_response(configNr,
+						 (rndis_keepalive_msg_type *)
+						 buf);
+
+	default:
+		/* At least Windows XP emits some undefined RNDIS messages.
+		 * In one case those messages seemed to relate to the host
+		 * suspending itself.
+		 */
+		pr_warning("%s: unknown RNDIS message 0x%08X len %d, buff: 0x%08x\n",
+			__func__, MsgType, MsgLength, (u32)buf);
+		if (MsgLength > USB_COMP_EP0_BUFSIZ)
+			MsgLength = USB_COMP_EP0_BUFSIZ;
+		print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET,
+				     buf, MsgLength);
+#ifdef CONFIG_USB_DWC2_PERIPHERAL
+		dwc2_dump_ep0_profiling_data();
+#endif
+		break;
+	}
+
+	return -ENOTSUPP;
+}
+
+int rndis_register(void (*resp_avail)(void *v), void *v)
+{
+	u8 i;
+
+	if (!resp_avail)
+		return -EINVAL;
+
+	for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+		if (!rndis_per_dev_params[i].used) {
+			rndis_per_dev_params[i].used = 1;
+			rndis_per_dev_params[i].resp_avail = resp_avail;
+			rndis_per_dev_params[i].v = v;
+			rndis_per_dev_params[i].max_pkt_per_xfer = 1;
+			rndis_per_dev_params[i].pkt_alignment_factor = 0;
+			pr_debug("%s: configNr = %d\n", __func__, i);
+			return i;
+		}
+	}
+	pr_info("%s failed\n", __func__);
+
+	return -ENODEV;
+}
+
+void rndis_deregister(int configNr)
+{
+	pr_debug("%s:\n", __func__);
+
+	if (configNr >= RNDIS_MAX_CONFIGS) return;
+	rndis_per_dev_params[configNr].used = 0;
+}
+
+int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter)
+{
+	pr_debug("%s:\n", __func__);
+	if (!dev)
+		return -EINVAL;
+	if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+	rndis_per_dev_params[configNr].dev = dev;
+	rndis_per_dev_params[configNr].filter = cdc_filter;
+
+	/* reset aggregation stats for every set_alt */
+	rndis_ul_max_xfer_size_rcvd = 0;
+	rndis_ul_max_pkt_per_xfer_rcvd = 0;
+
+	return 0;
+}
+
+int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr)
+{
+	pr_debug("%s:\n", __func__);
+	if (!vendorDescr) return -1;
+	if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+	rndis_per_dev_params[configNr].vendorID = vendorID;
+	rndis_per_dev_params[configNr].vendorDescr = vendorDescr;
+
+	return 0;
+}
+
+int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed)
+{
+	pr_debug("%s: %u %u\n", __func__, medium, speed);
+	if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+	rndis_per_dev_params[configNr].medium = medium;
+	rndis_per_dev_params[configNr].speed = speed;
+
+	return 0;
+}
+
+void rndis_set_max_pkt_xfer(u8 configNr, u8 max_pkt_per_xfer)
+{
+	pr_debug("%s:\n", __func__);
+
+	rndis_per_dev_params[configNr].max_pkt_per_xfer = max_pkt_per_xfer;
+}
+
+void rndis_set_pkt_alignment_factor(u8 configNr, u8 pkt_alignment_factor)
+{
+	pr_debug("%s:\n", __func__);
+
+	rndis_per_dev_params[configNr].pkt_alignment_factor =
+					pkt_alignment_factor;
+}
+
+void rndis_add_hdr(struct sk_buff *skb)
+{
+	struct rndis_packet_msg_type *header;
+
+	if (!skb)
+		return;
+	header = (void *)skb_push(skb, sizeof(*header));
+	memset(header, 0, sizeof *header);
+	header->MessageType = cpu_to_le32(RNDIS_MSG_PACKET);
+	header->MessageLength = cpu_to_le32(skb->len);
+	header->DataOffset = cpu_to_le32(36);
+	header->DataLength = cpu_to_le32(skb->len - sizeof(*header));
+}
+
+void rndis_free_response(int configNr, u8 *buf)
+{
+	rndis_resp_t *r;
+	struct list_head *act, *tmp;
+
+	spin_lock(&rndis_per_dev_params[configNr].resp_lock);
+	list_for_each_safe(act, tmp,
+			&(rndis_per_dev_params[configNr].resp_queue))
+	{
+		r = list_entry(act, rndis_resp_t, list);
+		if (r && r->buf == buf) {
+			list_del(&r->list);
+			kfree(r);
+		}
+	}
+	spin_unlock(&rndis_per_dev_params[configNr].resp_lock);
+}
+
+u8 *rndis_get_next_response(int configNr, u32 *length)
+{
+	rndis_resp_t *r;
+	struct list_head *act, *tmp;
+
+	if (!length) return NULL;
+
+	spin_lock(&rndis_per_dev_params[configNr].resp_lock);
+	list_for_each_safe(act, tmp,
+			&(rndis_per_dev_params[configNr].resp_queue))
+	{
+		r = list_entry(act, rndis_resp_t, list);
+		if (!r->send) {
+			r->send = 1;
+			*length = r->length;
+			spin_unlock(&rndis_per_dev_params[configNr].resp_lock);
+			return r->buf;
+		}
+	}
+	spin_unlock(&rndis_per_dev_params[configNr].resp_lock);
+	return NULL;
+}
+
+static rndis_resp_t *rndis_add_response(int configNr, u32 length)
+{
+	rndis_resp_t *r;
+
+	/* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */
+	r = kmalloc(sizeof(rndis_resp_t) + length, GFP_ATOMIC);
+	if (!r) return NULL;
+
+	r->buf = (u8 *)(r + 1);
+	r->length = length;
+	r->send = 0;
+
+	spin_lock(&rndis_per_dev_params[configNr].resp_lock);
+	list_add_tail(&r->list,
+		&(rndis_per_dev_params[configNr].resp_queue));
+	spin_unlock(&rndis_per_dev_params[configNr].resp_lock);
+
+	return r;
+}
+
+int rndis_rm_hdr(struct gether *port,
+			struct sk_buff *skb,
+			struct sk_buff_head *list)
+{
+	int num_pkts = 0;
+
+	if (skb->len > rndis_ul_max_xfer_size_rcvd)
+		rndis_ul_max_xfer_size_rcvd = skb->len;
+
+	while (skb->len) {
+		struct rndis_packet_msg_type *hdr;
+		struct sk_buff		*skb2;
+		u32		msg_len, data_offset, data_len;
+
+		if (skb->len < sizeof *hdr) {
+			pr_err("invalid rndis pkt: skblen:%u hdr_len:%zu",
+					skb->len, sizeof *hdr);
+			dev_kfree_skb_any(skb);
+			return -EINVAL;
+		}
+
+		hdr = (void *)skb->data;
+		msg_len = le32_to_cpu(hdr->MessageLength);
+		data_offset = le32_to_cpu(hdr->DataOffset);
+		data_len = le32_to_cpu(hdr->DataLength);
+
+		if (skb->len < msg_len ||
+			((data_offset + data_len + 8) > msg_len)) {
+			pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n",
+				le32_to_cpu(hdr->MessageType),
+				msg_len, data_offset, data_len, skb->len);
+			dev_kfree_skb_any(skb);
+			return -EOVERFLOW;
+		}
+
+		if (le32_to_cpu(hdr->MessageType) != RNDIS_MSG_PACKET) {
+			pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n",
+				le32_to_cpu(hdr->MessageType),
+				msg_len, data_offset, data_len, skb->len);
+			dev_kfree_skb_any(skb);
+			return -EINVAL;
+		}
+
+		num_pkts++;
+
+		skb_pull(skb, data_offset + 8);
+
+		if (data_len == skb->len ||
+				data_len == (skb->len - 1)) {
+			skb_trim(skb, data_len);
+			break;
+		}
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (!skb2) {
+			pr_err("%s:skb clone failed\n", __func__);
+			dev_kfree_skb_any(skb);
+			return -ENOMEM;
+		}
+
+		skb_pull(skb, msg_len - sizeof *hdr);
+		skb_trim(skb2, data_len);
+		skb_queue_tail(list, skb2);
+	}
+
+	if (num_pkts > rndis_ul_max_pkt_per_xfer_rcvd)
+		rndis_ul_max_pkt_per_xfer_rcvd = num_pkts;
+
+	skb_queue_tail(list, skb);
+
+	return 0;
+}
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+
+static int rndis_proc_show(struct seq_file *m, void *v)
+{
+	rndis_params *param = m->private;
+
+	seq_printf(m,
+			 "Config Nr. %d\n"
+			 "used      : %s\n"
+			 "state     : %s\n"
+			 "medium    : 0x%08X\n"
+			 "speed     : %d\n"
+			 "cable     : %s\n"
+			 "vendor ID : 0x%08X\n"
+			 "vendor    : %s\n"
+			 "ul-max-xfer-size:%zu max-xfer-size-rcvd: %d\n"
+			 "ul-max-pkts-per-xfer:%d max-pkts-per-xfer-rcvd:%d\n",
+			 param->confignr, (param->used) ? "y" : "n",
+			 ({ char *s = "?";
+			 switch (param->state) {
+			 case RNDIS_UNINITIALIZED:
+				s = "RNDIS_UNINITIALIZED"; break;
+			 case RNDIS_INITIALIZED:
+				s = "RNDIS_INITIALIZED"; break;
+			 case RNDIS_DATA_INITIALIZED:
+				s = "RNDIS_DATA_INITIALIZED"; break;
+			}; s; }),
+			 param->medium,
+			 (param->media_state) ? 0 : param->speed*100,
+			 (param->media_state) ? "disconnected" : "connected",
+			 param->vendorID, param->vendorDescr,
+			 param->max_pkt_per_xfer *
+				 (param->dev->mtu + sizeof(struct ethhdr) +
+				  sizeof(struct rndis_packet_msg_type) + 22),
+			 rndis_ul_max_xfer_size_rcvd,
+			 param->max_pkt_per_xfer,
+			 rndis_ul_max_pkt_per_xfer_rcvd);
+	return 0;
+}
+
+static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	rndis_params *p = PDE_DATA(file_inode(file));
+	u32 speed = 0;
+	int i, fl_speed = 0;
+
+	for (i = 0; i < count; i++) {
+		char c;
+		if (get_user(c, buffer))
+			return -EFAULT;
+		switch (c) {
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+		case '4':
+		case '5':
+		case '6':
+		case '7':
+		case '8':
+		case '9':
+			fl_speed = 1;
+			speed = speed * 10 + c - '0';
+			break;
+		case 'C':
+		case 'c':
+			rndis_signal_connect(p->confignr);
+			break;
+		case 'D':
+		case 'd':
+			rndis_signal_disconnect(p->confignr);
+			break;
+		default:
+			if (fl_speed) p->speed = speed;
+			else pr_debug("%c is not valid\n", c);
+			break;
+		}
+
+		buffer++;
+	}
+
+	return count;
+}
+
+static int rndis_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, rndis_proc_show, PDE_DATA(inode));
+}
+
+static const struct file_operations rndis_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= rndis_proc_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.write		= rndis_proc_write,
+};
+
+#define	NAME_TEMPLATE "driver/rndis-%03d"
+
+static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS];
+
+#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
+
+static bool rndis_initialized;
+
+int rndis_init(void)
+{
+	u8 i;
+
+	if (rndis_initialized)
+		return 0;
+
+	for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+#ifdef	CONFIG_USB_GADGET_DEBUG_FILES
+		char name [20];
+
+		sprintf(name, NAME_TEMPLATE, i);
+		rndis_connect_state[i] = proc_create_data(name, 0660, NULL,
+					&rndis_proc_fops,
+					(void *)(rndis_per_dev_params + i));
+		if (!rndis_connect_state[i]) {
+			pr_debug("%s: remove entries", __func__);
+			while (i) {
+				sprintf(name, NAME_TEMPLATE, --i);
+				remove_proc_entry(name, NULL);
+			}
+			pr_debug("\n");
+			return -EIO;
+		}
+#endif
+		rndis_per_dev_params[i].confignr = i;
+		rndis_per_dev_params[i].used = 0;
+		rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED;
+		rndis_per_dev_params[i].media_state
+				= RNDIS_MEDIA_STATE_DISCONNECTED;
+		INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue));
+		spin_lock_init(&rndis_per_dev_params[i].resp_lock);
+	}
+
+	rndis_initialized = true;
+	return 0;
+}
+
+void rndis_exit(void)
+{
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	u8 i;
+	char name[20];
+#endif
+
+	if (!rndis_initialized)
+		return;
+	rndis_initialized = false;
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+		sprintf(name, NAME_TEMPLATE, i);
+		remove_proc_entry(name, NULL);
+	}
+#endif
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/rndis.h b/marvell/linux/drivers/usb/gadget/function/rndis.h
new file mode 100644
index 0000000..4469746
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/rndis.h
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RNDIS	Definitions for Remote NDIS
+ *
+ * Authors:	Benedikt Spranger, Pengutronix
+ *		Robert Schwebel, Pengutronix
+ *
+ *		This software was originally developed in conformance with
+ *		Microsoft's Remote NDIS Specification License Agreement.
+ */
+
+#ifndef _LINUX_RNDIS_H
+#define _LINUX_RNDIS_H
+
+#include <linux/rndis.h>
+#include "ndis.h"
+
+#define RNDIS_MAXIMUM_FRAME_SIZE	1518
+#define RNDIS_MAX_TOTAL_SIZE		1558
+
+typedef struct rndis_init_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	MajorVersion;
+	__le32	MinorVersion;
+	__le32	MaxTransferSize;
+} rndis_init_msg_type;
+
+typedef struct rndis_init_cmplt_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	Status;
+	__le32	MajorVersion;
+	__le32	MinorVersion;
+	__le32	DeviceFlags;
+	__le32	Medium;
+	__le32	MaxPacketsPerTransfer;
+	__le32	MaxTransferSize;
+	__le32	PacketAlignmentFactor;
+	__le32	AFListOffset;
+	__le32	AFListSize;
+} rndis_init_cmplt_type;
+
+typedef struct rndis_halt_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+} rndis_halt_msg_type;
+
+typedef struct rndis_query_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	OID;
+	__le32	InformationBufferLength;
+	__le32	InformationBufferOffset;
+	__le32	DeviceVcHandle;
+} rndis_query_msg_type;
+
+typedef struct rndis_query_cmplt_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	Status;
+	__le32	InformationBufferLength;
+	__le32	InformationBufferOffset;
+} rndis_query_cmplt_type;
+
+typedef struct rndis_set_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	OID;
+	__le32	InformationBufferLength;
+	__le32	InformationBufferOffset;
+	__le32	DeviceVcHandle;
+} rndis_set_msg_type;
+
+typedef struct rndis_set_cmplt_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	Status;
+} rndis_set_cmplt_type;
+
+typedef struct rndis_reset_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	Reserved;
+} rndis_reset_msg_type;
+
+typedef struct rndis_reset_cmplt_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	Status;
+	__le32	AddressingReset;
+} rndis_reset_cmplt_type;
+
+typedef struct rndis_indicate_status_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	Status;
+	__le32	StatusBufferLength;
+	__le32	StatusBufferOffset;
+} rndis_indicate_status_msg_type;
+
+typedef struct rndis_keepalive_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+} rndis_keepalive_msg_type;
+
+typedef struct rndis_keepalive_cmplt_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	RequestID;
+	__le32	Status;
+} rndis_keepalive_cmplt_type;
+
+struct rndis_packet_msg_type
+{
+	__le32	MessageType;
+	__le32	MessageLength;
+	__le32	DataOffset;
+	__le32	DataLength;
+	__le32	OOBDataOffset;
+	__le32	OOBDataLength;
+	__le32	NumOOBDataElements;
+	__le32	PerPacketInfoOffset;
+	__le32	PerPacketInfoLength;
+	__le32	VcHandle;
+	__le32	Reserved;
+} __attribute__ ((packed));
+
+struct rndis_config_parameter
+{
+	__le32	ParameterNameOffset;
+	__le32	ParameterNameLength;
+	__le32	ParameterType;
+	__le32	ParameterValueOffset;
+	__le32	ParameterValueLength;
+};
+
+/* implementation specific */
+enum rndis_state
+{
+	RNDIS_UNINITIALIZED,
+	RNDIS_INITIALIZED,
+	RNDIS_DATA_INITIALIZED,
+};
+
+typedef struct rndis_resp_t
+{
+	struct list_head	list;
+	u8			*buf;
+	u32			length;
+	int			send;
+} rndis_resp_t;
+
+typedef struct rndis_params
+{
+	u8			confignr;
+	u8			used;
+	u16			saved_filter;
+	enum rndis_state	state;
+	u32			medium;
+	u32			speed;
+	u32			media_state;
+
+	const u8		*host_mac;
+	u16			*filter;
+	struct net_device	*dev;
+
+	u32			vendorID;
+	u8			max_pkt_per_xfer;
+	u8			pkt_alignment_factor;
+	const char		*vendorDescr;
+	void			(*resp_avail)(void *v);
+	void			*v;
+	struct list_head	resp_queue;
+	spinlock_t		resp_lock;
+} rndis_params;
+
+/* RNDIS Message parser and other useless functions */
+int  rndis_msg_parser (u8 configNr, u8 *buf);
+int  rndis_register(void (*resp_avail)(void *v), void *v);
+void rndis_deregister (int configNr);
+int  rndis_set_param_dev (u8 configNr, struct net_device *dev,
+			 u16 *cdc_filter);
+int  rndis_set_param_vendor (u8 configNr, u32 vendorID,
+			    const char *vendorDescr);
+int  rndis_set_param_medium (u8 configNr, u32 medium, u32 speed);
+void rndis_set_max_pkt_xfer(u8 configNr, u8 max_pkt_per_xfer);
+void rndis_add_hdr (struct sk_buff *skb);
+int rndis_rm_hdr(struct gether *port, struct sk_buff *skb,
+			struct sk_buff_head *list);
+u8   *rndis_get_next_response (int configNr, u32 *length);
+void rndis_free_response (int configNr, u8 *buf);
+
+void rndis_uninit (int configNr);
+int  rndis_signal_connect (int configNr);
+int  rndis_signal_disconnect (int configNr);
+int  rndis_state (int configNr);
+extern void rndis_set_host_mac (int configNr, const u8 *addr);
+
+int rndis_init(void);
+void rndis_exit (void);
+
+#endif  /* _LINUX_RNDIS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/skb_llist.h b/marvell/linux/drivers/usb/gadget/function/skb_llist.h
new file mode 100644
index 0000000..3f0fb21
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/skb_llist.h
@@ -0,0 +1,194 @@
+#ifndef SKB_LLIST_H
+#define SKB_LLIST_H
+/*
+ * modified from <linux/llist.h>
+ *
+ * Lock-less NULL terminated single linked list
+ * can be used in multiple producers and one consumer case
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 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 <asm/cmpxchg.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>	/* dev_kfree_skb_any */
+
+
+/*
+ * Prevent the compiler from merging or refetching accesses.  The compiler
+ * is also forbidden from reordering successive instances of ACCESS_ONCE(),
+ * but only when the compiler is aware of some particular ordering.  One way
+ * to make the compiler aware of ordering is to put the two invocations of
+ * ACCESS_ONCE() in different C statements.
+ *
+ * This macro does absolutely -nothing- to prevent the CPU from reordering,
+ * merging, or refetching absolutely anything at any time.  Its main intended
+ * use is to mediate communication between process-level code and irq/NMI
+ * handlers, all running on the same CPU.
+ */
+/* to keep the same logic with 3.10 to avoid potential issues */
+#ifndef ACCESS_ONCE
+#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
+#endif
+
+struct skb_llist_head {
+	struct sk_buff *first;
+};
+
+#define SKB_LLIST_HEAD_INIT(name)	{ NULL }
+#define SKB_LLIST_HEAD(name) \
+	struct skb_llist_head name = SKB_LLIST_HEAD_INIT(name)
+
+/**
+ * init_skb_llist_head - initialize lock-less list head
+ * @head:	the head for your lock-less list
+ */
+static inline void init_skb_llist_head(struct skb_llist_head *list)
+{
+	list->first = NULL;
+}
+
+/**
+ * skb_llist_empty - tests whether a lock-less list is empty
+ * @head:	the list to test
+ *
+ * Not guaranteed to be accurate or up to date.  Just a quick way to
+ * test whether the list is empty without deleting something from the
+ * list.
+ */
+static inline bool skb_llist_empty(const struct skb_llist_head *head)
+{
+	return ACCESS_ONCE(head->first) == NULL;
+}
+
+static inline struct sk_buff *skb_llist_next(struct sk_buff *node)
+{
+	return node->next;
+}
+
+/**
+ * skb_llist_add - add a new entry
+ * @new:	new entry to be added
+ * @head:	the head for your lock-less list
+ *
+ * Returns true if the list was empty prior to adding this entry.
+ */
+static inline bool skb_llist_add(struct sk_buff *new,
+				struct skb_llist_head *head)
+{
+
+	struct sk_buff *first;
+
+	do {
+		new->next = first = ACCESS_ONCE(head->first);
+	} while (cmpxchg(&head->first, first, new) != first);
+
+	return !first;
+}
+
+/**
+ * llist_del_all - delete all entries from lock-less list
+ * @head:	the head of lock-less list to delete all entries
+ *
+ * If list is empty, return NULL, otherwise, delete all entries and
+ * return the pointer to the first entry.  The order of entries
+ * deleted is from the newest to the oldest added one.
+ */
+static inline struct sk_buff *skb_llist_del_all(struct skb_llist_head *head)
+{
+	return xchg(&head->first, NULL);
+}
+
+struct sk_buff *skb_llist_reverse_order(struct sk_buff *head);
+
+
+struct skb_llist {
+	struct skb_llist_head head;
+
+	atomic_t len;
+	/* can only be used by the consumer */
+	struct sk_buff *detached;
+};
+
+static inline u32 skb_llist_len(struct skb_llist *list)
+{
+	return atomic_read(&list->len);
+}
+
+static inline void skb_llist_enqueue(struct skb_llist *list,
+					struct sk_buff *skb)
+{
+	skb_llist_add(skb, &list->head);
+	atomic_inc(&list->len);
+}
+
+static inline void skb_llist_queue_head(struct skb_llist *list,
+					struct sk_buff *skb)
+{
+	skb->next = list->detached;
+	list->detached = skb;
+	atomic_inc(&list->len);
+}
+
+static inline struct sk_buff *skb_llist_peek(struct skb_llist *list)
+{
+	if (!list->detached) {
+		list->detached = skb_llist_del_all(&list->head);
+		list->detached = skb_llist_reverse_order(list->detached);
+	}
+
+	return list->detached;
+}
+
+static inline struct sk_buff *skb_llist_dequeue(struct skb_llist *list)
+{
+	struct sk_buff *skb = NULL;
+
+	skb = skb_llist_peek(list);
+
+	if (list->detached) {
+		list->detached = list->detached->next;
+		atomic_dec(&list->len);
+	}
+
+	return skb;
+}
+
+static inline void skb_llist_init(struct skb_llist *list)
+{
+	init_skb_llist_head(&list->head);
+	list->detached = NULL;
+	atomic_set(&list->len, 0);
+}
+
+static inline void skb_llist_clean(struct skb_llist *list)
+{
+	struct sk_buff *head = list->detached;
+
+	atomic_set(&list->len, 0);
+	list->detached = NULL;
+
+	while (head) {
+		struct sk_buff *tmp = head;
+		head = head->next;
+		dev_kfree_skb_any(tmp);
+	}
+
+	head = skb_llist_del_all(&list->head);
+
+	while (head) {
+		struct sk_buff *tmp = head;
+		head = head->next;
+		dev_kfree_skb_any(tmp);
+	}
+}
+
+#endif /* LLIST_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/storage_common.c b/marvell/linux/drivers/usb/gadget/function/storage_common.c
new file mode 100644
index 0000000..69676bd
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/storage_common.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * storage_common.c -- Common definitions for mass storage functionality
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyeight (C) 2009 Samsung Electronics
+ * Author: Michal Nazarewicz (mina86@mina86.com)
+ */
+
+/*
+ * This file requires the following identifiers used in USB strings to
+ * be defined (each of type pointer to char):
+ *  - fsg_string_interface    -- name of the interface
+ */
+
+/*
+ * When USB_GADGET_DEBUG_FILES is defined the module param num_buffers
+ * sets the number of pipeline buffers (length of the fsg_buffhd array).
+ * The valid range of num_buffers is: num >= 2 && num <= 4.
+ */
+
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/usb/composite.h>
+
+#include "storage_common.h"
+
+/* There is only one interface. */
+
+struct usb_interface_descriptor fsg_intf_desc = {
+	.bLength =		sizeof fsg_intf_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bNumEndpoints =	2,		/* Adjusted during fsg_bind() */
+	.bInterfaceClass =	USB_CLASS_MASS_STORAGE,
+	.bInterfaceSubClass =	USB_SC_SCSI,	/* Adjusted during fsg_bind() */
+	.bInterfaceProtocol =	USB_PR_BULK,	/* Adjusted during fsg_bind() */
+	.iInterface =		FSG_STRING_INTERFACE,
+};
+EXPORT_SYMBOL_GPL(fsg_intf_desc);
+
+/*
+ * Three full-speed endpoint descriptors: bulk-in, bulk-out, and
+ * interrupt-in.
+ */
+
+struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	/* wMaxPacketSize set by autoconfiguration */
+};
+EXPORT_SYMBOL_GPL(fsg_fs_bulk_in_desc);
+
+struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	/* wMaxPacketSize set by autoconfiguration */
+};
+EXPORT_SYMBOL_GPL(fsg_fs_bulk_out_desc);
+
+struct usb_descriptor_header *fsg_fs_function[] = {
+	(struct usb_descriptor_header *) &fsg_intf_desc,
+	(struct usb_descriptor_header *) &fsg_fs_bulk_in_desc,
+	(struct usb_descriptor_header *) &fsg_fs_bulk_out_desc,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(fsg_fs_function);
+
+
+/*
+ * USB 2.0 devices need to expose both high speed and full speed
+ * descriptors, unless they only run at full speed.
+ *
+ * That means alternate endpoint descriptors (bigger packets).
+ */
+struct usb_endpoint_descriptor fsg_hs_bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+EXPORT_SYMBOL_GPL(fsg_hs_bulk_in_desc);
+
+struct usb_endpoint_descriptor fsg_hs_bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+	.bInterval =		1,	/* NAK every 1 uframe */
+};
+EXPORT_SYMBOL_GPL(fsg_hs_bulk_out_desc);
+
+
+struct usb_descriptor_header *fsg_hs_function[] = {
+	(struct usb_descriptor_header *) &fsg_intf_desc,
+	(struct usb_descriptor_header *) &fsg_hs_bulk_in_desc,
+	(struct usb_descriptor_header *) &fsg_hs_bulk_out_desc,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(fsg_hs_function);
+
+struct usb_endpoint_descriptor fsg_ss_bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_desc);
+
+struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = {
+	.bLength =		sizeof(fsg_ss_bulk_in_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/*.bMaxBurst =		DYNAMIC, */
+};
+EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_comp_desc);
+
+struct usb_endpoint_descriptor fsg_ss_bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_desc);
+
+struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = {
+	.bLength =		sizeof(fsg_ss_bulk_in_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/*.bMaxBurst =		DYNAMIC, */
+};
+EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_comp_desc);
+
+struct usb_descriptor_header *fsg_ss_function[] = {
+	(struct usb_descriptor_header *) &fsg_intf_desc,
+	(struct usb_descriptor_header *) &fsg_ss_bulk_in_desc,
+	(struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc,
+	(struct usb_descriptor_header *) &fsg_ss_bulk_out_desc,
+	(struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc,
+	NULL,
+};
+EXPORT_SYMBOL_GPL(fsg_ss_function);
+
+
+ /*-------------------------------------------------------------------------*/
+
+/*
+ * If the next two routines are called while the gadget is registered,
+ * the caller must own fsg->filesem for writing.
+ */
+
+void fsg_lun_close(struct fsg_lun *curlun)
+{
+	if (curlun->filp) {
+		LDBG(curlun, "close backing file\n");
+		fput(curlun->filp);
+		curlun->filp = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(fsg_lun_close);
+
+int fsg_lun_open(struct fsg_lun *curlun, const char *filename)
+{
+	int				ro;
+	struct file			*filp = NULL;
+	int				rc = -EINVAL;
+	struct inode			*inode = NULL;
+	loff_t				size;
+	loff_t				num_sectors;
+	loff_t				min_sectors;
+	unsigned int			blkbits;
+	unsigned int			blksize;
+
+	/* R/W if we can, R/O if we must */
+	ro = curlun->initially_ro;
+	if (!ro) {
+		filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0);
+		if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES)
+			ro = 1;
+	}
+	if (ro)
+		filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0);
+	if (IS_ERR(filp)) {
+		LINFO(curlun, "unable to open backing file: %s\n", filename);
+		return PTR_ERR(filp);
+	}
+
+	if (!(filp->f_mode & FMODE_WRITE))
+		ro = 1;
+
+	inode = file_inode(filp);
+	if ((!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) {
+		LINFO(curlun, "invalid file type: %s\n", filename);
+		goto out;
+	}
+
+	/*
+	 * If we can't read the file, it's no good.
+	 * If we can't write the file, use it read-only.
+	 */
+	if (!(filp->f_mode & FMODE_CAN_READ)) {
+		LINFO(curlun, "file not readable: %s\n", filename);
+		goto out;
+	}
+	if (!(filp->f_mode & FMODE_CAN_WRITE))
+		ro = 1;
+
+	size = i_size_read(inode->i_mapping->host);
+	if (size < 0) {
+		LINFO(curlun, "unable to find file size: %s\n", filename);
+		rc = (int) size;
+		goto out;
+	}
+
+	if (curlun->cdrom) {
+		blksize = 2048;
+		blkbits = 11;
+	} else if (inode->i_bdev) {
+		blksize = bdev_logical_block_size(inode->i_bdev);
+		blkbits = blksize_bits(blksize);
+	} else {
+		blksize = 512;
+		blkbits = 9;
+	}
+
+	num_sectors = size >> blkbits; /* File size in logic-block-size blocks */
+	min_sectors = 1;
+	if (curlun->cdrom) {
+		//min_sectors = 300;	/* Smallest track is 300 frames */
+		min_sectors = 1;	/* Smallest track is 300 frames */
+		if (num_sectors >= 256*60*75) {
+			num_sectors = 256*60*75 - 1;
+			LINFO(curlun, "file too big: %s\n", filename);
+			LINFO(curlun, "using only first %d blocks\n",
+					(int) num_sectors);
+		}
+	}
+	if (num_sectors < min_sectors) {
+		LINFO(curlun, "file too small: %s, num_sectors: %d, min_sectors: %d\n",
+			filename, (u32)num_sectors, (u32)min_sectors);
+		rc = -ETOOSMALL;
+		goto out;
+	}
+
+	if (fsg_lun_is_open(curlun))
+		fsg_lun_close(curlun);
+
+	curlun->blksize = blksize;
+	curlun->blkbits = blkbits;
+	curlun->ro = ro;
+	curlun->filp = filp;
+	curlun->file_length = size;
+	curlun->num_sectors = num_sectors;
+	LDBG(curlun, "open backing file: %s\n", filename);
+	return 0;
+
+out:
+	fput(filp);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fsg_lun_open);
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Sync the file data, don't bother with the metadata.
+ * This code was copied from fs/buffer.c:sys_fdatasync().
+ */
+int fsg_lun_fsync_sub(struct fsg_lun *curlun)
+{
+	struct file	*filp = curlun->filp;
+
+	if (curlun->ro || !filp)
+		return 0;
+	return vfs_fsync(filp, 1);
+}
+EXPORT_SYMBOL_GPL(fsg_lun_fsync_sub);
+
+void store_cdrom_address(u8 *dest, int msf, u32 addr)
+{
+	if (msf) {
+		/*
+		 * Convert to Minutes-Seconds-Frames.
+		 * Sector size is already set to 2048 bytes.
+		 */
+		addr += 2*75;		/* Lead-in occupies 2 seconds */
+		dest[3] = addr % 75;	/* Frames */
+		addr /= 75;
+		dest[2] = addr % 60;	/* Seconds */
+		addr /= 60;
+		dest[1] = addr;		/* Minutes */
+		dest[0] = 0;		/* Reserved */
+	} else {
+		/* Absolute sector */
+		put_unaligned_be32(addr, dest);
+	}
+}
+EXPORT_SYMBOL_GPL(store_cdrom_address);
+
+/*-------------------------------------------------------------------------*/
+
+
+ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf)
+{
+	return sprintf(buf, "%d\n", fsg_lun_is_open(curlun)
+				  ? curlun->ro
+				  : curlun->initially_ro);
+}
+EXPORT_SYMBOL_GPL(fsg_show_ro);
+
+ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf)
+{
+	return sprintf(buf, "%u\n", curlun->nofua);
+}
+EXPORT_SYMBOL_GPL(fsg_show_nofua);
+
+ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		      char *buf)
+{
+	char		*p;
+	ssize_t		rc;
+
+	down_read(filesem);
+	if (fsg_lun_is_open(curlun)) {	/* Get the complete pathname */
+		p = file_path(curlun->filp, buf, PAGE_SIZE - 1);
+		if (IS_ERR(p))
+			rc = PTR_ERR(p);
+		else {
+			rc = strlen(p);
+			memmove(buf, p, rc);
+			buf[rc] = '\n';		/* Add a newline */
+			buf[++rc] = 0;
+		}
+	} else {				/* No file, return 0 bytes */
+		*buf = 0;
+		rc = 0;
+	}
+	up_read(filesem);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fsg_show_file);
+
+ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf)
+{
+	return sprintf(buf, "%u\n", curlun->cdrom);
+}
+EXPORT_SYMBOL_GPL(fsg_show_cdrom);
+
+ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf)
+{
+	return sprintf(buf, "%u\n", curlun->removable);
+}
+EXPORT_SYMBOL_GPL(fsg_show_removable);
+
+ssize_t fsg_show_inquiry_string(struct fsg_lun *curlun, char *buf)
+{
+	return sprintf(buf, "%s\n", curlun->inquiry_string);
+}
+EXPORT_SYMBOL_GPL(fsg_show_inquiry_string);
+
+/*
+ * The caller must hold fsg->filesem for reading when calling this function.
+ */
+static ssize_t _fsg_store_ro(struct fsg_lun *curlun, bool ro)
+{
+	if (fsg_lun_is_open(curlun)) {
+		LDBG(curlun, "read-only status change prevented\n");
+		return -EBUSY;
+	}
+
+	curlun->ro = ro;
+	curlun->initially_ro = ro;
+	LDBG(curlun, "read-only status set to %d\n", curlun->ro);
+
+	return 0;
+}
+
+ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		     const char *buf, size_t count)
+{
+	ssize_t		rc;
+	bool		ro;
+
+	rc = strtobool(buf, &ro);
+	if (rc)
+		return rc;
+
+	/*
+	 * Allow the write-enable status to change only while the
+	 * backing file is closed.
+	 */
+	down_read(filesem);
+	rc = _fsg_store_ro(curlun, ro);
+	if (!rc)
+		rc = count;
+	up_read(filesem);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fsg_store_ro);
+
+ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count)
+{
+	bool		nofua;
+	int		ret;
+
+	ret = strtobool(buf, &nofua);
+	if (ret)
+		return ret;
+
+	/* Sync data when switching from async mode to sync */
+	if (!nofua && curlun->nofua)
+		fsg_lun_fsync_sub(curlun);
+
+	curlun->nofua = nofua;
+
+	return count;
+}
+EXPORT_SYMBOL_GPL(fsg_store_nofua);
+
+ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		       const char *buf, size_t count)
+{
+	int		rc = 0;
+
+	if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) {
+		LDBG(curlun, "eject attempt prevented\n");
+		return -EBUSY;				/* "Door is locked" */
+	}
+
+	/* Remove a trailing newline */
+	if (count > 0 && buf[count-1] == '\n')
+		((char *) buf)[count-1] = 0;		/* Ugh! */
+
+	/* Load new medium */
+	down_write(filesem);
+	if (count > 0 && buf[0]) {
+		/* fsg_lun_open() will close existing file if any. */
+		rc = fsg_lun_open(curlun, buf);
+		if (rc == 0)
+			curlun->unit_attention_data =
+					SS_NOT_READY_TO_READY_TRANSITION;
+	} else if (fsg_lun_is_open(curlun)) {
+		fsg_lun_close(curlun);
+		curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
+	}
+	up_write(filesem);
+	return (rc < 0 ? rc : count);
+}
+EXPORT_SYMBOL_GPL(fsg_store_file);
+
+ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+			const char *buf, size_t count)
+{
+	bool		cdrom;
+	int		ret;
+
+	ret = strtobool(buf, &cdrom);
+	if (ret)
+		return ret;
+
+	down_read(filesem);
+	ret = cdrom ? _fsg_store_ro(curlun, true) : 0;
+
+	if (!ret) {
+		curlun->cdrom = cdrom;
+		ret = count;
+	}
+	up_read(filesem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(fsg_store_cdrom);
+
+ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf,
+			    size_t count)
+{
+	bool		removable;
+	int		ret;
+
+	ret = strtobool(buf, &removable);
+	if (ret)
+		return ret;
+
+	curlun->removable = removable;
+
+	return count;
+}
+EXPORT_SYMBOL_GPL(fsg_store_removable);
+
+ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf,
+				 size_t count)
+{
+	const size_t len = min(count, sizeof(curlun->inquiry_string));
+
+	if (len == 0 || buf[0] == '\n') {
+		curlun->inquiry_string[0] = 0;
+	} else {
+		snprintf(curlun->inquiry_string,
+			 sizeof(curlun->inquiry_string), "%-28s", buf);
+		if (curlun->inquiry_string[len-1] == '\n')
+			curlun->inquiry_string[len-1] = ' ';
+	}
+
+	return count;
+}
+EXPORT_SYMBOL_GPL(fsg_store_inquiry_string);
+
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/storage_common.h b/marvell/linux/drivers/usb/gadget/function/storage_common.h
new file mode 100644
index 0000000..13a95e3
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/storage_common.h
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef USB_STORAGE_COMMON_H
+#define USB_STORAGE_COMMON_H
+
+#include <linux/device.h>
+#include <linux/usb/storage.h>
+#include <scsi/scsi.h>
+#include <asm/unaligned.h>
+
+#ifndef DEBUG
+#undef VERBOSE_DEBUG
+#undef DUMP_MSGS
+#endif /* !DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VLDBG	LDBG
+#else
+#define VLDBG(lun, fmt, args...) do { } while (0)
+#endif /* VERBOSE_DEBUG */
+
+#define _LMSG(func, lun, fmt, args...)					\
+	do {								\
+		if ((lun)->name_pfx && *(lun)->name_pfx)		\
+			func("%s/%s: " fmt, *(lun)->name_pfx,		\
+				 (lun)->name, ## args);			\
+		else							\
+			func("%s: " fmt, (lun)->name, ## args);		\
+	} while (0)
+
+#define LDBG(lun, fmt, args...)		_LMSG(pr_debug, lun, fmt, ## args)
+#define LERROR(lun, fmt, args...)	_LMSG(pr_err, lun, fmt, ## args)
+#define LWARN(lun, fmt, args...)	_LMSG(pr_warn, lun, fmt, ## args)
+#define LINFO(lun, fmt, args...)	_LMSG(pr_info, lun, fmt, ## args)
+
+
+#ifdef DUMP_MSGS
+
+#  define dump_msg(fsg, /* const char * */ label,			\
+		   /* const u8 * */ buf, /* unsigned */ length)		\
+do {									\
+	if (length < 512) {						\
+		DBG(fsg, "%s, length %u:\n", label, length);		\
+		print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET,	\
+			       16, 1, buf, length, 0);			\
+	}								\
+} while (0)
+
+#  define dump_cdb(fsg) do { } while (0)
+
+#else
+
+#  define dump_msg(fsg, /* const char * */ label, \
+		   /* const u8 * */ buf, /* unsigned */ length) do { } while (0)
+
+#  ifdef VERBOSE_DEBUG
+
+#    define dump_cdb(fsg)						\
+	print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE,	\
+		       16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0)		\
+
+#  else
+
+#    define dump_cdb(fsg) do { } while (0)
+
+#  endif /* VERBOSE_DEBUG */
+
+#endif /* DUMP_MSGS */
+
+/* Length of a SCSI Command Data Block */
+#define MAX_COMMAND_SIZE	16
+
+/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */
+#define SS_NO_SENSE				0
+#define SS_COMMUNICATION_FAILURE		0x040800
+#define SS_INVALID_COMMAND			0x052000
+#define SS_INVALID_FIELD_IN_CDB			0x052400
+#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE	0x052100
+#define SS_LOGICAL_UNIT_NOT_SUPPORTED		0x052500
+#define SS_MEDIUM_NOT_PRESENT			0x023a00
+#define SS_MEDIUM_REMOVAL_PREVENTED		0x055302
+#define SS_NOT_READY_TO_READY_TRANSITION	0x062800
+#define SS_RESET_OCCURRED			0x062900
+#define SS_SAVING_PARAMETERS_NOT_SUPPORTED	0x053900
+#define SS_UNRECOVERED_READ_ERROR		0x031100
+#define SS_WRITE_ERROR				0x030c02
+#define SS_WRITE_PROTECTED			0x072700
+
+#define SK(x)		((u8) ((x) >> 16))	/* Sense Key byte, etc. */
+#define ASC(x)		((u8) ((x) >> 8))
+#define ASCQ(x)		((u8) (x))
+
+/*
+ * Vendor (8 chars), product (16 chars), release (4 hexadecimal digits) and NUL
+ * byte
+ */
+#define INQUIRY_STRING_LEN ((size_t) (8 + 16 + 4 + 1))
+
+struct fsg_lun {
+	struct file	*filp;
+	loff_t		file_length;
+	loff_t		num_sectors;
+
+	unsigned int	initially_ro:1;
+	unsigned int	ro:1;
+	unsigned int	removable:1;
+	unsigned int	cdrom:1;
+	unsigned int	prevent_medium_removal:1;
+	unsigned int	registered:1;
+	unsigned int	info_valid:1;
+	unsigned int	nofua:1;
+
+	u32		sense_data;
+	u32		sense_data_info;
+	u32		unit_attention_data;
+
+	unsigned int	blkbits; /* Bits of logical block size
+						       of bound block device */
+	unsigned int	blksize; /* logical block size of bound block device */
+	struct device	dev;
+	const char	*name;		/* "lun.name" */
+	const char	**name_pfx;	/* "function.name" */
+	char		inquiry_string[INQUIRY_STRING_LEN];
+	const char	*filename;
+};
+
+static inline bool fsg_lun_is_open(struct fsg_lun *curlun)
+{
+	return curlun->filp != NULL;
+}
+
+/* Default size of buffer length. */
+#define FSG_BUFLEN	((u32)16384)
+
+/* Maximal number of LUNs supported in mass storage function */
+#define FSG_MAX_LUNS	16
+
+enum fsg_buffer_state {
+	BUF_STATE_SENDING = -2,
+	BUF_STATE_RECEIVING,
+	BUF_STATE_EMPTY = 0,
+	BUF_STATE_FULL
+};
+
+struct fsg_buffhd {
+	void				*buf;
+	enum fsg_buffer_state		state;
+	struct fsg_buffhd		*next;
+
+	/*
+	 * The NetChip 2280 is faster, and handles some protocol faults
+	 * better, if we don't submit any short bulk-out read requests.
+	 * So we will record the intended request length here.
+	 */
+	unsigned int			bulk_out_intended_length;
+
+	struct usb_request		*inreq;
+	struct usb_request		*outreq;
+};
+
+enum fsg_state {
+	FSG_STATE_NORMAL,
+	FSG_STATE_ABORT_BULK_OUT,
+	FSG_STATE_PROTOCOL_RESET,
+	FSG_STATE_CONFIG_CHANGE,
+	FSG_STATE_EXIT,
+	FSG_STATE_TERMINATED
+};
+
+enum data_direction {
+	DATA_DIR_UNKNOWN = 0,
+	DATA_DIR_FROM_HOST,
+	DATA_DIR_TO_HOST,
+	DATA_DIR_NONE
+};
+
+static inline u32 get_unaligned_be24(u8 *buf)
+{
+	return 0xffffff & (u32) get_unaligned_be32(buf - 1);
+}
+
+static inline struct fsg_lun *fsg_lun_from_dev(struct device *dev)
+{
+	return container_of(dev, struct fsg_lun, dev);
+}
+
+enum {
+	FSG_STRING_INTERFACE
+};
+
+extern struct usb_interface_descriptor fsg_intf_desc;
+
+extern struct usb_endpoint_descriptor fsg_fs_bulk_in_desc;
+extern struct usb_endpoint_descriptor fsg_fs_bulk_out_desc;
+extern struct usb_descriptor_header *fsg_fs_function[];
+
+extern struct usb_endpoint_descriptor fsg_hs_bulk_in_desc;
+extern struct usb_endpoint_descriptor fsg_hs_bulk_out_desc;
+extern struct usb_descriptor_header *fsg_hs_function[];
+
+extern struct usb_endpoint_descriptor fsg_ss_bulk_in_desc;
+extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc;
+extern struct usb_endpoint_descriptor fsg_ss_bulk_out_desc;
+extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc;
+extern struct usb_descriptor_header *fsg_ss_function[];
+
+void fsg_lun_close(struct fsg_lun *curlun);
+int fsg_lun_open(struct fsg_lun *curlun, const char *filename);
+int fsg_lun_fsync_sub(struct fsg_lun *curlun);
+void store_cdrom_address(u8 *dest, int msf, u32 addr);
+ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf);
+ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf);
+ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		      char *buf);
+ssize_t fsg_show_inquiry_string(struct fsg_lun *curlun, char *buf);
+ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf);
+ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf);
+ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		     const char *buf, size_t count);
+ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count);
+ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+		       const char *buf, size_t count);
+ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem,
+			const char *buf, size_t count);
+ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf,
+			    size_t count);
+ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf,
+				 size_t count);
+
+#endif /* USB_STORAGE_COMMON_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/tcm.h b/marvell/linux/drivers/usb/gadget/function/tcm.h
new file mode 100644
index 0000000..3cd5657
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/tcm.h
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TARGET_USB_GADGET_H__
+#define __TARGET_USB_GADGET_H__
+
+#include <linux/kref.h>
+/* #include <linux/usb/uas.h> */
+#include <linux/usb/composite.h>
+#include <linux/usb/uas.h>
+#include <linux/usb/storage.h>
+#include <target/target_core_base.h>
+#include <target/target_core_fabric.h>
+
+#define USBG_NAMELEN 32
+
+#define fuas_to_gadget(f)	(f->function.config->cdev->gadget)
+#define UASP_SS_EP_COMP_LOG_STREAMS 4
+#define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS)
+
+enum {
+	USB_G_STR_INT_UAS = 0,
+	USB_G_STR_INT_BBB,
+};
+
+#define USB_G_ALT_INT_BBB       0
+#define USB_G_ALT_INT_UAS       1
+
+#define USB_G_DEFAULT_SESSION_TAGS	128
+
+struct tcm_usbg_nexus {
+	struct se_session *tvn_se_sess;
+};
+
+struct usbg_tpg {
+	struct mutex tpg_mutex;
+	/* SAS port target portal group tag for TCM */
+	u16 tport_tpgt;
+	/* Pointer back to usbg_tport */
+	struct usbg_tport *tport;
+	struct workqueue_struct *workqueue;
+	/* Returned by usbg_make_tpg() */
+	struct se_portal_group se_tpg;
+	u32 gadget_connect;
+	struct tcm_usbg_nexus *tpg_nexus;
+	atomic_t tpg_port_count;
+
+	struct usb_function_instance *fi;
+};
+
+struct usbg_tport {
+	/* Binary World Wide unique Port Name for SAS Target port */
+	u64 tport_wwpn;
+	/* ASCII formatted WWPN for SAS Target port */
+	char tport_name[USBG_NAMELEN];
+	/* Returned by usbg_make_tport() */
+	struct se_wwn tport_wwn;
+};
+
+enum uas_state {
+	UASP_SEND_DATA,
+	UASP_RECEIVE_DATA,
+	UASP_SEND_STATUS,
+	UASP_QUEUE_COMMAND,
+};
+
+#define USBG_MAX_CMD    64
+struct usbg_cmd {
+	/* common */
+	u8 cmd_buf[USBG_MAX_CMD];
+	u32 data_len;
+	struct work_struct work;
+	int unpacked_lun;
+	struct se_cmd se_cmd;
+	void *data_buf; /* used if no sg support available */
+	struct f_uas *fu;
+	struct completion write_complete;
+	struct kref ref;
+
+	/* UAS only */
+	u16 tag;
+	u16 prio_attr;
+	struct sense_iu sense_iu;
+	enum uas_state state;
+	struct uas_stream *stream;
+
+	/* BOT only */
+	__le32 bot_tag;
+	unsigned int csw_code;
+	unsigned is_read:1;
+
+};
+
+struct uas_stream {
+	struct usb_request	*req_in;
+	struct usb_request	*req_out;
+	struct usb_request	*req_status;
+};
+
+struct usbg_cdb {
+	struct usb_request	*req;
+	void			*buf;
+};
+
+struct bot_status {
+	struct usb_request	*req;
+	struct bulk_cs_wrap	csw;
+};
+
+struct f_uas {
+	struct usbg_tpg		*tpg;
+	struct usb_function	function;
+	u16			iface;
+
+	u32			flags;
+#define USBG_ENABLED		(1 << 0)
+#define USBG_IS_UAS		(1 << 1)
+#define USBG_USE_STREAMS	(1 << 2)
+#define USBG_IS_BOT		(1 << 3)
+#define USBG_BOT_CMD_PEND	(1 << 4)
+
+	struct usbg_cdb		cmd;
+	struct usb_ep		*ep_in;
+	struct usb_ep		*ep_out;
+
+	/* UAS */
+	struct usb_ep		*ep_status;
+	struct usb_ep		*ep_cmd;
+	struct uas_stream	stream[UASP_SS_EP_COMP_NUM_STREAMS];
+
+	/* BOT */
+	struct bot_status	bot_status;
+	struct usb_request	*bot_req_in;
+	struct usb_request	*bot_req_out;
+};
+
+#endif /* __TARGET_USB_GADGET_H__ */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.c b/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.c
new file mode 100644
index 0000000..3283b1a
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_audio.c -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * Sound card implementation was cut-and-pasted with changes
+ * from f_uac2.c and has:
+ *    Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01@gmail.com)
+ *    Jaswinder Singh (jaswinder.singh@linaro.org)
+ */
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "u_asr_uac1.h"
+#include "f_asr_uac1.h"
+
+#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
+#define PRD_SIZE_MAX	PAGE_SIZE
+#define MIN_PERIODS	4
+
+struct uac_req {
+	struct uac_rtd_params *pp; /* parent param */
+	struct usb_request *req;
+};
+
+/* Runtime data params for one stream */
+struct uac_rtd_params {
+	struct snd_uac_chip *uac; /* parent chip */
+	bool ep_enabled; /* if the ep is enabled */
+
+	struct snd_pcm_substream *ss;
+
+	/* Ring buffer */
+	ssize_t hw_ptr;
+
+	void *rbuf;
+
+	unsigned int max_psize;	/* MaxPacketSize of endpoint */
+	struct uac_req *ureq;
+
+	spinlock_t lock;
+};
+
+struct snd_uac_chip {
+	struct g_audio *audio_dev;
+
+	struct uac_rtd_params p_prm;
+	struct uac_rtd_params c_prm;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+
+	/* timekeeping for the playback endpoint */
+	unsigned int p_interval;
+	unsigned int p_residue;
+
+	/* pre-calculated values for playback iso completion */
+	unsigned int p_pktsize;
+	unsigned int p_pktsize_residue;
+	unsigned int p_framesize;
+};
+
+static const struct snd_pcm_hardware uac_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
+		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
+	.buffer_bytes_max = BUFF_SIZE_MAX,
+	.period_bytes_max = PRD_SIZE_MAX,
+	.periods_min = MIN_PERIODS,
+};
+
+/*
+ * This component encapsulates the ALSA devices for USB audio gadget
+ */
+
+#define FILE_PCM_PLAYBACK	"/dev/snd/pcmC0D0p"
+#define FILE_PCM_CAPTURE	"/dev/snd/pcmC0D0c"
+#define FILE_CONTROL		"/dev/snd/controlC0"
+
+extern ssize_t audiostub_pcm_read(char* data, size_t size);
+extern ssize_t audiostub_pcm_write(const char* data, size_t size);
+extern int audiostub_pcm_control(u8 on_off, u8, u8, u8);
+
+static int uac_mode = 0;//0->VoIP,1->VoUSB,2->multimedia
+
+static bool uac_playback_enable = false;
+static bool uac_capture_enable = false;
+
+static char *fn_play = FILE_PCM_PLAYBACK;
+module_param(fn_play, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
+
+static char *fn_cap = FILE_PCM_CAPTURE;
+module_param(fn_cap, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
+
+static char *fn_cntl = FILE_CONTROL;
+module_param(fn_cntl, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_cntl, "Control device file name");
+
+#define OUT_EP_MAX_PACKET_SIZE	UAC1_OUT_EP_MAX_PACKET_SIZE
+#define IN_EP_MAX_PACKET_SIZE	UAC1_IN_EP_MAX_PACKET_SIZE
+
+static int cap_req_size = IN_EP_MAX_PACKET_SIZE;
+module_param(cap_req_size, int, S_IRUGO);
+MODULE_PARM_DESC(cap_req_size, "ISO IN endpoint request buffer size");
+
+static int playbk_req_size = OUT_EP_MAX_PACKET_SIZE;
+module_param(playbk_req_size, int, S_IRUGO);
+MODULE_PARM_DESC(playbk_req_size, "ISO OUT endpoint request buffer size");
+
+
+static int playbk_buf_size = (OUT_EP_MAX_PACKET_SIZE * 40);
+module_param(playbk_buf_size, int, S_IRUGO);
+MODULE_PARM_DESC(playbk_buf_size, "playback buffer size");
+
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Some ALSA internal helper functions
+ */
+static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
+{
+	struct snd_interval t;
+	t.empty = 0;
+	t.min = t.max = val;
+	t.openmin = t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(i, &t);
+}
+
+static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
+				 snd_pcm_hw_param_t var, unsigned int val,
+				 int dir)
+{
+	int changed;
+	if (hw_is_mask(var)) {
+		struct snd_mask *m = hw_param_mask(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_mask_none(m);
+		} else {
+			if (dir > 0)
+				val++;
+			else if (dir < 0)
+				val--;
+			changed = snd_mask_refine_set(
+					hw_param_mask(params, var), val);
+		}
+	} else if (hw_is_interval(var)) {
+		struct snd_interval *i = hw_param_interval(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_interval_none(i);
+		} else if (dir == 0)
+			changed = snd_interval_refine_set(i, val);
+		else {
+			struct snd_interval t;
+			t.openmin = 1;
+			t.openmax = 1;
+			t.empty = 0;
+			t.integer = 0;
+			if (dir < 0) {
+				t.min = val - 1;
+				t.max = val;
+			} else {
+				t.min = val;
+				t.max = val+1;
+			}
+			changed = snd_interval_refine(i, &t);
+		}
+	} else
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * Set default hardware params
+ */
+static int playback_default_hw_params(struct gaudio_snd_dev *snd)
+{
+	struct snd_pcm_substream *substream = snd->substream;
+	struct snd_pcm_hw_params *params;
+	snd_pcm_sframes_t result;
+
+       /*
+	* SNDRV_PCM_ACCESS_RW_INTERLEAVED,
+	* SNDRV_PCM_FORMAT_S16_LE
+	* CHANNELS: 2
+	* RATE: 48000
+	*/
+	snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+	snd->format = SNDRV_PCM_FORMAT_S16_LE;
+	snd->channels = 2;
+	snd->rate = 48000;
+
+	params = kzalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	_snd_pcm_hw_params_any(params);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+			snd->access, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+			snd->format, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+			snd->channels, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+			snd->rate, 0);
+
+	snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+	snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params);
+
+	result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+	if (result < 0) {
+		pr_err("Preparing sound card failed: %d\n", (int)result);
+		kfree(params);
+		return result;
+	}
+
+	/* Store the hardware parameters */
+	snd->access = params_access(params);
+	snd->format = params_format(params);
+	snd->channels = params_channels(params);
+	snd->rate = params_rate(params);
+
+	kfree(params);
+
+	pr_info("Hardware params: access %x, format %x, channels %d, rate %d\n",
+		snd->access, snd->format, snd->channels, snd->rate);
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Playback audio buffer data by ALSA PCM device
+ */
+static size_t u_audio_playback(struct g_audio *card, void *buf, size_t count)
+{
+    ssize_t written_size = 0;
+#if 0
+	struct gaudio_snd_dev	*snd = &card->playback;
+	struct snd_pcm_substream *substream = snd->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	mm_segment_t old_fs;
+	ssize_t result;
+	snd_pcm_sframes_t frames;
+
+try_again:
+	if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+		result = snd_pcm_kernel_ioctl(substream,
+				SNDRV_PCM_IOCTL_PREPARE, NULL);
+		if (result < 0) {
+			pr_err("Preparing sound card failed: %d\n",
+					(int)result);
+			return result;
+		}
+	}
+
+	frames = bytes_to_frames(runtime, count);
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+	result = snd_pcm_lib_write(snd->substream, buf, frames);
+	if (result != frames) {
+		pr_err("Playback error: %d\n", (int)result);
+		set_fs(old_fs);
+		goto try_again;
+	}
+	set_fs(old_fs);
+#endif
+    if (count > 0 && buf) {
+        written_size = audiostub_pcm_write(buf, count);
+    }
+
+    if (written_size <= 0) {
+        pr_err_ratelimited("%s, buf:0x%lx, size:%d, written:%d\n", __func__, (unsigned long)buf, count, written_size);
+    }
+
+    return 0;
+}
+
+static void f_audio_buffer_free(struct f_audio_buf *audio_buf)
+{
+	kfree(audio_buf->buf);
+	kfree(audio_buf);
+}
+
+static struct f_audio_buf *f_audio_buffer_alloc(int buf_size)
+{
+	struct f_audio_buf *copy_buf;
+
+	copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC);
+	if (!copy_buf)
+		return ERR_PTR(-ENOMEM);
+
+	copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC);
+	if (!copy_buf->buf) {
+		kfree(copy_buf);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return copy_buf;
+}
+
+static inline void free_ep(struct g_audio *audio_dev, struct usb_ep *ep, bool is_in)
+{
+	int i;
+
+	if (is_in) {
+		if (!audio_dev->in_enabled) {
+			pr_err("%s return on in ep disabled\n", __func__);
+			return;
+		}
+
+		for (i = 0; i < NR_USB_AUDIO_REQ; i++) {
+			if (audio_dev->in_req[i]) {
+				if (usb_ep_dequeue(ep, audio_dev->in_req[i]))
+					usb_ep_free_request(ep, audio_dev->in_req[i]);
+				/*
+				 * If usb_ep_dequeue() cannot successfully dequeue the
+				 * request, the request will be freed by the completion
+				 * callback.
+				 */
+
+				audio_dev->in_req[i] = NULL;
+			}
+		}
+
+		audio_dev->in_enabled = false;
+
+		if (usb_ep_disable(ep))
+			pr_err("%s:%d Error!\n", __func__, __LINE__);
+	} else {
+		if (!audio_dev->out_enabled) {
+			pr_err("%s return on out ep disabled\n", __func__);
+			return;
+		}
+		for (i = 0; i < NR_USB_AUDIO_REQ; i++) {
+			if (audio_dev->out_req[i]) {
+				if (usb_ep_dequeue(ep, audio_dev->out_req[i]))
+					usb_ep_free_request(ep, audio_dev->out_req[i]);
+				/*
+				 * If usb_ep_dequeue() cannot successfully dequeue the
+				 * request, the request will be freed by the completion
+				 * callback.
+				 */
+				audio_dev->out_req[i] = NULL;
+			}
+		}
+		audio_dev->out_enabled = false;
+	
+		if (usb_ep_disable(ep))
+			pr_err("%s:%d Error!\n", __func__, __LINE__);
+	}
+}
+
+static void u_audio_capture_work(struct work_struct *data)
+{
+    ssize_t readed_size = 0;
+    struct g_audio *audio = container_of(data, struct g_audio, capture_work);
+    char tmp_buf[20 * IN_EP_MAX_PACKET_SIZE] = { 0 };
+
+    while (1) {
+        readed_size = audiostub_pcm_read(&tmp_buf[0], sizeof(tmp_buf));
+        if (readed_size == sizeof(tmp_buf)) {
+            int i;
+            for (i = 0; i < 20; i++) {
+                struct f_audio_buf *copy_buf = f_audio_buffer_alloc(cap_req_size);
+                memcpy(copy_buf->buf, &tmp_buf[i * IN_EP_MAX_PACKET_SIZE], IN_EP_MAX_PACKET_SIZE);
+                copy_buf->actual = IN_EP_MAX_PACKET_SIZE;
+                list_add_tail(&copy_buf->list, &audio->capture_queue);
+            }
+            //pr_err_ratelimited("%s, readed:%d!\n", __func__, readed_size);
+        } else {
+            break;
+        }
+    }
+}
+
+
+static void u_audio_playback_work(struct work_struct *data)
+{
+    struct g_audio *audio = container_of(data, struct g_audio,
+                                         playback_work);
+
+    if (list_empty(&audio->play_queue)) {
+        return;
+    } else {
+        struct f_audio_buf *play_buf;
+        play_buf = list_first_entry(&audio->play_queue,
+                                    struct f_audio_buf, list);
+        if (play_buf) {
+            list_del(&play_buf->list);
+            u_audio_playback(audio, play_buf->buf, play_buf->actual);
+            f_audio_buffer_free(play_buf);
+        } else {
+            pr_err("%s, play_buf is null!\n", __func__);
+        }
+    }
+}
+
+static void u_audio_playback_complete(struct usb_ep *ep, struct usb_request *req)
+{
+    struct g_audio *audio = req->context;
+    struct f_audio_buf *copy_buf = audio->copy_buf;
+    int err;
+    int status = req->status;
+
+    /* i/f shutting down */
+    if (!audio->out_enabled) {
+        pr_err("audio->ep_enabled = 0\n");
+        usb_ep_free_request(ep, req);
+        return;
+    }
+
+    if (req->status == -ESHUTDOWN) {
+        pr_err("req->status == -ESHUTDOWN\n");
+        return;
+    }
+    /*
+    * We can't really do much about bad xfers.
+    * Afterall, the ISOCH xfers could fail legitimately.
+    */
+    if (status)
+    	printk_ratelimited(KERN_DEBUG "%s: iso_complete status(%d) %d/%d\n",
+           __func__, status, req->actual, req->length);
+
+    if (!copy_buf)
+        return;
+
+    spin_lock_irq(&audio->lock);
+    /* Copy buffer is full, add it to the play_queue */
+    if (playbk_buf_size - copy_buf->actual < req->actual) {
+        list_add_tail(&copy_buf->list, &audio->play_queue);
+        schedule_work(&audio->playback_work);
+        copy_buf = f_audio_buffer_alloc(playbk_buf_size);
+        if (IS_ERR(copy_buf)) {
+            pr_err("%s,copy_buf is null!\n", __func__);
+            spin_unlock_irq(&audio->lock);
+            return;
+        }
+    }
+
+    memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual);
+    copy_buf->actual += req->actual;
+    audio->copy_buf = copy_buf;
+    spin_unlock_irq(&audio->lock);
+
+    err = usb_ep_queue(ep, req, GFP_ATOMIC);
+    if (err)
+        pr_err("%s queue req: %d\n", ep->name, err);
+}
+
+
+static __maybe_unused void u_audio_capture_complete(struct usb_ep *ep, struct usb_request *req)
+{
+    struct g_audio *audio = req->context;
+    int err = 0;
+    int status = req->status;
+
+    /* i/f shutting down */
+    if (!audio->in_enabled) {
+        pr_err_ratelimited("prm->in_enabled = 0\n");
+        usb_ep_free_request(ep, req);
+        return;
+    }
+
+    if (req->status == -ESHUTDOWN) {
+        pr_err("req->status == -ESHUTDOWN\n");
+        return;
+    }
+    /*
+    * We can't really do much about bad xfers.
+    * Afterall, the ISOCH xfers could fail legitimately.
+    */
+    if (status)
+        printk_ratelimited(KERN_DEBUG "%s: iso_complete status(%d) %d/%d\n",
+           __func__, status, req->actual, req->length);
+
+    spin_lock_irq(&audio->cap_lock);
+    if (list_empty(&audio->capture_queue)) {
+        req->length = 0;
+        schedule_work(&audio->capture_work);
+    } else {
+        struct f_audio_buf *cap_buf;
+        cap_buf = list_first_entry(&audio->capture_queue,
+                                   struct f_audio_buf, list);
+        list_del(&cap_buf->list);
+        if (cap_buf-> buf && req->buf) {
+            memcpy(req->buf, cap_buf->buf, cap_req_size);
+            req->length = cap_req_size;
+        } else {
+            pr_err("%s req->buf:0x%lx,cap_buf->buf:0x%lx\n", __func__, (unsigned long)req->buf, (unsigned long)cap_buf->buf);
+        }
+        f_audio_buffer_free(cap_buf);
+    }
+    err = usb_ep_queue(ep, req, GFP_ATOMIC);
+    spin_unlock_irq(&audio->cap_lock);
+
+    if (err)
+        pr_err("%s queue req: %d\n", ep->name, err);
+}
+
+static int u_audio_start_playback_impl(struct g_audio *audio_dev);
+static void u_audio_control_work(struct work_struct *data)
+{
+    struct g_audio *audio_dev = container_of(data, struct g_audio,
+                                         control_work.work);
+
+    if (audio_dev->playback_enabled) {
+        printk(KERN_INFO"%s, playback start.\n", __FUNCTION__);
+        uac_playback_enable = true;
+
+        if (uac_mode == 0)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 1, 2, 1);//playback to near end near vocoder
+        else if (uac_mode == 1)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 2, 1, 1);//playback to far end near codec
+        else if (uac_mode == 2)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 1, 1, 1);//playback to near end near codec
+        //u_audio_start_playback_impl(audio_dev);
+    } else {
+        printk(KERN_INFO"%s, playback stop.\n", __FUNCTION__);
+        uac_playback_enable = false;
+
+        audiostub_pcm_control(0, 0, 0, 0);
+    }
+
+    if (audio_dev->capture_enabled) {
+        printk(KERN_INFO"%s, capture start.\n", __FUNCTION__);
+        uac_capture_enable = true;
+
+        if (uac_mode == 1)
+            audiostub_pcm_control(0x20 | 0x1 | 0x2 | 0x4, 2, 1, 0);//record far end near codec
+        else
+            audiostub_pcm_control(0x20 | 0x1 | 0x2 | 0x4, 1, 2, 0);//record near end near vocoder
+    } else {
+        printk(KERN_INFO"%s, capture stop.\n", __FUNCTION__);
+        uac_capture_enable = false;
+
+        audiostub_pcm_control(0x20, 0, 0, 0);
+    }
+}
+
+void u_audio_control_rework(void)
+{
+    printk(KERN_INFO"%s ...\n", __FUNCTION__);
+
+    if (uac_playback_enable) {
+        printk(KERN_INFO"%s, UAC playback enable.\n", __FUNCTION__);
+
+        if (uac_mode == 0)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 1, 2, 1);//playback to near end near vocoder
+        else if (uac_mode == 1)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 2, 1, 1);//playback to far end near codec
+        else if (uac_mode == 2)
+            audiostub_pcm_control(0x1 | 0x2 | 0x4, 1, 1, 1);//playback to near end near codec
+    }
+
+    if (uac_capture_enable) {
+        printk(KERN_INFO"%s, UAC capture enable.\n", __FUNCTION__);
+
+        if (uac_mode == 1)
+            audiostub_pcm_control(0x20 | 0x1 | 0x2 | 0x4, 2, 1, 0);//record far end near codec
+        else
+            audiostub_pcm_control(0x20 | 0x1 | 0x2 | 0x4, 1, 2, 0);//record near end near vocoder
+    }
+
+    return;
+}
+
+int u_audio_start_capture(struct g_audio *audio_dev)
+{
+	struct usb_gadget *gadget = audio_dev->gadget;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	int ret, i;
+
+	ep = audio_dev->in_ep;
+	config_ep_by_speed(gadget, &audio_dev->func, ep);
+	audio_dev->in_enabled = true;
+	usb_ep_enable(ep);
+
+	for (i = 0; i < NR_USB_AUDIO_REQ; i++) {
+		if (!audio_dev->in_req[i]) {
+			req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+			if (req) {
+				req->buf = kzalloc(cap_req_size,
+						GFP_ATOMIC);
+				if (req->buf) {
+					req->length = cap_req_size;
+					req->context = audio_dev;
+					req->complete =
+						u_audio_capture_complete;
+					audio_dev->in_req[i] = req;
+					ret = usb_ep_queue(ep,
+						req, GFP_ATOMIC);
+					if (ret)
+						pr_err("%s error queue req: %d\n",
+						ep->name, ret);
+				} else {
+					usb_ep_free_request(ep, req);
+					pr_err("%s error NOMEM: %d\n",
+						ep->name, ret);
+					ret = -ENOMEM;
+				}
+			} else {
+				ret = -ENOMEM;
+				pr_err("%s error NOMEM2: %d\n",
+					ep->name, ret);
+			}
+		}
+	}
+
+    if (work_pending(&audio_dev->control_work.work))
+        cancel_delayed_work(&audio_dev->control_work);
+
+    audio_dev->capture_enabled = true;
+    schedule_delayed_work(&audio_dev->control_work, 0);
+    pr_err("%s\n", __func__);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_capture);
+
+void u_audio_stop_capture(struct g_audio *audio_dev)
+{
+    free_ep(audio_dev, audio_dev->in_ep, true);
+
+    if (work_pending(&audio_dev->control_work.work))
+        cancel_delayed_work(&audio_dev->control_work);
+
+    audio_dev->capture_enabled = false;
+    schedule_delayed_work(&audio_dev->control_work, 0);
+    pr_err("%s\n", __func__);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_capture);
+
+static int u_audio_start_playback_impl(struct g_audio *audio_dev)
+{
+    struct usb_gadget *gadget = audio_dev->gadget;
+    struct usb_request *req;
+    struct usb_ep *ep;
+    int ret, i;
+
+    if (audio_dev->copy_buf) {
+        f_audio_buffer_free(audio_dev->copy_buf);
+    }
+    audio_dev->copy_buf = f_audio_buffer_alloc(playbk_buf_size);
+    while (1) {
+        if (list_empty(&audio_dev->play_queue)) {
+            break;
+        } else {
+            struct f_audio_buf *play_buf;
+            play_buf = list_first_entry(&audio_dev->play_queue,
+                                        struct f_audio_buf, list);
+            if (play_buf) {
+                list_del(&play_buf->list);
+                f_audio_buffer_free(play_buf);
+                pr_err("%s, free previous mem:0x%lx\n", __func__, (unsigned long)play_buf);
+            }
+        }
+    }
+
+    if (audio_dev->out_enabled)
+        free_ep(audio_dev, audio_dev->out_ep, false);
+    ep = audio_dev->out_ep;
+    config_ep_by_speed(gadget, &audio_dev->func, ep);
+    audio_dev->out_enabled = true;
+    usb_ep_enable(ep);
+
+    for (i = 0; i < NR_USB_AUDIO_REQ; i++) {
+        if (!audio_dev->out_req[i]) {
+            req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+            if (req) {
+                req->buf = kzalloc(playbk_req_size,
+                                   GFP_ATOMIC);
+                if (req->buf) {
+                    req->length = playbk_req_size;
+                    req->context = audio_dev;
+                    req->complete =
+                    u_audio_playback_complete;
+                    audio_dev->out_req[i] = req;
+                    ret = usb_ep_queue(ep,
+                                       req, GFP_ATOMIC);
+                    if (ret)
+                    pr_err("%s error queue req: %d\n",
+                           ep->name, ret);
+                } else {
+                    usb_ep_free_request(ep, req);
+                    pr_err("%s error NOMEM: %d\n",
+                           ep->name, ret);
+                    ret = -ENOMEM;
+                }
+            } else {
+                ret = -ENOMEM;
+                pr_err("%s error NOMEM2: %d\n",
+                       ep->name, ret);
+            }
+        }
+    }
+
+    pr_err("%s\n",__func__);
+    return ret;
+}
+
+int u_audio_start_playback(struct g_audio *audio_dev)
+{
+    if (work_pending(&audio_dev->control_work.work))
+        cancel_delayed_work(&audio_dev->control_work);
+
+    u_audio_start_playback_impl(audio_dev);
+    audio_dev->playback_enabled = true;
+    schedule_delayed_work(&audio_dev->control_work, 0);
+    pr_err("%s\n",__func__);
+    return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_playback);
+
+void u_audio_stop_playback(struct g_audio *audio_dev)
+{
+    pr_err("%s enter!\n", __func__);
+    free_ep(audio_dev, audio_dev->out_ep, false);
+    cancel_work_sync(&audio_dev->playback_work);
+
+    if (work_pending(&audio_dev->control_work.work))
+        cancel_delayed_work(&audio_dev->control_work);
+
+    audio_dev->playback_enabled = false;
+    schedule_delayed_work(&audio_dev->control_work, 0);
+    pr_err("%s leave!\n", __func__);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_playback);
+
+
+/**
+ * Open ALSA PCM and control device files
+ * Initial the PCM or control device
+ */
+static int gaudio_open_snd_dev(struct g_audio *card)
+{
+	struct snd_pcm_file *pcm_file;
+	struct gaudio_snd_dev *snd;
+
+	//////////////////////////////////////////////////TODO FOR AUDIO FILES
+	return 0;
+
+	if (!card)
+		return -ENODEV;
+
+	/* Open control device */
+	snd = &card->control;
+	snd->filp = filp_open(fn_cntl, O_RDWR, 0);
+	if (IS_ERR(snd->filp)) {
+		int ret = PTR_ERR(snd->filp);
+		pr_err("%s unable to open sound control device file: %s\n",
+				__func__, fn_cntl);
+		snd->filp = NULL;
+		return ret;
+	}
+	snd->card = card;
+
+	/* Open PCM playback device and setup substream */
+	snd = &card->playback;
+	snd->filp = filp_open(fn_play, O_WRONLY, 0);
+	if (IS_ERR(snd->filp)) {
+		int ret = PTR_ERR(snd->filp);
+
+		pr_err("%s No such PCM playback device: %s\n", __func__, fn_play);
+		snd->filp = NULL;
+		return ret;
+	}
+	pcm_file = snd->filp->private_data;
+	snd->substream = pcm_file->substream;
+	snd->card = card;
+	playback_default_hw_params(snd);
+
+	/* Open PCM capture device and setup substream */
+	snd = &card->capture;
+	snd->filp = filp_open(fn_cap, O_RDONLY, 0);
+	if (IS_ERR(snd->filp)) {
+		pr_err("%s No such PCM capture device: %s\n", __func__, fn_cap);
+		snd->substream = NULL;
+		snd->card = NULL;
+		snd->filp = NULL;
+	} else {
+		pcm_file = snd->filp->private_data;
+		snd->substream = pcm_file->substream;
+		snd->card = card;
+	}
+
+	return 0;
+}
+
+/**
+ * Close ALSA PCM and control device files
+ */
+static int gaudio_close_snd_dev(struct g_audio *gau)
+{
+	struct gaudio_snd_dev	*snd;
+
+	/* Close control device */
+	snd = &gau->control;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	/* Close PCM playback device and setup substream */
+	snd = &gau->playback;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	/* Close PCM capture device and setup substream */
+	snd = &gau->capture;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	return 0;
+}
+
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+					const char *card_name)
+{
+	int	ret;
+
+	ret = gaudio_open_snd_dev(g_audio);
+	if (ret)
+		pr_info("%s gaudio_open_snd_dev failed\n", __func__);
+    g_audio_init(g_audio); 
+	return ret;
+}
+EXPORT_SYMBOL_GPL(g_audio_setup);
+
+void g_audio_cleanup(struct g_audio *g_audio)
+{
+	if (!g_audio) {
+		pr_err("g_audio_cleanup: g_audio is NULL\n");
+		return;
+	}
+	g_audio->in_enabled = false;
+	g_audio->out_enabled = false;
+	g_audio->capture_enabled = false;
+	g_audio->playback_enabled = false;
+	cancel_work_sync(&g_audio->playback_work);
+	cancel_work_sync(&g_audio->capture_work);
+	gaudio_close_snd_dev(g_audio);
+}
+EXPORT_SYMBOL_GPL(g_audio_cleanup);
+
+void g_audio_init(struct g_audio *g_audio)
+{
+	if (!g_audio) {
+		pr_info("%s:g_audio is NULL\n", __func__);
+		return;
+	}
+    INIT_WORK(&g_audio->playback_work, u_audio_playback_work);
+    INIT_LIST_HEAD(&g_audio->play_queue);
+    spin_lock_init(&g_audio->lock);
+
+    INIT_WORK(&g_audio->capture_work, u_audio_capture_work);
+    INIT_LIST_HEAD(&g_audio->capture_queue);
+    spin_lock_init(&g_audio->cap_lock);
+
+    INIT_DELAYED_WORK(&g_audio->control_work, u_audio_control_work);
+}
+EXPORT_SYMBOL_GPL(g_audio_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.h b/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.h
new file mode 100644
index 0000000..457c3ba
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_asr_uac1.h
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_audio.h -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ */
+
+#ifndef __U_ASR_UAC1_H
+#define __U_ASR_UAC1_H
+
+#include <linux/usb/composite.h>
+
+struct uac_params {
+	/* playback */
+	int p_chmask;	/* channel mask */
+	int p_srate;	/* rate in Hz */
+	int p_ssize;	/* sample size */
+
+	/* capture */
+	int c_chmask;	/* channel mask */
+	int c_srate;	/* rate in Hz */
+	int c_ssize;	/* sample size */
+
+	int req_number; /* number of preallocated requests */
+};
+
+/*
+ * This represents the USB side of an audio card device, managed by a USB
+ * function which provides control and stream interfaces.
+ */
+
+struct g_audio;
+struct gaudio_snd_dev {
+	struct g_audio			*card;
+	struct file			*filp;
+	struct snd_pcm_substream	*substream;
+	int				access;
+	int				format;
+	int				channels;
+	int				rate;
+};
+
+#ifdef CONFIG_USB_DWC3
+#define NR_USB_AUDIO_REQ	(64)
+#else
+#define NR_USB_AUDIO_REQ	(32)
+#endif
+
+struct g_audio {
+	struct usb_function func;
+	struct usb_gadget *gadget;
+
+	struct usb_ep *in_ep;
+	struct usb_ep *out_ep;
+	bool in_enabled;
+	bool out_enabled;
+	bool capture_enabled;
+	bool playback_enabled;
+
+	/* Max packet size for all in_ep possible speeds */
+	unsigned int in_ep_maxpsize;
+	/* Max packet size for all out_ep possible speeds */
+	unsigned int out_ep_maxpsize;
+
+	/* The ALSA Sound Card it represents on the USB-Client side */
+	struct snd_uac_chip *uac;
+
+	struct uac_params params;
+
+	struct usb_request *in_req[NR_USB_AUDIO_REQ];
+	struct usb_request *out_req[NR_USB_AUDIO_REQ];
+
+	/* ALSA sound device interfaces */
+	struct gaudio_snd_dev		control;
+	struct gaudio_snd_dev		playback;
+	struct gaudio_snd_dev		capture;
+
+	spinlock_t			lock;
+	struct f_audio_buf *copy_buf;
+	struct work_struct playback_work;
+	struct list_head play_queue;
+
+	spinlock_t			cap_lock;
+	struct f_audio_buf *cap_copy_buf;
+	struct work_struct capture_work;
+	struct list_head capture_queue;
+	
+    struct delayed_work control_work;
+};
+
+struct f_audio_buf {
+	u8 *buf;
+	int actual;
+	struct list_head list;
+};
+
+static inline struct g_audio *func_to_g_audio(struct usb_function *f)
+{
+	return container_of(f, struct g_audio, func);
+}
+
+static inline uint num_channels(uint chanmask)
+{
+	uint num = 0;
+
+	while (chanmask) {
+		num += (chanmask & 1);
+		chanmask >>= 1;
+	}
+
+	if (num == 0)
+        num = 1;
+
+	return num;
+}
+
+/*
+ * g_audio_setup - initialize one virtual ALSA sound card
+ * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize
+ * @pcm_name: the id string for a PCM instance of this sound card
+ * @card_name: name of this soundcard
+ *
+ * This sets up the single virtual ALSA sound card that may be exported by a
+ * gadget driver using this framework.
+ *
+ * Context: may sleep
+ *
+ * Returns zero on success, or a negative error on failure.
+ */
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+					const char *card_name);
+void g_audio_cleanup(struct g_audio *g_audio);
+
+int u_audio_start_capture(struct g_audio *g_audio);
+void u_audio_stop_capture(struct g_audio *g_audio);
+int u_audio_start_playback(struct g_audio *g_audio);
+void u_audio_stop_playback(struct g_audio *g_audio);
+void g_audio_init(struct g_audio *g_audio);
+
+#endif /* __U_AUDIO_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_audio.c b/marvell/linux/drivers/usb/gadget/function/u_audio.c
new file mode 100644
index 0000000..994316d
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_audio.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_audio.c -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * Sound card implementation was cut-and-pasted with changes
+ * from f_uac2.c and has:
+ *    Copyright (C) 2011
+ *    Yadwinder Singh (yadi.brar01@gmail.com)
+ *    Jaswinder Singh (jaswinder.singh@linaro.org)
+ */
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "u_audio.h"
+
+#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
+#define PRD_SIZE_MAX	PAGE_SIZE
+#define MIN_PERIODS	4
+
+struct uac_req {
+	struct uac_rtd_params *pp; /* parent param */
+	struct usb_request *req;
+};
+
+/* Runtime data params for one stream */
+struct uac_rtd_params {
+	struct snd_uac_chip *uac; /* parent chip */
+	bool ep_enabled; /* if the ep is enabled */
+
+	struct snd_pcm_substream *ss;
+
+	/* Ring buffer */
+	ssize_t hw_ptr;
+
+	void *rbuf;
+
+	unsigned int max_psize;	/* MaxPacketSize of endpoint */
+	struct uac_req *ureq;
+
+	spinlock_t lock;
+};
+
+struct snd_uac_chip {
+	struct g_audio *audio_dev;
+
+	struct uac_rtd_params p_prm;
+	struct uac_rtd_params c_prm;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+
+	/* timekeeping for the playback endpoint */
+	unsigned int p_interval;
+	unsigned int p_residue;
+
+	/* pre-calculated values for playback iso completion */
+	unsigned int p_pktsize;
+	unsigned int p_pktsize_residue;
+	unsigned int p_framesize;
+};
+
+static const struct snd_pcm_hardware uac_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
+		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
+	.buffer_bytes_max = BUFF_SIZE_MAX,
+	.period_bytes_max = PRD_SIZE_MAX,
+	.periods_min = MIN_PERIODS,
+};
+
+static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	unsigned int pending;
+	unsigned long flags, flags2;
+	unsigned int hw_ptr;
+	int status = req->status;
+	struct uac_req *ur = req->context;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	struct uac_rtd_params *prm = ur->pp;
+	struct snd_uac_chip *uac = prm->uac;
+
+	/* i/f shutting down */
+	if (!prm->ep_enabled) {
+		usb_ep_free_request(ep, req);
+		return;
+	}
+
+	if (req->status == -ESHUTDOWN)
+		return;
+
+	/*
+	 * We can't really do much about bad xfers.
+	 * Afterall, the ISOCH xfers could fail legitimately.
+	 */
+	if (status)
+		pr_debug("%s: iso_complete status(%d) %d/%d\n",
+			__func__, status, req->actual, req->length);
+
+	substream = prm->ss;
+
+	/* Do nothing if ALSA isn't active */
+	if (!substream)
+		goto exit;
+
+	snd_pcm_stream_lock_irqsave(substream, flags2);
+
+	runtime = substream->runtime;
+	if (!runtime || !snd_pcm_running(substream)) {
+		snd_pcm_stream_unlock_irqrestore(substream, flags2);
+		goto exit;
+	}
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/*
+		 * For each IN packet, take the quotient of the current data
+		 * rate and the endpoint's interval as the base packet size.
+		 * If there is a residue from this division, add it to the
+		 * residue accumulator.
+		 */
+		req->length = uac->p_pktsize;
+		uac->p_residue += uac->p_pktsize_residue;
+
+		/*
+		 * Whenever there are more bytes in the accumulator than we
+		 * need to add one more sample frame, increase this packet's
+		 * size and decrease the accumulator.
+		 */
+		if (uac->p_residue / uac->p_interval >= uac->p_framesize) {
+			req->length += uac->p_framesize;
+			uac->p_residue -= uac->p_framesize *
+					   uac->p_interval;
+		}
+
+		req->actual = req->length;
+	}
+
+	hw_ptr = prm->hw_ptr;
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Pack USB load in ALSA ring buffer */
+	pending = runtime->dma_bytes - hw_ptr;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (unlikely(pending < req->actual)) {
+			memcpy(req->buf, runtime->dma_area + hw_ptr, pending);
+			memcpy(req->buf + pending, runtime->dma_area,
+			       req->actual - pending);
+		} else {
+			memcpy(req->buf, runtime->dma_area + hw_ptr,
+			       req->actual);
+		}
+	} else {
+		if (unlikely(pending < req->actual)) {
+			memcpy(runtime->dma_area + hw_ptr, req->buf, pending);
+			memcpy(runtime->dma_area, req->buf + pending,
+			       req->actual - pending);
+		} else {
+			memcpy(runtime->dma_area + hw_ptr, req->buf,
+			       req->actual);
+		}
+	}
+
+	spin_lock_irqsave(&prm->lock, flags);
+	/* update hw_ptr after data is copied to memory */
+	prm->hw_ptr = (hw_ptr + req->actual) % runtime->dma_bytes;
+	hw_ptr = prm->hw_ptr;
+	spin_unlock_irqrestore(&prm->lock, flags);
+	snd_pcm_stream_unlock_irqrestore(substream, flags2);
+
+	if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual)
+		snd_pcm_period_elapsed(substream);
+
+exit:
+	if (usb_ep_queue(ep, req, GFP_ATOMIC))
+		dev_err(uac->card->dev, "%d Error!\n", __LINE__);
+}
+
+static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+	struct uac_rtd_params *prm;
+	struct g_audio *audio_dev;
+	struct uac_params *params;
+	unsigned long flags;
+	int err = 0;
+
+	audio_dev = uac->audio_dev;
+	params = &audio_dev->params;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac->p_prm;
+	else
+		prm = &uac->c_prm;
+
+	spin_lock_irqsave(&prm->lock, flags);
+
+	/* Reset */
+	prm->hw_ptr = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		prm->ss = substream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		prm->ss = NULL;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&prm->lock, flags);
+
+	/* Clear buffer after Play stops */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
+		memset(prm->rbuf, 0, prm->max_psize * params->req_number);
+
+	return err;
+}
+
+static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+	struct uac_rtd_params *prm;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prm = &uac->p_prm;
+	else
+		prm = &uac->c_prm;
+
+	return bytes_to_frames(substream->runtime, prm->hw_ptr);
+}
+
+static int uac_pcm_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int uac_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int uac_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct g_audio *audio_dev;
+	struct uac_params *params;
+	int p_ssize, c_ssize;
+	int p_srate, c_srate;
+	int p_chmask, c_chmask;
+
+	audio_dev = uac->audio_dev;
+	params = &audio_dev->params;
+	p_ssize = params->p_ssize;
+	c_ssize = params->c_ssize;
+	p_srate = params->p_srate;
+	c_srate = params->c_srate;
+	p_chmask = params->p_chmask;
+	c_chmask = params->c_chmask;
+	uac->p_residue = 0;
+
+	runtime->hw = uac_pcm_hardware;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		spin_lock_init(&uac->p_prm.lock);
+		runtime->hw.rate_min = p_srate;
+		switch (p_ssize) {
+		case 3:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			break;
+		case 4:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+			break;
+		default:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+			break;
+		}
+		runtime->hw.channels_min = num_channels(p_chmask);
+		runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize
+						/ runtime->hw.periods_min;
+	} else {
+		spin_lock_init(&uac->c_prm.lock);
+		runtime->hw.rate_min = c_srate;
+		switch (c_ssize) {
+		case 3:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
+			break;
+		case 4:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+			break;
+		default:
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+			break;
+		}
+		runtime->hw.channels_min = num_channels(c_chmask);
+		runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize
+						/ runtime->hw.periods_min;
+	}
+
+	runtime->hw.rate_max = runtime->hw.rate_min;
+	runtime->hw.channels_max = runtime->hw.channels_min;
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return 0;
+}
+
+/* ALSA cries without these function pointers */
+static int uac_pcm_null(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static const struct snd_pcm_ops uac_pcm_ops = {
+	.open = uac_pcm_open,
+	.close = uac_pcm_null,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = uac_pcm_hw_params,
+	.hw_free = uac_pcm_hw_free,
+	.trigger = uac_pcm_trigger,
+	.pointer = uac_pcm_pointer,
+	.prepare = uac_pcm_null,
+};
+
+static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
+{
+	struct snd_uac_chip *uac = prm->uac;
+	struct g_audio *audio_dev;
+	struct uac_params *params;
+	int i;
+
+	if (!prm->ep_enabled)
+		return;
+
+	audio_dev = uac->audio_dev;
+	params = &audio_dev->params;
+
+	for (i = 0; i < params->req_number; i++) {
+		if (prm->ureq[i].req) {
+			if (usb_ep_dequeue(ep, prm->ureq[i].req))
+				usb_ep_free_request(ep, prm->ureq[i].req);
+			/*
+			 * If usb_ep_dequeue() cannot successfully dequeue the
+			 * request, the request will be freed by the completion
+			 * callback.
+			 */
+
+			prm->ureq[i].req = NULL;
+		}
+	}
+
+	prm->ep_enabled = false;
+
+	if (usb_ep_disable(ep))
+		dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
+}
+
+int u_audio_start_capture(struct g_audio *audio_dev)
+{
+	struct snd_uac_chip *uac = audio_dev->uac;
+	struct usb_gadget *gadget = audio_dev->gadget;
+	struct device *dev = &gadget->dev;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	struct uac_rtd_params *prm;
+	struct uac_params *params = &audio_dev->params;
+	int req_len, i;
+
+	ep = audio_dev->out_ep;
+	prm = &uac->c_prm;
+	config_ep_by_speed(gadget, &audio_dev->func, ep);
+	req_len = prm->max_psize;
+
+	prm->ep_enabled = true;
+	usb_ep_enable(ep);
+
+	for (i = 0; i < params->req_number; i++) {
+		if (!prm->ureq[i].req) {
+			req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+			if (req == NULL)
+				return -ENOMEM;
+
+			prm->ureq[i].req = req;
+			prm->ureq[i].pp = prm;
+
+			req->zero = 0;
+			req->context = &prm->ureq[i];
+			req->length = req_len;
+			req->complete = u_audio_iso_complete;
+			req->buf = prm->rbuf + i * prm->max_psize;
+		}
+
+		if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_capture);
+
+void u_audio_stop_capture(struct g_audio *audio_dev)
+{
+	struct snd_uac_chip *uac = audio_dev->uac;
+
+	free_ep(&uac->c_prm, audio_dev->out_ep);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_capture);
+
+int u_audio_start_playback(struct g_audio *audio_dev)
+{
+	struct snd_uac_chip *uac = audio_dev->uac;
+	struct usb_gadget *gadget = audio_dev->gadget;
+	struct device *dev = &gadget->dev;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	struct uac_rtd_params *prm;
+	struct uac_params *params = &audio_dev->params;
+	unsigned int factor, rate;
+	const struct usb_endpoint_descriptor *ep_desc;
+	int req_len, i;
+
+	ep = audio_dev->in_ep;
+	prm = &uac->p_prm;
+	config_ep_by_speed(gadget, &audio_dev->func, ep);
+
+	ep_desc = ep->desc;
+
+	/* pre-calculate the playback endpoint's interval */
+	if (gadget->speed == USB_SPEED_FULL)
+		factor = 1000;
+	else
+		factor = 8000;
+
+	/* pre-compute some values for iso_complete() */
+	uac->p_framesize = params->p_ssize *
+			    num_channels(params->p_chmask);
+	rate = params->p_srate * uac->p_framesize;
+	uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
+	uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval,
+				prm->max_psize);
+
+	if (uac->p_pktsize < prm->max_psize)
+		uac->p_pktsize_residue = rate % uac->p_interval;
+	else
+		uac->p_pktsize_residue = 0;
+
+	req_len = uac->p_pktsize;
+	uac->p_residue = 0;
+
+	prm->ep_enabled = true;
+	usb_ep_enable(ep);
+
+	for (i = 0; i < params->req_number; i++) {
+		if (!prm->ureq[i].req) {
+			req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+			if (req == NULL)
+				return -ENOMEM;
+
+			prm->ureq[i].req = req;
+			prm->ureq[i].pp = prm;
+
+			req->zero = 0;
+			req->context = &prm->ureq[i];
+			req->length = req_len;
+			req->complete = u_audio_iso_complete;
+			req->buf = prm->rbuf + i * prm->max_psize;
+		}
+
+		if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+			dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_playback);
+
+void u_audio_stop_playback(struct g_audio *audio_dev)
+{
+	struct snd_uac_chip *uac = audio_dev->uac;
+
+	free_ep(&uac->p_prm, audio_dev->in_ep);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_playback);
+
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+					const char *card_name)
+{
+	struct snd_uac_chip *uac;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct uac_params *params;
+	int p_chmask, c_chmask;
+	int err;
+
+	if (!g_audio)
+		return -EINVAL;
+
+	uac = kzalloc(sizeof(*uac), GFP_KERNEL);
+	if (!uac)
+		return -ENOMEM;
+	g_audio->uac = uac;
+	uac->audio_dev = g_audio;
+
+	params = &g_audio->params;
+	p_chmask = params->p_chmask;
+	c_chmask = params->c_chmask;
+
+	if (c_chmask) {
+		struct uac_rtd_params *prm = &uac->c_prm;
+
+		uac->c_prm.uac = uac;
+		prm->max_psize = g_audio->out_ep_maxpsize;
+
+		prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
+				GFP_KERNEL);
+		if (!prm->ureq) {
+			err = -ENOMEM;
+			goto fail;
+		}
+
+		prm->rbuf = kcalloc(params->req_number, prm->max_psize,
+				GFP_KERNEL);
+		if (!prm->rbuf) {
+			prm->max_psize = 0;
+			err = -ENOMEM;
+			goto fail;
+		}
+	}
+
+	if (p_chmask) {
+		struct uac_rtd_params *prm = &uac->p_prm;
+
+		uac->p_prm.uac = uac;
+		prm->max_psize = g_audio->in_ep_maxpsize;
+
+		prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
+				GFP_KERNEL);
+		if (!prm->ureq) {
+			err = -ENOMEM;
+			goto fail;
+		}
+
+		prm->rbuf = kcalloc(params->req_number, prm->max_psize,
+				GFP_KERNEL);
+		if (!prm->rbuf) {
+			prm->max_psize = 0;
+			err = -ENOMEM;
+			goto fail;
+		}
+	}
+
+	/* Choose any slot, with no id */
+	err = snd_card_new(&g_audio->gadget->dev,
+			-1, NULL, THIS_MODULE, 0, &card);
+	if (err < 0)
+		goto fail;
+
+	uac->card = card;
+
+	/*
+	 * Create first PCM device
+	 * Create a substream only for non-zero channel streams
+	 */
+	err = snd_pcm_new(uac->card, pcm_name, 0,
+			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
+	if (err < 0)
+		goto snd_fail;
+
+	strlcpy(pcm->name, pcm_name, sizeof(pcm->name));
+	pcm->private_data = uac;
+	uac->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);
+
+	strlcpy(card->driver, card_name, sizeof(card->driver));
+	strlcpy(card->shortname, card_name, sizeof(card->shortname));
+	sprintf(card->longname, "%s %i", card_name, card->dev->id);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
+
+	err = snd_card_register(card);
+
+	if (!err)
+		return 0;
+
+snd_fail:
+	snd_card_free(card);
+fail:
+	kfree(uac->p_prm.ureq);
+	kfree(uac->c_prm.ureq);
+	kfree(uac->p_prm.rbuf);
+	kfree(uac->c_prm.rbuf);
+	kfree(uac);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(g_audio_setup);
+
+void g_audio_cleanup(struct g_audio *g_audio)
+{
+	struct snd_uac_chip *uac;
+	struct snd_card *card;
+
+	if (!g_audio || !g_audio->uac)
+		return;
+
+	uac = g_audio->uac;
+	g_audio->uac = NULL;
+
+	card = uac->card;
+	if (card)
+		snd_card_free_when_closed(card);
+
+	kfree(uac->p_prm.ureq);
+	kfree(uac->c_prm.ureq);
+	kfree(uac->p_prm.rbuf);
+	kfree(uac->c_prm.rbuf);
+	kfree(uac);
+}
+EXPORT_SYMBOL_GPL(g_audio_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/marvell/linux/drivers/usb/gadget/function/u_audio.h b/marvell/linux/drivers/usb/gadget/function/u_audio.h
new file mode 100644
index 0000000..81d3d4e
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_audio.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_audio.h -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ */
+
+#ifndef __U_AUDIO_H
+#define __U_AUDIO_H
+
+#include <linux/usb/composite.h>
+
+struct uac_params {
+	/* playback */
+	int p_chmask;	/* channel mask */
+	int p_srate;	/* rate in Hz */
+	int p_ssize;	/* sample size */
+
+	/* capture */
+	int c_chmask;	/* channel mask */
+	int c_srate;	/* rate in Hz */
+	int c_ssize;	/* sample size */
+
+	int req_number; /* number of preallocated requests */
+};
+
+struct g_audio {
+	struct usb_function func;
+	struct usb_gadget *gadget;
+
+	struct usb_ep *in_ep;
+	struct usb_ep *out_ep;
+
+	/* Max packet size for all in_ep possible speeds */
+	unsigned int in_ep_maxpsize;
+	/* Max packet size for all out_ep possible speeds */
+	unsigned int out_ep_maxpsize;
+
+	/* The ALSA Sound Card it represents on the USB-Client side */
+	struct snd_uac_chip *uac;
+
+	struct uac_params params;
+};
+
+static inline struct g_audio *func_to_g_audio(struct usb_function *f)
+{
+	return container_of(f, struct g_audio, func);
+}
+
+static inline uint num_channels(uint chanmask)
+{
+	uint num = 0;
+
+	while (chanmask) {
+		num += (chanmask & 1);
+		chanmask >>= 1;
+	}
+
+	return num;
+}
+
+/*
+ * g_audio_setup - initialize one virtual ALSA sound card
+ * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize
+ * @pcm_name: the id string for a PCM instance of this sound card
+ * @card_name: name of this soundcard
+ *
+ * This sets up the single virtual ALSA sound card that may be exported by a
+ * gadget driver using this framework.
+ *
+ * Context: may sleep
+ *
+ * Returns zero on success, or a negative error on failure.
+ */
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+					const char *card_name);
+void g_audio_cleanup(struct g_audio *g_audio);
+
+int u_audio_start_capture(struct g_audio *g_audio);
+void u_audio_stop_capture(struct g_audio *g_audio);
+int u_audio_start_playback(struct g_audio *g_audio);
+void u_audio_stop_playback(struct g_audio *g_audio);
+
+#endif /* __U_AUDIO_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ecm.h b/marvell/linux/drivers/usb/gadget/function/u_ecm.h
new file mode 100644
index 0000000..098ece5
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ecm.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_ecm.h
+ *
+ * Utility definitions for the ecm function
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_ECM_H
+#define U_ECM_H
+
+#include <linux/usb/composite.h>
+
+struct f_ecm_opts {
+	struct usb_function_instance	func_inst;
+	struct net_device		*net;
+	bool				bound;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_ECM_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_eem.h b/marvell/linux/drivers/usb/gadget/function/u_eem.h
new file mode 100644
index 0000000..921386a
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_eem.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_eem.h
+ *
+ * Utility definitions for the eem function
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_EEM_H
+#define U_EEM_H
+
+#include <linux/usb/composite.h>
+
+struct f_eem_opts {
+	struct usb_function_instance	func_inst;
+	struct net_device		*net;
+	bool				bound;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_EEM_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ether.c b/marvell/linux/drivers/usb/gadget/function/u_ether.c
new file mode 100644
index 0000000..91c51cd
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ether.c
@@ -0,0 +1,2130 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+#include <linux/ctype.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_vlan.h>
+#include <linux/interrupt.h>
+#include <linux/if_vlan.h>
+#include <linux/pm_qos.h>
+#include <linux/mm.h>
+#include <linux/suspend.h>
+#include "u_ether.h"
+/*
+ * This component encapsulates the Ethernet link glue needed to provide
+ * one (!) network link through the USB gadget stack, normally "usb0".
+ *
+ * The control and data models are handled by the function driver which
+ * connects to this code; such as CDC Ethernet (ECM or EEM),
+ * "CDC Subset", or RNDIS.  That includes all descriptor and endpoint
+ * management.
+ *
+ * Link level addressing is handled by this component using module
+ * parameters; if no such parameters are provided, random link level
+ * addresses are used.  Each end of the link uses one address.  The
+ * host end address is exported in various ways, and is often recorded
+ * in configuration databases.
+ *
+ * The driver which assembles each configuration using such a link is
+ * responsible for ensuring that each configuration includes at most one
+ * instance of is network link.  (The network layer provides ways for
+ * this single "physical" link to be used by multiple virtual links.)
+ */
+
+#define UETH__VERSION	"29-May-2008"
+
+#ifdef CONFIG_CPU_ASR1903
+#define CONFIG_USB_GADGET_DEBUG_FILES	1
+extern u32 nr_aggr_fail_padding;
+#endif
+
+struct eth_dev {
+	/* lock is held while accessing port_usb
+	 */
+	spinlock_t		lock;
+	struct gether		*port_usb;
+
+	struct net_device	*net;
+	struct usb_gadget	*gadget;
+
+	spinlock_t		req_lock;	/* guard {rx,tx}_reqs */
+	struct list_head	tx_reqs, rx_reqs;
+	unsigned		tx_qlen;
+	atomic_t		no_tx_req_used;
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	int			*tx_mult_histogram;
+#endif
+
+	struct sk_buff_head	rx_frames;
+
+	unsigned		header_len;
+	unsigned int		ul_max_pkts_per_xfer;
+	struct sk_buff		*(*wrap)(struct gether *,
+						struct sk_buff *skb,
+						struct aggr_ctx *aggr_ctx);
+	int			(*unwrap)(struct gether *,
+						struct sk_buff *skb,
+						struct sk_buff_head *list);
+	struct sk_buff		*(*unwrap_fixup)(struct gether *,
+						struct sk_buff *skb);
+
+	struct work_struct	work;
+	struct work_struct	rx_work;
+	struct tasklet_struct	rx_tl;
+
+	unsigned long		todo;
+#define	WORK_RX_MEMORY		0
+
+	bool			zlp;
+	u8			host_mac[ETH_ALEN];
+
+#ifdef CONFIG_DDR_DEVFREQ
+/* for nezhas the boost freq is 355mhz, while 398mhz for nezha3 */
+#define DDR_BOOST_FREQ		(350000)
+#if defined(CONFIG_CPU_ASR18XX) && defined(CONFIG_USB_MV_UDC) 
+#define INTERVALS_PER_SEC	(50)
+#else
+#define INTERVALS_PER_SEC	(10)
+#endif
+
+#if defined(CONFIG_CPU_ASR18XX)
+#ifdef CONFIG_USB_MV_UDC
+#define DDR_TX_BOOST_BYTES	(( 10000000 / INTERVALS_PER_SEC) >> 3) /* 10mbps*/
+#define DDR_RX_BOOST_BYTES	(( 1000000 / INTERVALS_PER_SEC) >> 3)  /* 1mbps*/
+#else
+#define DDR_TX_BOOST_BYTES	(( 15000000 / INTERVALS_PER_SEC) >> 3) /* 15mbps*/
+#define DDR_RX_BOOST_BYTES	(( 15000000 / INTERVALS_PER_SEC) >> 3)  /* 15mbps*/
+#endif
+#else
+#define DDR_TX_BOOST_BYTES	((150000000 / INTERVALS_PER_SEC) >> 3) /* 150mbps*/
+#define DDR_RX_BOOST_BYTES	(( 20000000 / INTERVALS_PER_SEC) >> 3)  /* 20mbps*/
+#endif
+
+	atomic_t		no_rx_skb;
+	unsigned int		tx_boost_threshhold;
+	unsigned int		rx_boost_threshhold;
+	struct pm_qos_request	ddr_qos_min;
+	struct delayed_work	txrx_monitor_work;
+	bool			dwork_inited;
+#endif
+};
+
+/*-------------------------------------------------------------------------*/
+
+#define AGGR_DONE(req)	\
+		(AGGRCTX(req)->pending_skb || \
+		AGGRCTX(req)->total_size == 0)
+
+#define RX_EXTRA	20	/* bytes guarding against rx overflows */
+#define DEFAULT_QLEN	5	/* quintuple buffering by default */
+
+#ifdef CONFIG_CPU_ASR1903
+static unsigned qmult_rx = 20;
+#else
+static unsigned qmult_rx = 15;
+#endif
+static unsigned qmult_tx = 40;
+static struct net_device *g_usbnet_dev;
+#ifdef CONFIG_CPU_ASR1903
+static unsigned rx_notl = 0;
+#else
+static unsigned rx_notl = 0;
+#endif
+/* 
+ * TX_REQ_THRESHOLD is used for two different (but related) optimizations:
+ * 1. IRQ_OPTIMIZATION -> getting complete interrupt every TX_REQ_THRESHOLD requestes.
+ * 2. USB_AGGRIGATION -> Start aggregate only after USB has more then TX_REQ_THRESHOLD requestes.
+ * --> To prevent packet delay the value for #1 and #2 MUST be the same.
+ * NOTE: The value of TX_REQ_THRESHOLD must be smaller then half of the queue size
+ *       e.g. TX_REQ_THRESHOLD_MAX <= DEFAULT_QLEN * (qmult_tx / 2) 
+ */
+#ifdef CONFIG_CPU_ASR1903
+#define TX_REQ_THRESHOLD	(1)
+#else
+#define TX_REQ_THRESHOLD	(DEFAULT_QLEN * 4)
+#endif
+
+#define USB_ALLOC_MEM_LOW_THRESH	(2 * 1024 * 1024)
+
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+static unsigned histogram_size;
+#endif
+
+//only asr1803 need to use work
+#ifdef CONFIG_USB_MV_UDC
+#define USB_RX_USE_WORK	1
+#endif
+
+module_param(qmult_rx, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qmult_rx, "rx queue length multiplier at high/super speed");
+module_param(qmult_tx, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qmult_tx, "tx queue length multiplier at high/super speed");
+module_param(rx_notl, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(rx_notl, "rx not use tasklet");
+
+static unsigned rx_boost_thr = DDR_RX_BOOST_BYTES;
+module_param(rx_boost_thr, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(rx_boost_thr, "rx ddr boost threshold");
+
+#ifdef CONFIG_USBNET_USE_SG
+static bool sg_is_enabled;
+static struct scatterlist *
+alloc_sglist(int nents)
+{
+	struct scatterlist	*sg;
+
+	sg = kmalloc_array(nents, sizeof *sg, GFP_KERNEL);
+	if (!sg) {
+		pr_err("usbnet error: sg alloc failed\n");
+		return NULL;
+	}
+	sg_init_table(sg, nents);
+	if (!sg) {
+		pr_err("usbnet error: sg_init_table failed\n");
+		return NULL;
+	}
+	return sg;
+}
+
+static void
+build_sglist(struct usb_request *req, struct aggr_ctx* aggrctx)
+{
+	struct scatterlist	*sg = aggrctx->sg;
+	struct sk_buff *cur, *next;
+	int nr_pkts = 0;
+
+	if (unlikely(!sg)) {
+		pr_err("sg of aggr is NULL\n");
+		BUG();
+	}
+
+	skb_queue_walk_safe(&AGGRCTX(req)->skb_list, cur, next) {
+		sg_set_buf(&sg[nr_pkts], (void *)cur->data, cur->len);
+		nr_pkts++;
+	}
+	if (nr_pkts == 0 || nr_pkts > USBNET_SG_NENTS) {
+		pr_err("error pkts: %d\n", nr_pkts);
+		BUG();
+	}
+	sg_mark_end(&sg[nr_pkts - 1]);
+
+	req->sg = sg;
+	req->num_sgs = nr_pkts;
+}
+#endif
+
+/* for dual-speed hardware, use deeper queues at high/super speed */
+static inline int qlen(struct usb_gadget *gadget, bool rx)
+{
+	if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH ||
+					    gadget->speed == USB_SPEED_SUPER))
+		return DEFAULT_QLEN * (rx ? qmult_rx : qmult_tx);
+	else
+		return DEFAULT_QLEN;
+}
+
+int u_ether_rx_qlen(void)
+{
+	return qmult_rx * DEFAULT_QLEN;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* REVISIT there must be a better way than having two sets
+ * of debug calls ...
+ */
+#if 0
+#undef DBG
+#undef VDBG
+#undef ERROR
+#undef INFO
+
+#define xprintk(d, level, fmt, args...) \
+	printk(level "%s: " fmt , (d)->net->name , ## args)
+
+#ifdef DEBUG
+#undef DEBUG
+#define DBG(dev, fmt, args...) \
+	xprintk(dev , KERN_DEBUG , fmt , ## args)
+#else
+#define DBG(dev, fmt, args...) \
+	do { } while (0)
+#endif /* DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VDBG	DBG
+#else
+#define VDBG(dev, fmt, args...) \
+	do { } while (0)
+#endif /* DEBUG */
+
+#define ERROR(dev, fmt, args...) \
+	xprintk(dev , KERN_ERR , fmt , ## args)
+#define INFO(dev, fmt, args...) \
+	xprintk(dev , KERN_INFO , fmt , ## args)
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+static void histogram_realloc(struct eth_dev *dev, int skb_qlen)
+{
+	int *tmp, *oldbuf;
+	int size = histogram_size;
+
+	while (size <= skb_qlen)
+		size = size * 2;
+
+	tmp = kzalloc(sizeof(int) * size, GFP_ATOMIC);
+	if (!tmp){
+		histogram_size = 0;
+		kfree(dev->tx_mult_histogram);
+		dev->tx_mult_histogram = NULL;		
+	}
+
+	memcpy(tmp, dev->tx_mult_histogram, sizeof(int) * histogram_size);
+	oldbuf = dev->tx_mult_histogram;
+	dev->tx_mult_histogram = tmp;
+	histogram_size = size;
+	if (oldbuf)
+		kfree(oldbuf);
+	else
+		BUG();
+}
+
+static ssize_t
+u_ether_histogram_clean(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	if (priv->tx_mult_histogram)
+		memset(priv->tx_mult_histogram, 0, histogram_size * sizeof(int));
+	return size;
+}
+
+static ssize_t
+u_ether_histogram_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	char *fmt = "mult[%d] = %d/100 (%d)\n";
+	int i, ret = 0, total = 0, max_aggr;
+
+	if (!priv->tx_mult_histogram)
+		return 0;
+
+	for (i = 0 ; i < histogram_size; i++)
+		total += priv->tx_mult_histogram[i];
+
+	for (i = histogram_size - 1; i >= 0; i--)
+		if (priv->tx_mult_histogram[i])
+			break;
+	max_aggr = i;
+
+#ifdef CONFIG_CPU_ASR1903
+	ret += sprintf(buf + ret, "padding_fail=%d\n", nr_aggr_fail_padding);
+#endif
+
+	ret += sprintf(buf + ret, "histogram_size = %d\n", histogram_size);
+	for (i = 0; i <= max_aggr; i++)
+		ret += sprintf(buf + ret, fmt, i, priv->tx_mult_histogram[i] * 100 / total, priv->tx_mult_histogram[i]);
+	return ret;
+}
+
+static
+DEVICE_ATTR(u_ether_tx_mult_histogram, S_IRUGO|S_IWUSR, u_ether_histogram_show, u_ether_histogram_clean);
+
+#ifdef CONFIG_DDR_DEVFREQ
+static ssize_t
+tx_boost_thresh_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+
+	sscanf(buf, "%d", &priv->tx_boost_threshhold);
+
+	pr_info("u_ether tx boost threshold set as %d\n",
+				priv->tx_boost_threshhold);
+
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static ssize_t
+tx_boost_thresh_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	int ret = 0;
+
+	ret += sprintf(buf + ret, "tx_boost_threshhold = %d\n",
+				priv->tx_boost_threshhold);
+	return ret;
+}
+static
+DEVICE_ATTR(tx_boost_param, S_IRUGO|S_IWUSR, tx_boost_thresh_show,
+		tx_boost_thresh_store);
+
+static ssize_t
+rx_boost_thresh_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+
+	sscanf(buf, "%d", &priv->rx_boost_threshhold);
+
+	pr_info("u_ether tx boost threshold set as %d\n",
+				priv->rx_boost_threshhold);
+
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static ssize_t
+rx_boost_thresh_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	int ret = 0;
+
+	ret += sprintf(buf + ret, "rx_boost_threshhold = %d\n",
+				priv->rx_boost_threshhold);
+	return ret;
+}
+static
+DEVICE_ATTR(rx_boost_param, S_IRUGO|S_IWUSR, rx_boost_thresh_show,
+		rx_boost_thresh_store);
+#endif
+
+static struct attribute *u_ether_attrs[] = {
+	&dev_attr_u_ether_tx_mult_histogram.attr,
+#ifdef CONFIG_DDR_DEVFREQ
+	&dev_attr_tx_boost_param.attr,
+	&dev_attr_rx_boost_param.attr,
+#endif
+	NULL,
+};
+static struct attribute_group u_ether_attr_group = {
+	.attrs = u_ether_attrs,
+};
+#endif /*CONFIG_USB_GADGET_DEBUG_FILES*/
+
+static inline void req_aggr_clean(struct usb_request *req)
+{
+	struct sk_buff *skb;
+	BUG_ON(!AGGRCTX(req));
+	BUG_ON(AGGRCTX(req)->pending_skb);
+
+	while ((skb = skb_dequeue(&AGGRCTX(req)->skb_list) ))
+		dev_kfree_skb_any(skb);
+
+	AGGRCTX(req)->total_size = 0;
+	req->length = 0;
+	req->buf = NULL;
+#ifdef CONFIG_USBNET_USE_SG
+	AGGRCTX(req)->num_sgs = 0;
+	if (AGGRCTX(req)->sg)
+		sg_init_table(AGGRCTX(req)->sg, USBNET_SG_NENTS);
+	req->num_sgs = 0;
+	req->num_mapped_sgs = 0;
+#endif
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
+
+static int ueth_change_mtu(struct net_device *net, int new_mtu)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	unsigned long	flags;
+	int		status = 0;
+
+	/* don't change MTU on "live" link (peer won't know) */
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		status = -EBUSY;
+	else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN)
+		status = -ERANGE;
+	else
+		net->mtu = new_mtu;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return status;
+}
+
+static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p)
+{
+	struct eth_dev *dev = netdev_priv(net);
+
+	strlcpy(p->driver, "g_ether", sizeof(p->driver));
+	strlcpy(p->version, UETH__VERSION, sizeof(p->version));
+	strlcpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
+	strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
+}
+
+/* REVISIT can also support:
+ *   - WOL (by tracking suspends and issuing remote wakeup)
+ *   - msglevel (implies updated messaging)
+ *   - ... probably more ethtool ops
+ */
+
+static const struct ethtool_ops ops = {
+	.get_drvinfo = eth_get_drvinfo,
+	.get_link = ethtool_op_get_link,
+};
+
+static void defer_kevent(struct eth_dev *dev, int flag)
+{
+	if (test_and_set_bit(flag, &dev->todo))
+		return;
+	if (!schedule_work(&dev->work))
+		ERROR(dev, "kevent %d may have been dropped\n", flag);
+	else
+		DBG(dev, "kevent %d scheduled\n", flag);
+}
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req);
+static void rx_complete_notl(struct usb_ep *ep, struct usb_request *req);
+static void tx_complete(struct usb_ep *ep, struct usb_request *req);
+
+#define EXTRA_SKB_WIFI_HEADROOM 96
+static int
+rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
+{
+	struct sk_buff	*skb;
+	int		retval = -ENOMEM;
+	size_t		size = 0;
+	struct usb_ep	*out;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return -ENOTCONN;
+	}
+
+	/* Padding up to RX_EXTRA handles minor disagreements with host.
+	 * Normally we use the USB "terminate on short read" convention;
+	 * so allow up to (N*maxpacket), since that memory is normally
+	 * already allocated.  Some hardware doesn't deal well with short
+	 * reads (e.g. DMA must be N*maxpacket), so for now don't trim a
+	 * byte off the end (to force hardware errors on overflow).
+	 *
+	 * RNDIS uses internal framing, and explicitly allows senders to
+	 * pad to end-of-packet.  That's potentially nice for speed, but
+	 * means receivers can't recover lost synch on their own (because
+	 * new packets don't only start after a short RX).
+	 */
+	size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
+	size += dev->port_usb->header_len;
+
+	size += EXTRA_SKB_WIFI_HEADROOM; /* for wifi header */
+	size += out->maxpacket - 1;
+	size -= size % out->maxpacket;
+
+	if (dev->ul_max_pkts_per_xfer)
+		size *= dev->ul_max_pkts_per_xfer;
+
+	if (dev->port_usb->is_fixed)
+		size = max_t(size_t, size, dev->port_usb->fixed_out_len);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	pr_debug("%s: size: %zu", __func__, size);
+
+	//if ((global_zone_page_state(NR_FREE_PAGES) << PAGE_SHIFT) < USB_ALLOC_MEM_LOW_THRESH) {
+	if ((global_zone_page_state(NR_FREE_PAGES) << PAGE_SHIFT) < ((min_free_kbytes + 64) << 10)) {
+		pr_err_ratelimited("usb mem low\n");
+		skb = NULL;
+		goto enomem;
+	}
+
+#ifdef CONFIG_USB_DWC2
+	/* add 3 bytes for 4-bytes alignment requirement
+	* wifi headroom is not calculated in the fixed length
+	*/
+	if (dev->port_usb->is_fixed)
+		skb = alloc_skb(size + NET_IP_ALIGN + 3 + EXTRA_SKB_WIFI_HEADROOM, gfp_flags);
+	else
+		skb = alloc_skb(size + NET_IP_ALIGN + 3, gfp_flags);
+#else
+	if (dev->port_usb->is_fixed)
+		skb = alloc_skb(size + NET_IP_ALIGN + EXTRA_SKB_WIFI_HEADROOM, gfp_flags);
+	else
+		skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags);
+#endif
+	if (skb == NULL) {
+		ERROR(dev, "no rx skb\n");
+		goto enomem;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		goto enomem;
+	}
+
+	/* Some platforms perform better when IP packets are aligned,
+	 * but on at least one, checksumming fails otherwise.  Note:
+	 * RNDIS headers involve variable numbers of LE32 values.
+	 */
+	skb_reserve(skb, NET_IP_ALIGN + EXTRA_SKB_WIFI_HEADROOM);
+
+#ifdef CONFIG_USB_DWC2
+	if (((u32)skb->data) & 0x3)
+		skb_reserve(skb, (4 - (((u32)skb->data) & 3)));
+#endif
+
+	req->buf = skb->data;
+	req->length = size;
+	req->context = skb;
+
+#if !defined(CONFIG_USB_DWC3) && !defined(CONFIG_USB_DWC2)
+	/* Always active, handled in the low level driver*/
+	req->no_interrupt = 1;
+#endif
+
+	retval = usb_ep_queue(out, req, gfp_flags);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (retval == -ENOMEM)
+enomem:
+		defer_kevent(dev, WORK_RX_MEMORY);
+	if (retval) {
+		printk_ratelimited(KERN_DEBUG "rx submit --> %d\n", retval);
+		if (skb)
+			dev_kfree_skb_any(skb);
+	}
+	return retval;
+}
+
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags);
+static void rx_complete_notl(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sk_buff	*skb = req->context, *skb2;
+	struct eth_dev	*dev = ep->driver_data;
+	int		status = req->status;
+	unsigned long	flags;
+	bool		queue = false;
+
+	switch (status) {
+
+	/* normal completion */
+	case 0:
+		skb_put(skb, req->actual);
+
+		if (dev->unwrap) {
+			spin_lock_irqsave(&dev->lock, flags);
+			if (dev->port_usb) {
+				status = dev->unwrap(dev->port_usb,
+							skb,
+							&dev->rx_frames);
+			} else {
+				dev_kfree_skb_any(skb);
+				skb = NULL;
+				status = -ENOTCONN;
+			}
+			spin_unlock_irqrestore(&dev->lock, flags);
+		} else {
+			skb_queue_tail(&dev->rx_frames, skb);
+		}
+		skb = NULL;
+
+		if (!status)
+			queue = true;
+		else
+			pr_err_ratelimited("%s nq status: %d\n", __func__, status);
+
+		skb2 = skb_dequeue(&dev->rx_frames);
+		while (skb2) {
+			if (status < 0
+					|| ETH_HLEN > skb2->len
+					|| skb2->len > VLAN_ETH_FRAME_LEN) {
+				dev->net->stats.rx_errors++;
+				dev->net->stats.rx_length_errors++;
+				ERROR(dev, "rx length %d\n", skb2->len);
+				dev_kfree_skb_any(skb2);
+				goto next_frame;
+			}
+			skb2->protocol = eth_type_trans(skb2, dev->net);
+			dev->net->stats.rx_packets++;
+			dev->net->stats.rx_bytes += skb2->len;
+
+			/* no buffer copies needed, unless hardware can't
+			 * use skb buffers.
+			 */
+			status = netif_rx(skb2);
+next_frame:
+			skb2 = skb_dequeue(&dev->rx_frames);
+		}
+		break;
+
+	/* software-driven interface shutdown */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		VDBG(dev, "rx shutdown, code %d\n", status);
+		goto quiesce;
+
+	/* for hardware automagic (such as pxa) */
+	case -ECONNABORTED:		/* endpoint reset */
+		ERROR(dev, "rx %s reset\n", ep->name);
+		defer_kevent(dev, WORK_RX_MEMORY);
+quiesce:
+		dev_kfree_skb_any(skb);
+		skb = NULL;
+		goto clean;
+
+	/* data overrun */
+	case -EOVERFLOW:
+		dev->net->stats.rx_over_errors++;
+		/* FALLTHROUGH */
+
+	default:
+		queue = true;
+		dev->net->stats.rx_errors++;
+		ERROR(dev, "rx status %d\n", status);
+		break;
+	}
+
+	if (skb)
+		dev_kfree_skb_any(skb);
+clean:
+	spin_lock_irqsave(&dev->req_lock, flags);
+	list_add(&req->list, &dev->rx_reqs);
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	if (queue)
+		rx_fill(dev, GFP_ATOMIC);
+}
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sk_buff	*skb = req->context;
+	struct eth_dev	*dev = ep->driver_data;
+	int		status = req->status;
+	bool		queue = false;
+
+	switch (status) {
+
+	/* normal completion */
+	case 0:
+		skb_put(skb, req->actual);
+
+		if (dev->unwrap) {
+			unsigned long	flags;
+
+			spin_lock_irqsave(&dev->lock, flags);
+			if (dev->port_usb) {
+				status = dev->unwrap(dev->port_usb,
+							skb,
+							&dev->rx_frames);
+				if (status == -EINVAL)
+					dev->net->stats.rx_errors++;
+				else if (status == -EOVERFLOW)
+					dev->net->stats.rx_over_errors++;
+			} else {
+				dev_kfree_skb_any(skb);
+				status = -ENOTCONN;
+			}
+			spin_unlock_irqrestore(&dev->lock, flags);
+		} else {
+			skb_queue_tail(&dev->rx_frames, skb);
+		}
+		if (!status)
+			queue = true;
+		break;
+
+	/* software-driven interface shutdown */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		VDBG(dev, "rx shutdown, code %d\n", status);
+		goto quiesce;
+
+	/* for hardware automagic (such as pxa) */
+	case -ECONNABORTED:		/* endpoint reset */
+		DBG(dev, "rx %s reset\n", ep->name);
+		defer_kevent(dev, WORK_RX_MEMORY);
+quiesce:
+		dev_kfree_skb_any(skb);
+		goto clean;
+
+	/* data overrun */
+	case -EOVERFLOW:
+		dev->net->stats.rx_over_errors++;
+		/* FALLTHROUGH */
+
+	default:
+		queue = true;
+		dev_kfree_skb_any(skb);
+		dev->net->stats.rx_errors++;
+		DBG(dev, "rx status %d\n", status);
+		break;
+	}
+
+clean:
+	spin_lock(&dev->req_lock);
+	list_add(&req->list, &dev->rx_reqs);
+	spin_unlock(&dev->req_lock);
+#ifndef USB_RX_USE_WORK
+	if (queue)
+		tasklet_schedule(&dev->rx_tl);
+#else
+	if (queue)
+		schedule_work(&dev->rx_work);
+#endif
+}
+
+static inline void usb_ep_free_request_tx_mult(struct usb_ep *ep,
+					        struct usb_request *req)
+{
+	req_aggr_clean(req);
+	BUG_ON(!AGGRCTX(req));
+#ifdef CONFIG_USBNET_USE_SG
+	if(AGGRCTX(req)->sg)
+		kfree(AGGRCTX(req)->sg);
+#endif
+	kfree(AGGRCTX(req));
+	req->context = NULL;
+	usb_ep_free_request(ep, req);
+}
+
+static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
+{
+	unsigned		i;
+	struct usb_request	*req;
+	bool			usb_in;
+
+	if (!n)
+		return -ENOMEM;
+
+	if (ep->desc->bEndpointAddress & USB_DIR_IN)
+		usb_in = true;
+	else
+		usb_in = false;
+
+	/* queue/recycle up to N requests */
+	i = n;
+	list_for_each_entry(req, list, list) {
+		if (i-- == 0)
+			goto extra;
+	}
+
+
+	while (i--) {
+		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+		if (!req)
+			return list_empty(list) ? -ENOMEM : 0;
+		/* update completion handler */
+		if (usb_in){
+			req->complete = tx_complete;
+			req->context = (void*)kzalloc(sizeof(struct aggr_ctx), GFP_ATOMIC);
+			if (!req->context){
+				usb_ep_free_request(ep, req);
+				pr_err("%s:%d: error: only %d reqs allocated\n",
+				           __func__, __LINE__, (n - i));
+				return list_empty(list) ? -ENOMEM : 0;
+			}
+			skb_queue_head_init(&AGGRCTX(req)->skb_list);
+			AGGRCTX(req)->total_size = 0;
+#ifdef CONFIG_USBNET_USE_SG
+			if (sg_is_enabled)
+				AGGRCTX(req)->sg = alloc_sglist(USBNET_SG_NENTS);
+#endif
+		} else {
+			if (rx_notl)
+				req->complete = rx_complete_notl;
+			else
+				req->complete = rx_complete;
+		}
+		list_add(&req->list, list);
+	}
+
+	return 0;
+
+extra:
+	/* free extras */
+	for (;;) {
+		struct list_head	*next;
+
+		next = req->list.next;
+		list_del(&req->list);
+		if (usb_in) {
+			usb_ep_free_request_tx_mult(ep, req);
+		} else
+			usb_ep_free_request(ep, req);
+
+		if (next == list)
+			break;
+
+		req = container_of(next, struct usb_request, list);
+	}
+	return 0;
+}
+
+static int alloc_requests(struct eth_dev *dev, struct gether *link,
+			   unsigned n_rx, unsigned n_tx)
+{
+	int	status;
+
+	spin_lock(&dev->req_lock);
+	status = prealloc(&dev->tx_reqs, link->in_ep, n_tx);
+	if (status < 0)
+		goto fail;
+	status = prealloc(&dev->rx_reqs, link->out_ep, n_rx);
+	if (status < 0)
+		goto fail;
+	goto done;
+fail:
+	DBG(dev, "can't alloc requests\n");
+done:
+	spin_unlock(&dev->req_lock);
+	return status;
+}
+
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
+{
+	struct usb_request	*req;
+	unsigned long		flags;
+	int			req_cnt = 0;
+
+	/* fill unused rxq slots with some skb */
+	spin_lock_irqsave(&dev->req_lock, flags);
+	while (!list_empty(&dev->rx_reqs)) {
+		/* break the nexus of continuous completion and re-submission*/
+		if (++req_cnt > qlen(dev->gadget, true))
+			break;
+
+		req = container_of(dev->rx_reqs.next,
+				struct usb_request, list);
+		list_del_init(&req->list);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+
+		if (rx_submit(dev, req, gfp_flags) < 0) {
+			spin_lock_irqsave(&dev->req_lock, flags);
+			list_add(&req->list, &dev->rx_reqs);
+			spin_unlock_irqrestore(&dev->req_lock, flags);
+			defer_kevent(dev, WORK_RX_MEMORY);
+			return;
+		}
+
+		spin_lock_irqsave(&dev->req_lock, flags);
+	}
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+}
+
+static void process_rx_tl(unsigned long priv)
+{
+	struct eth_dev	*dev = (struct eth_dev *)priv;
+	struct sk_buff	*skb;
+	int		status = 0;
+
+	if (!dev->port_usb)
+		return;
+
+	while ((skb = skb_dequeue(&dev->rx_frames))) {
+		if (status < 0
+				|| ETH_HLEN > skb->len
+				|| skb->len > VLAN_ETH_FRAME_LEN) {
+			dev->net->stats.rx_errors++;
+			dev->net->stats.rx_length_errors++;
+			DBG(dev, "rx length %d\n", skb->len);
+			dev_kfree_skb_any(skb);
+			continue;
+		}
+		
+		if (dev->unwrap_fixup) {
+			struct sk_buff *new =
+				dev->unwrap_fixup(dev->port_usb, skb);
+			if (!new) {
+				pr_info("unwrap_fixup failed\n");
+				dev_kfree_skb_any(skb);
+				continue;
+			}
+
+			dev_kfree_skb_any(skb);
+			skb = new;
+			WARN_ON(!skb_mac_header_was_set(skb));
+		}
+
+#ifdef CONFIG_DDR_DEVFREQ
+		atomic_inc(&dev->no_rx_skb);
+#endif
+		skb->protocol = eth_type_trans(skb, dev->net);
+		dev->net->stats.rx_packets++;
+		dev->net->stats.rx_bytes += skb->len;
+
+		status = netif_rx(skb);
+	}
+
+	if (netif_running(dev->net))
+		rx_fill(dev, GFP_ATOMIC);
+}
+
+#ifdef USB_RX_USE_WORK
+static DEFINE_MUTEX(rx_work_lock);
+
+static void process_rx_work(struct work_struct *data)
+{
+	struct eth_dev	*dev = container_of(data, struct eth_dev, rx_work);
+
+	mutex_lock(&rx_work_lock);
+	local_bh_disable();
+	process_rx_tl((unsigned long)dev);
+	local_bh_enable();
+	mutex_unlock(&rx_work_lock);
+}
+#endif
+
+#if defined(USB_RX_USE_WORK) && defined(CONFIG_USB_MV_HSIC_UDC)
+static DEFINE_MUTEX(hsic_rx_work_lock);
+
+static void process_hsic_rx_work(struct work_struct *data)
+{
+	struct eth_dev	*dev = container_of(data, struct eth_dev, rx_work);
+
+	mutex_lock(&hsic_rx_work_lock);
+	local_bh_disable();
+	process_rx_tl((unsigned long)dev);
+	local_bh_enable();
+	mutex_unlock(&hsic_rx_work_lock);
+}
+#endif
+
+static void eth_work(struct work_struct *work)
+{
+	struct eth_dev	*dev = container_of(work, struct eth_dev, work);
+
+	if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) {
+		if (netif_running(dev->net))
+			rx_fill(dev, GFP_KERNEL);
+	}
+
+	if (dev->todo)
+		DBG(dev, "work done, flags = 0x%lx\n", dev->todo);
+}
+
+static void tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct eth_dev	*dev;
+	struct net_device *net;
+	struct usb_request *new_req;
+	struct usb_ep *in;
+	int length;
+	int retval;
+	int skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+
+	if (!ep->driver_data) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	dev = ep->driver_data;
+	net = dev->net;
+
+	if (!dev->port_usb) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	switch (req->status) {
+	default:
+		dev->net->stats.tx_errors += skb_qlen;
+		VDBG(dev, "tx err %d\n", req->status);
+		/* FALLTHROUGH */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		break;
+	case 0:
+		if (!req->zero)
+			dev->net->stats.tx_bytes += req->length-1;
+		else
+			dev->net->stats.tx_bytes += req->length;
+		dev->net->stats.tx_packets += skb_qlen;
+	}
+
+	spin_lock(&dev->req_lock);
+	req_aggr_clean(req);
+
+	list_add_tail(&req->list, &dev->tx_reqs);
+	atomic_dec(&dev->no_tx_req_used);
+	in = dev->port_usb->in_ep;
+
+	if (!list_empty(&dev->tx_reqs)) {
+		new_req = container_of(dev->tx_reqs.next,
+				struct usb_request, list);
+		list_del(&new_req->list);
+		spin_unlock(&dev->req_lock);
+		if (AGGRCTX(new_req)->total_size > 0) {
+			length = AGGRCTX(new_req)->total_size;
+
+			/* NCM requires no zlp if transfer is
+			 * dwNtbInMaxSize */
+			if (dev->port_usb->is_fixed &&
+				length == dev->port_usb->fixed_in_len &&
+				(length % in->maxpacket) == 0)
+				new_req->zero = 0;
+			else
+				new_req->zero = 1;
+
+			/* use zlp framing on tx for strict CDC-Ether
+			 * conformance, though any robust network rx
+			 * path ignores extra padding. and some hardware
+			 * doesn't like to write zlps.
+			 */
+			if (new_req->zero && !dev->zlp &&
+					(length % in->maxpacket) == 0) {
+				new_req->zero = 0;
+				length++;
+#ifdef CONFIG_USBNET_USE_SG
+				if (sg_is_enabled && new_req->num_sgs) {
+					new_req->sg[new_req->num_sgs - 1].length++;
+				}
+#endif
+			}
+			new_req->length = length;
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+			skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+			if (skb_qlen > histogram_size && histogram_size) {
+				histogram_realloc(dev, skb_qlen);
+				pr_err("%s: %d histogram buffer to small, realloc to %d, "
+				      "hold_count=%d\n", __func__, __LINE__, histogram_size,
+				      skb_qlen);
+			}
+			if (dev->tx_mult_histogram && skb_qlen <= histogram_size)
+				dev->tx_mult_histogram[skb_qlen - 1]++;
+#endif
+#ifdef CONFIG_USBNET_USE_SG
+			if (sg_is_enabled)
+				build_sglist(new_req, AGGRCTX(new_req));
+#endif
+			retval = usb_ep_queue(in, new_req, GFP_ATOMIC);
+			switch (retval) {
+			default:
+				DBG(dev, "tx queue err %d\n", retval);
+				dev->net->stats.tx_dropped +=
+					              skb_queue_len(&AGGRCTX(new_req)->skb_list);
+				req_aggr_clean(req);
+				spin_lock(&dev->req_lock);
+				list_add_tail(&new_req->list,
+						&dev->tx_reqs);
+				spin_unlock(&dev->req_lock);
+				break;
+			case 0:
+				atomic_inc(&dev->no_tx_req_used);
+				//net->trans_start = jiffies;
+			}
+		} else {
+			spin_lock(&dev->req_lock);
+			/*
+			 * Put the idle request at the back of the
+			 * queue. The xmit function will put the
+			 * unfinished request at the beginning of the
+			 * queue.
+			 */
+			list_add_tail(&new_req->list, &dev->tx_reqs);
+			spin_unlock(&dev->req_lock);
+		}
+	} else {
+		spin_unlock(&dev->req_lock);
+	}
+
+	if (netif_carrier_ok(dev->net))
+		netif_wake_queue(dev->net);
+}
+
+static inline int is_promisc(u16 cdc_filter)
+{
+	return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS;
+}
+
+static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
+					struct net_device *net)
+{
+	struct eth_dev		*dev = netdev_priv(net);
+	int			skb_qlen = 0;
+	int			length = skb->len;
+	struct 		sk_buff *pending_skb = NULL;
+	int			retval;
+	struct usb_request	*req = NULL;
+	unsigned long		flags;
+	struct usb_ep		*in;
+	u16			cdc_filter;
+
+	if (unlikely(!skb))
+		return NETDEV_TX_OK;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb) {
+		in = dev->port_usb->in_ep;
+		cdc_filter = dev->port_usb->cdc_filter;
+	} else {
+		in = NULL;
+		cdc_filter = 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (!in) {
+		dev_kfree_skb_any(skb);
+		return NETDEV_TX_OK;
+	}
+
+	/* apply outgoing CDC or RNDIS filters */
+	if (!is_promisc(cdc_filter)) {
+		u8		*dest = skb->data;
+
+		if (is_multicast_ether_addr(dest)) {
+			u16	type;
+
+			/* ignores USB_CDC_PACKET_TYPE_MULTICAST and host
+			 * SET_ETHERNET_MULTICAST_FILTERS requests
+			 */
+			if (is_broadcast_ether_addr(dest))
+				type = USB_CDC_PACKET_TYPE_BROADCAST;
+			else
+				type = USB_CDC_PACKET_TYPE_ALL_MULTICAST;
+			if (!(cdc_filter & type)) {
+				dev_kfree_skb_any(skb);
+				return NETDEV_TX_OK;
+			}
+		}
+		/* ignores USB_CDC_PACKET_TYPE_DIRECTED */
+	}
+
+	spin_lock_irqsave(&dev->req_lock, flags);
+	/*
+	 * this freelist can be empty if an interrupt triggered disconnect()
+	 * and reconfigured the gadget (shutting down this queue) after the
+	 * network stack decided to xmit but before we got the spinlock.
+	 */
+	if (list_empty(&dev->tx_reqs)) {
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		return NETDEV_TX_BUSY;
+	}
+
+	req = container_of(dev->tx_reqs.next, struct usb_request, list);
+	list_del(&req->list);
+
+	/* temporarily stop TX queue when the freelist empties */
+	if (list_empty(&dev->tx_reqs))
+		netif_stop_queue(net);
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	BUG_ON(!AGGRCTX(req));
+	//TODO
+	//BUG_ON(skb->next || skb->prev);
+	BUG_ON(skb->next);
+
+	/* no buffer copies needed, unless the network stack did it
+	 * or the hardware can't use skb buffers.
+	 * or there's not enough space for extra headers we need
+	 */
+	if (dev->wrap) {
+		unsigned long	flags;
+
+		spin_lock_irqsave(&dev->lock, flags);
+		if (dev->port_usb)
+			skb = dev->wrap(dev->port_usb, skb, AGGRCTX(req));
+		spin_unlock_irqrestore(&dev->lock, flags);
+		if (!skb)
+			goto drop;
+	}
+	req->buf = skb->data;
+
+	if (AGGRCTX(req)->total_size > 0) {
+		BUG_ON(skb_queue_empty(&AGGRCTX(req)->skb_list));
+		length = AGGRCTX(req)->total_size;
+	} else {
+		BUG_ON(!skb_queue_empty(&AGGRCTX(req)->skb_list));
+		skb_queue_tail(&AGGRCTX(req)->skb_list, skb);
+		length = skb->len;
+	}
+
+	if (!AGGR_DONE(req) &&
+		      atomic_read(&dev->no_tx_req_used) > TX_REQ_THRESHOLD) {
+		BUG_ON(!AGGRCTX(req)->total_size);
+		spin_lock_irqsave(&dev->req_lock, flags);
+		list_add(&req->list, &dev->tx_reqs);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		goto success;
+	}
+	atomic_inc(&dev->no_tx_req_used);
+
+	if (unlikely(!dev->port_usb)) {
+		ERROR(dev, "port_usb = NULL\n");
+		goto drop;
+	}
+	/* NCM requires no zlp if transfer is dwNtbInMaxSize */
+	if (dev->port_usb->is_fixed &&
+	length == dev->port_usb->fixed_in_len &&
+	(length % in->maxpacket) == 0)
+		req->zero = 0;
+	else
+		req->zero = 1;
+	
+	/* use zlp framing on tx for strict CDC-Ether conformance,
+	 * though any robust network rx path ignores extra padding.
+	 * and some hardware doesn't like to write zlps.
+	 */
+	if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) {
+		req->zero = 0;
+		length++;
+#ifdef CONFIG_USBNET_USE_SG
+		if (sg_is_enabled && req->num_sgs) {
+			req->sg[req->num_sgs - 1].length++;
+		}
+#endif
+	}
+	req->length = length;
+
+#if defined(CONFIG_USB_DWC3) || defined(CONFIG_USB_DWC2)
+	req->no_interrupt = 0;
+#else
+	/* throttle highspeed IRQ rate back slightly */
+	if (gadget_is_dualspeed(dev->gadget) &&
+		(dev->gadget->speed >= USB_SPEED_HIGH)) {
+		dev->tx_qlen++;
+		if (dev->tx_qlen == TX_REQ_THRESHOLD) {
+			req->no_interrupt = 0;
+			dev->tx_qlen = 0;
+		} else {
+			req->no_interrupt = 1;
+		}
+	} else {
+		req->no_interrupt = 0;
+	}
+#endif
+
+	pending_skb = AGGRCTX(req)->pending_skb;
+	AGGRCTX(req)->pending_skb = NULL;
+
+#ifdef CONFIG_USBNET_USE_SG
+	if (sg_is_enabled)
+		build_sglist(req, AGGRCTX(req));
+#endif
+
+	retval = usb_ep_queue(in, req, GFP_ATOMIC);
+
+	spin_lock_irqsave(&dev->req_lock, flags);
+	if (likely((retval == 0) && (req->context != NULL))) {
+		skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+	} else if (unlikely(req->context == NULL)) {
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		pr_err_ratelimited("req is already freed\n");
+		return NETDEV_TX_OK;
+	}
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	switch (retval) {
+	default:
+		DBG(dev, "tx queue err %d\n", retval);
+		break;
+	case 0:
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+		if (skb_qlen > histogram_size && histogram_size) {
+			histogram_realloc(dev, skb_qlen);
+			pr_err("%s: %d histogram buffer to small, realloc to %d, "
+				  "hold_count=%d\n", __func__, __LINE__, histogram_size,
+				  skb_qlen);
+		}
+		if (dev->tx_mult_histogram && skb_qlen <= histogram_size)
+			dev->tx_mult_histogram[skb_qlen - 1]++;
+#endif
+		//net->trans_start = jiffies;
+		do {}while(0);
+	}
+
+	if (retval) {
+		atomic_dec(&dev->no_tx_req_used);
+drop:
+		dev->net->stats.tx_dropped += (skb_qlen == 0) ? 1 : skb_qlen;
+		req_aggr_clean(req);
+		spin_lock_irqsave(&dev->req_lock, flags);
+		if (list_empty(&dev->tx_reqs))
+			netif_start_queue(net);
+		list_add_tail(&req->list, &dev->tx_reqs);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+	}
+
+	if (pending_skb)
+		return eth_start_xmit(pending_skb, net);
+success:
+	return NETDEV_TX_OK;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
+{
+	DBG(dev, "%s\n", __func__);
+
+	/* fill the rx queue */
+	rx_fill(dev, gfp_flags);
+
+	/* and open the tx floodgates */
+	dev->tx_qlen = 0;
+	netif_wake_queue(dev->net);
+}
+
+static int eth_open(struct net_device *net)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	struct gether	*link;
+
+	DBG(dev, "%s\n", __func__);
+	if (netif_carrier_ok(dev->net))
+		eth_start(dev, GFP_KERNEL);
+
+	spin_lock_irq(&dev->lock);
+	link = dev->port_usb;
+	if (link && link->open)
+		link->open(link);
+	spin_unlock_irq(&dev->lock);
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	if (sysfs_create_group(&dev->net->dev.kobj, &u_ether_attr_group))
+		pr_err("%s:%d: fail to register sysfs attr group\n", __func__,
+		                __LINE__);
+#endif
+
+	return 0;
+}
+
+static int eth_stop(struct net_device *net)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	unsigned long	flags;
+
+	VDBG(dev, "%s\n", __func__);
+	netif_stop_queue(net);
+
+	INFO(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n",
+		dev->net->stats.rx_packets, dev->net->stats.tx_packets,
+		dev->net->stats.rx_errors, dev->net->stats.tx_errors
+		);
+
+	/* ensure there are no more active requests */
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb) {
+		struct gether	*link = dev->port_usb;
+		const struct usb_endpoint_descriptor *in;
+		const struct usb_endpoint_descriptor *out;
+
+		if (link->close)
+			link->close(link);
+
+		/* NOTE:  we have no abort-queue primitive we could use
+		 * to cancel all pending I/O.  Instead, we disable then
+		 * reenable the endpoints ... this idiom may leave toggle
+		 * wrong, but that's a self-correcting error.
+		 *
+		 * REVISIT:  we *COULD* just let the transfers complete at
+		 * their own pace; the network stack can handle old packets.
+		 * For the moment we leave this here, since it works.
+		 */
+		in = link->in_ep->desc;
+		out = link->out_ep->desc;
+		/* usb_ep_disable(link->in_ep);
+		usb_ep_disable(link->out_ep); */
+		if (netif_carrier_ok(net)) {
+			INFO(dev, "host still using in/out endpoints\n");
+			link->in_ep->desc = in;
+			link->out_ep->desc = out;
+			/* usb_ep_enable(link->in_ep);
+			usb_ep_enable(link->out_ep); */
+		}
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+		sysfs_remove_group(&dev->net->dev.kobj, &u_ether_attr_group);
+	#endif
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static u8 host_ethaddr[ETH_ALEN];
+
+/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
+static char *dev_addr;
+module_param(dev_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(dev_addr, "Device Ethernet Address");
+
+#ifdef CONFIG_USB_MV_HSIC_UDC
+static u8 host_ethaddr_hsic[ETH_ALEN];
+static char *dev_addr_hsic;
+module_param(dev_addr_hsic, charp, S_IRUGO);
+MODULE_PARM_DESC(dev_addr_hsic, "HSIC Device Ethernet Addr");
+#endif
+
+/* this address is invisible to ifconfig */
+static char *host_addr;
+module_param(host_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(host_addr, "Host Ethernet Address");
+
+void save_usbnet_host_ethaddr(u8 addr[])
+{
+	memcpy(host_ethaddr, addr, ETH_ALEN);
+}
+
+static int get_ether_addr(const char *str, u8 *dev_addr)
+{
+	if (str) {
+		unsigned	i;
+
+		for (i = 0; i < 6; i++) {
+			unsigned char num;
+
+			if ((*str == '.') || (*str == ':'))
+				str++;
+			num = hex_to_bin(*str++) << 4;
+			num |= hex_to_bin(*str++);
+			dev_addr [i] = num;
+		}
+		if (is_valid_ether_addr(dev_addr))
+			return 0;
+	}
+	eth_random_addr(dev_addr);
+	return 1;
+}
+
+static int get_host_ether_addr(u8 *str, u8 *dev_addr)
+{
+	memcpy(dev_addr, str, ETH_ALEN);
+	if (is_valid_ether_addr(dev_addr))
+		return 0;
+
+	random_ether_addr(dev_addr);
+	memcpy(str, dev_addr, ETH_ALEN);
+	return 1;
+}
+
+static const struct net_device_ops eth_netdev_ops = {
+	.ndo_open		= eth_open,
+	.ndo_stop		= eth_stop,
+	.ndo_start_xmit		= eth_start_xmit,
+	.ndo_change_mtu		= ueth_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+static struct device_type gadget_type = {
+	.name	= "gadget",
+};
+
+/**
+ * gether_setup_name - initialize one ethernet-over-usb link
+ * @g: gadget to associated with these links
+ * @ethaddr: NULL, or a buffer in which the ethernet address of the
+ *	host side of the link is recorded
+ * @netname: name for network device (for example, "usb")
+ * Context: may sleep
+ *
+ * This sets up the single network link that may be exported by a
+ * gadget driver using this framework.  The link layer addresses are
+ * set up using module parameters.
+ *
+ * Returns negative errno, or zero on success
+ */
+struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname)
+{
+	struct eth_dev		*dev;
+	struct net_device	*net;
+	int			status;
+
+	net = alloc_etherdev(sizeof *dev);
+	if (!net)
+		return ERR_PTR(-ENOMEM);
+#if 0
+	net->element_used = 0;
+	for (i = 0; i < DEBUG_ELEMENT_CNT; i++) {
+		net->caller_elments[i].func_address = 0;
+		net->caller_elments[i].cnt = 0;
+	}
+#endif
+	dev = netdev_priv(net);
+	spin_lock_init(&dev->lock);
+	spin_lock_init(&dev->req_lock);
+	INIT_WORK(&dev->work, eth_work);
+#ifdef USB_RX_USE_WORK
+	INIT_WORK(&dev->rx_work, process_rx_work);
+#else
+	tasklet_init(&dev->rx_tl, process_rx_tl,(unsigned long)dev);
+#endif
+	INIT_LIST_HEAD(&dev->tx_reqs);
+	INIT_LIST_HEAD(&dev->rx_reqs);
+
+	/* by default we always have a random MAC address */
+	net->addr_assign_type = NET_ADDR_RANDOM;
+
+	skb_queue_head_init(&dev->rx_frames);
+
+	/* network device setup */
+	dev->net = net;
+	snprintf(net->name, sizeof(net->name), "%s%%d", netname);
+
+	if (get_ether_addr(dev_addr, net->dev_addr)) {
+		net->addr_assign_type = NET_ADDR_RANDOM;
+		dev_warn(&g->dev,
+			"using random %s ethernet address\n", "self");
+	} else {
+		net->addr_assign_type = NET_ADDR_SET;
+	}
+	if (get_host_ether_addr(host_ethaddr, dev->host_mac))
+		dev_warn(&g->dev, "using random %s ethernet address\n", "host");
+	else
+		dev_warn(&g->dev, "using previous %s ethernet address\n", "host");
+
+	if (ethaddr)
+		memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+
+	net->netdev_ops = &eth_netdev_ops;
+
+	//SET_ETHTOOL_OPS(net, &ops);
+	net->ethtool_ops = &ops;
+
+	dev->gadget = g;
+	SET_NETDEV_DEV(net, &g->dev);
+	SET_NETDEV_DEVTYPE(net, &gadget_type);
+
+	status = register_netdev(net);
+	if (status < 0) {
+		dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
+		free_netdev(net);
+		dev = ERR_PTR(status);
+		g_usbnet_dev = NULL;
+	} else {
+		INFO(dev, "MAC %pM\n", net->dev_addr);
+		INFO(dev, "HOST MAC %pM\n", dev->host_mac);
+
+		/* two kinds of host-initiated state changes:
+		 *  - iff DATA transfer is active, carrier is "on"
+		 *  - tx queueing enabled if open *and* carrier is "on"
+		 */
+		netif_carrier_off(net);
+		g_usbnet_dev = net;
+	}
+
+#ifdef CONFIG_DDR_DEVFREQ
+	dev->ddr_qos_min.name = net->name;
+	pm_qos_add_request(&dev->ddr_qos_min,
+		PM_QOS_DDR_DEVFREQ_MIN, PM_QOS_DEFAULT_VALUE);
+#endif
+	return dev;
+}
+
+#ifdef CONFIG_USB_MV_HSIC_UDC
+struct eth_dev *gether_setup_name_hsic(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname)
+{
+	struct eth_dev		*dev;
+	struct net_device	*net;
+	int			status, i;
+
+	net = alloc_etherdev(sizeof *dev);
+	if (!net)
+		return ERR_PTR(-ENOMEM);
+#if 0
+	net->element_used = 0;
+	for (i = 0; i < DEBUG_ELEMENT_CNT; i++) {
+		net->caller_elments[i].func_address = 0;
+		net->caller_elments[i].cnt = 0;
+	}
+#endif
+	dev = netdev_priv(net);
+	spin_lock_init(&dev->lock);
+	spin_lock_init(&dev->req_lock);
+	INIT_WORK(&dev->work, eth_work);
+#ifdef USB_RX_USE_WORK
+	INIT_WORK(&dev->rx_work, process_hsic_rx_work);
+#else
+	tasklet_init(&dev->rx_tl, process_rx_tl,(unsigned long)dev);
+#endif
+	INIT_LIST_HEAD(&dev->tx_reqs);
+	INIT_LIST_HEAD(&dev->rx_reqs);
+
+	/* by default we always have a random MAC address */
+	net->addr_assign_type = NET_ADDR_RANDOM;
+
+	skb_queue_head_init(&dev->rx_frames);
+
+	/* network device setup */
+	dev->net = net;
+	snprintf(net->name, sizeof(net->name), "%s%%d", netname);
+
+	if (get_ether_addr(dev_addr, net->dev_addr)) {
+		net->addr_assign_type = NET_ADDR_RANDOM;
+		dev_warn(&g->dev,
+			"using random %s ethernet address\n", "self");
+	} else {
+		net->addr_assign_type = NET_ADDR_SET;
+	}
+
+	if (get_host_ether_addr(host_ethaddr_hsic, dev->host_mac))
+		dev_warn(&g->dev, "using random %s ethernet address\n", "host");
+	else
+		dev_warn(&g->dev, "using previous %s ethernet address\n", "host");
+
+	if (ethaddr)
+		memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+
+	net->netdev_ops = &eth_netdev_ops;
+
+	SET_ETHTOOL_OPS(net, &ops);
+
+	dev->gadget = g;
+	SET_NETDEV_DEV(net, &g->dev);
+	SET_NETDEV_DEVTYPE(net, &gadget_type);
+
+	status = register_netdev(net);
+	if (status < 0) {
+		dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
+		free_netdev(net);
+		dev = ERR_PTR(status);
+	} else {
+		INFO(dev, "MAC %pM\n", net->dev_addr);
+		INFO(dev, "HOST MAC %pM\n", dev->host_mac);
+
+		/* two kinds of host-initiated state changes:
+		 *  - iff DATA transfer is active, carrier is "on"
+		 *  - tx queueing enabled if open *and* carrier is "on"
+		 */
+		netif_carrier_off(net);
+	}
+
+#ifdef CONFIG_DDR_DEVFREQ
+	dev->ddr_qos_min.name = net->name;
+	pm_qos_add_request(&dev->ddr_qos_min,
+		PM_QOS_DDR_DEVFREQ_MIN, PM_QOS_DEFAULT_VALUE);
+#endif
+	return dev;
+}
+
+#endif
+
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_CPU_ASR1901)
+static bool is_netifd_online(void)
+{
+	struct task_struct *g, *p;
+
+	do_each_thread(g, p) {
+		if (!strcmp("netifd", p->comm))
+			return true;
+	} while_each_thread(g, p);
+
+	return false;
+}
+
+/* wait until the network is configured by netifd */
+void wait_usbnet_br_up_and_brigded(void)
+{
+	struct net_device *dev;
+	int timeout = 300;
+
+	might_sleep();
+	/* just return when netfid is not lauched up */
+	if (unlikely(!is_netifd_online())) {
+		return;
+	} else {
+		/* first wait for usbnet to be up */
+		if (g_usbnet_dev == NULL) {
+			pr_info("!!!!!!!!! usbnet regsiter failed\n");
+			WARN_ON(1);
+			return;
+		} else {
+			/* there may be issue for netifd if system is not wakeup */
+			timeout = 200;
+			while (pm_suspend_target_state != PM_SUSPEND_ON) {
+				msleep(10);
+				timeout--;
+				if (timeout == 0) {
+					pr_info("!!!!! wait system resume timeout\n");
+					WARN_ON(1);
+					return;
+				}
+			}
+			timeout = 300;
+			while (!((g_usbnet_dev->flags & IFF_UP) &&
+				(g_usbnet_dev->priv_flags & IFF_BRIDGE_PORT))) {
+				timeout--;
+				msleep(30);
+				if (timeout == 0) {
+					pr_info("!!!!! wait netifd timeout\n");
+					WARN_ON(1);
+					return;
+				}
+			}
+		}
+
+		/* wait for br-lan to be up */
+		timeout = 150;
+		dev = dev_get_by_name(&init_net, "br-lan");
+		/* network regsiter failed we just return true to move on */
+		if (dev == NULL) {
+			pr_info("!!!!!!!!! br-lan get failed\n");
+			WARN_ON(1);
+			return;
+		} else {
+			while (!(dev->flags & IFF_UP)) {
+				timeout--;
+				msleep(30);
+				if (timeout == 0) {
+					pr_info("!!!!! wait netifd timeout\n");
+					WARN_ON(1);
+					dev_put(dev);
+					return;
+				}
+			}
+			dev_put(dev);
+			return;
+		}
+	}
+}
+
+void wait_usbnet_if_down(void)
+{
+	int timeout = 60;
+
+	might_sleep();
+	/* just return when netfid is not lauched up */
+	if (unlikely(!is_netifd_online())) {
+		return;
+	} else {
+		while (!get_usbnet_ifdown_flag()) {
+			timeout--;
+			msleep(30);
+			if (timeout == 0) {
+				pr_info("!!!!! wait usbnet if_down timeout\n");
+				WARN_ON(1);
+				return;
+			}
+		}
+	}
+}
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+#ifdef CONFIG_CPU_ASR1803
+extern void asr1803_set_32k_to_rtc32k(void);
+#endif
+
+static void txrx_monitor_work(struct work_struct *work)
+{
+	static unsigned long old_rx_bytes = 0;
+	static unsigned long old_tx_bytes = 0;
+	unsigned long rx_bytes, tx_bytes;
+
+	struct eth_dev	*dev = container_of(work, struct eth_dev,
+						txrx_monitor_work.work);
+
+	if (!dev || !dev->net || !dev->port_usb) {
+		pr_err("%s: dev or net is not ready\n", __func__);
+		return;
+	}
+
+#ifdef CONFIG_CPU_ASR1803
+	/* call it in this polling loop for none suspend usb dongle mode */
+	asr1803_set_32k_to_rtc32k();
+#endif
+	if (likely(dev->net->stats.rx_bytes > old_rx_bytes))
+		rx_bytes = dev->net->stats.rx_bytes - old_rx_bytes;
+	else
+		rx_bytes = ULONG_MAX - old_rx_bytes + dev->net->stats.rx_bytes + 1;
+
+	if (likely(dev->net->stats.tx_bytes > old_tx_bytes))
+		tx_bytes = dev->net->stats.tx_bytes - old_tx_bytes;
+	else
+		tx_bytes = ULONG_MAX - old_tx_bytes + dev->net->stats.tx_bytes + 1;
+
+	old_rx_bytes = dev->net->stats.rx_bytes;
+	old_tx_bytes = dev->net->stats.tx_bytes;
+
+	/* first check the tx side and boost ddr_freq to 398MHZ */
+	if (tx_bytes >= DDR_TX_BOOST_BYTES) {
+		pm_qos_update_request_timeout(&dev->ddr_qos_min,
+			DDR_BOOST_FREQ,
+			(2 * USEC_PER_SEC / INTERVALS_PER_SEC));
+		goto out;
+	}
+
+	if (rx_bytes >= rx_boost_thr) {
+		pm_qos_update_request_timeout(&dev->ddr_qos_min,
+			DDR_BOOST_FREQ,
+			(2 * USEC_PER_SEC / INTERVALS_PER_SEC));
+		goto out;
+	}
+
+	if (tx_bytes < DDR_TX_BOOST_BYTES && rx_bytes < rx_boost_thr)
+		pm_qos_update_request(&dev->ddr_qos_min, PM_QOS_DEFAULT_VALUE);
+out:
+	schedule_delayed_work(&dev->txrx_monitor_work, HZ / INTERVALS_PER_SEC);
+}
+#endif
+
+/**
+ * gether_cleanup - remove Ethernet-over-USB device
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gether_setup().
+ */
+void gether_cleanup(struct eth_dev *dev)
+{
+	if (!dev)
+		return;
+
+#ifndef USB_RX_USE_WORK
+	tasklet_kill(&dev->rx_tl);
+#endif
+	unregister_netdev(dev->net);
+	flush_work(&dev->work);
+#ifdef USB_RX_USE_WORK
+	flush_work(&dev->rx_work);
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+	if (dev->dwork_inited)
+		flush_delayed_work(&dev->txrx_monitor_work);
+	pm_qos_remove_request(&dev->ddr_qos_min);
+#endif
+
+	free_netdev(dev->net);
+}
+
+/**
+ * gether_connect - notify network layer that USB link is active
+ * @link: the USB link, set up with endpoints, descriptors matching
+ *	current device speed, and any framing wrapper(s) set up.
+ * Context: irqs blocked
+ *
+ * This is called to activate endpoints and let the network layer know
+ * the connection is active ("carrier detect").  It may cause the I/O
+ * queues to open and start letting network packets flow, but will in
+ * any case activate the endpoints so that they respond properly to the
+ * USB host.
+ *
+ * Verify net_device pointer returned using IS_ERR().  If it doesn't
+ * indicate some error code (negative errno), ep->driver_data values
+ * have been overwritten.
+ */
+struct net_device *gether_connect(struct gether *link)
+{
+	struct eth_dev		*dev = link->ioport;
+	int			result = 0;
+
+	if (!dev)
+		return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_USBNET_USE_SG
+	sg_is_enabled = link->is_sg_mode;
+	pr_info("sg enabled: %d\n", sg_is_enabled);
+#endif
+
+	link->in_ep->driver_data = dev;
+	result = usb_ep_enable(link->in_ep);
+	if (result != 0) {
+		DBG(dev, "enable %s --> %d\n",
+			link->in_ep->name, result);
+		goto fail0;
+	}
+
+	link->out_ep->driver_data = dev;
+	result = usb_ep_enable(link->out_ep);
+	if (result != 0) {
+		DBG(dev, "enable %s --> %d\n",
+			link->out_ep->name, result);
+		goto fail1;
+	}
+	if (result == 0)
+		result = alloc_requests(dev, link, qlen(dev->gadget, true),
+					qlen(dev->gadget, false));
+
+	if (result == 0) {
+		dev->zlp = link->is_zlp_ok;
+		DBG(dev, "qlen_rx %d qlen_tx %d\n", qlen(dev->gadget, true),
+		    qlen(dev->gadget, false));
+
+#ifdef CONFIG_DDR_DEVFREQ
+		dev->tx_boost_threshhold = qlen(dev->gadget, false) / 5;
+
+		/* boost when about 12 * 8 = 96Mbps */
+		dev->rx_boost_threshhold = 8;
+		atomic_set(&dev->no_rx_skb, 0);
+		if (false == dev->dwork_inited) {
+			INIT_DELAYED_WORK(&dev->txrx_monitor_work, txrx_monitor_work);
+			dev->dwork_inited = true;
+		}
+#endif
+		dev->header_len = link->header_len;
+		dev->unwrap = link->unwrap;
+		dev->wrap = link->wrap;
+		dev->unwrap_fixup = link->unwrap_fixup;
+		dev->ul_max_pkts_per_xfer = link->ul_max_pkts_per_xfer;
+		spin_lock(&dev->lock);
+		atomic_set(&dev->no_tx_req_used, 0);
+		dev->port_usb = link;
+		if (netif_running(dev->net)) {
+			if (link->open)
+				link->open(link);
+		} else {
+			if (link->close)
+				link->close(link);
+		}
+		spin_unlock(&dev->lock);
+
+		netif_carrier_on(dev->net);
+		if (netif_running(dev->net))
+			eth_start(dev, GFP_ATOMIC);
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+		/*allocate tx multiple packets histogram pointer:*/
+		BUG_ON(dev->tx_mult_histogram);
+		histogram_size = 16;
+		dev->tx_mult_histogram = kzalloc(sizeof(int) * histogram_size, GFP_ATOMIC);
+		if (!dev->tx_mult_histogram) {
+			histogram_size = 0;
+			pr_err("u_ether: failed to alloc tx_mult_histogram\n");
+		}
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+		/* the delay set as 10ms */
+		schedule_delayed_work(&dev->txrx_monitor_work, HZ / 100);
+#endif
+	/* on error, disable any endpoints  */
+	} else {
+		(void) usb_ep_disable(link->out_ep);
+fail1:
+		(void) usb_ep_disable(link->in_ep);
+	}
+fail0:
+	/* caller is responsible for cleanup on error */
+	if (result < 0)
+		return ERR_PTR(result);
+
+	return dev->net;
+}
+
+/**
+ * gether_disconnect - notify network layer that USB link is inactive
+ * @link: the USB link, on which gether_connect() was called
+ * Context: irqs blocked
+ *
+ * This is called to deactivate endpoints and let the network layer know
+ * the connection went inactive ("no carrier").
+ *
+ * On return, the state is as if gether_connect() had never been called.
+ * The endpoints are inactive, and accordingly without active USB I/O.
+ * Pointers to endpoint descriptors and endpoint private data are nulled.
+ */
+void gether_disconnect(struct gether *link)
+{
+	struct eth_dev		*dev = link->ioport;
+	struct usb_request	*req;
+	struct sk_buff		*skb;
+
+	WARN_ON(!dev);
+	if (!dev)
+		return;
+
+	DBG(dev, "%s\n", __func__);
+
+	netif_stop_queue(dev->net);
+	netif_carrier_off(dev->net);
+
+#ifdef CONFIG_DDR_DEVFREQ
+	/* don't use cancel_delayed_work_sync as this function will be called
+	* in irq context under MAC-ECM resume process
+	*/
+	if (work_pending(&dev->txrx_monitor_work.work))
+		cancel_delayed_work(&dev->txrx_monitor_work);
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	histogram_size = 0;
+	if (dev->tx_mult_histogram) {
+		kfree(dev->tx_mult_histogram);
+		dev->tx_mult_histogram = NULL;
+	}
+#endif
+	/* disable endpoints, forcing (synchronous) completion
+	 * of all pending i/o.  then free the request objects
+	 * and forget about the endpoints.
+	 */
+	usb_ep_disable(link->in_ep);
+	spin_lock(&dev->req_lock);
+	while (!list_empty(&dev->tx_reqs)) {
+		req = container_of(dev->tx_reqs.next,
+					struct usb_request, list);
+		list_del(&req->list);
+		spin_unlock(&dev->req_lock);
+		usb_ep_free_request_tx_mult(link->in_ep, req);
+		spin_lock(&dev->req_lock);
+	}
+	spin_unlock(&dev->req_lock);
+	link->in_ep->driver_data = NULL;
+	link->in_ep->desc = NULL;
+
+	usb_ep_disable(link->out_ep);
+	spin_lock(&dev->req_lock);
+	while (!list_empty(&dev->rx_reqs)) {
+		req = container_of(dev->rx_reqs.next,
+					struct usb_request, list);
+		list_del(&req->list);
+
+		spin_unlock(&dev->req_lock);
+		usb_ep_free_request(link->out_ep, req);
+		spin_lock(&dev->req_lock);
+	}
+	spin_unlock(&dev->req_lock);
+
+	spin_lock(&dev->rx_frames.lock);
+	while ((skb = __skb_dequeue(&dev->rx_frames)))
+		dev_kfree_skb_any(skb);
+	spin_unlock(&dev->rx_frames.lock);
+
+	link->out_ep->driver_data = NULL;
+	link->out_ep->desc = NULL;
+
+	/* finish forgetting about this USB link episode */
+	dev->header_len = 0;
+	dev->unwrap = NULL;
+	dev->wrap = NULL;
+
+	spin_lock(&dev->lock);
+	dev->port_usb = NULL;
+	spin_unlock(&dev->lock);
+}
+
+u8 *eth_get_host_mac(struct net_device *net)
+{
+	struct eth_dev *eth = netdev_priv(net);
+	return eth->host_mac;
+}
+EXPORT_SYMBOL(eth_get_host_mac);
+
+MODULE_DESCRIPTION("ethernet over USB driver");
+MODULE_LICENSE("GPL v2");
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ether.h b/marvell/linux/drivers/usb/gadget/function/u_ether.h
new file mode 100644
index 0000000..13a4345
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ether.h
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_ether.h -- interface to USB gadget "ethernet link" utilities
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ */
+
+#ifndef __U_ETHER_H
+#define __U_ETHER_H
+
+#include <linux/err.h>
+#include <linux/if_ether.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+#include "gadget_chips.h"
+#ifdef CONFIG_ASR_TOE
+#include <linux/toe.h>
+#endif
+
+struct eth_dev;
+struct aggr_ctx;
+
+#ifdef CONFIG_CPU_ASR1903
+#define CONFIG_USBNET_USE_SG 1
+#endif
+
+#define USBNET_SG_NENTS		(32)
+
+/*
+ * This represents the USB side of an "ethernet" link, managed by a USB
+ * function which provides control and (maybe) framing.  Two functions
+ * in different configurations could share the same ethernet link/netdev,
+ * using different host interaction models.
+ *
+ * There is a current limitation that only one instance of this link may
+ * be present in any given configuration.  When that's a problem, network
+ * layer facilities can be used to package multiple logical links on this
+ * single "physical" one.
+ */
+struct gether {
+	struct usb_function		func;
+
+	/* updated by gether_{connect,disconnect} */
+	struct eth_dev			*ioport;
+
+	/* endpoints handle full and/or high speeds */
+	struct usb_ep			*in_ep;
+	struct usb_ep			*out_ep;
+
+	bool				is_zlp_ok;
+	bool				is_sg_mode;
+	u16				cdc_filter;
+
+	/* hooks for added framing, as needed for RNDIS and EEM. */
+	u32				header_len;
+	/* NCM requires fixed size bundles */
+	bool				is_fixed;
+	u32				fixed_out_len;
+	u32				fixed_in_len;
+
+	unsigned			ul_max_pkts_per_xfer;
+	struct sk_buff			*(*wrap)(struct gether *port,
+						struct sk_buff *skb,
+						struct aggr_ctx *aggr_ctx);
+	int				(*unwrap)(struct gether *port,
+						struct sk_buff *skb,
+						struct sk_buff_head *list);
+	struct sk_buff		*(*unwrap_fixup)(struct gether *,
+						struct sk_buff *skb);
+
+	/* called on network open/close */
+	void				(*open)(struct gether *);
+	void				(*close)(struct gether *);
+
+#ifdef CONFIG_ASR_TOE
+	enum uether_type ueth_type;
+#endif
+};
+
+struct aggr_ctx {
+#ifdef CONFIG_ASR_TOE
+	struct list_head toe_list;
+#endif
+	struct sk_buff_head	skb_list;
+	struct sk_buff		*pending_skb;
+	unsigned			total_size;
+
+#ifdef CONFIG_USBNET_USE_SG
+	struct scatterlist	*sg;
+	u32	num_sgs;
+#endif
+
+#ifdef CONFIG_ASR_TOE
+	bool				is_toe;
+#endif
+};
+
+#ifdef CONFIG_ASR_TOE
+#define MAX_RX_REQ_NUMBER	(32)
+struct uether_rx_aggr{
+	int nr_rx_req;
+	struct usb_request *rx_req_array[MAX_RX_REQ_NUMBER];
+};
+
+#define CONFIG_ASR_TOE_USE_SMALL_MEMORY 1
+#endif
+
+
+
+#define AGGR_MAX_PADDING        (256)
+#define AGGRCTX(x) 		((struct aggr_ctx*)((x)->context))
+
+#define	DEFAULT_FILTER	(USB_CDC_PACKET_TYPE_BROADCAST \
+			|USB_CDC_PACKET_TYPE_ALL_MULTICAST \
+			|USB_CDC_PACKET_TYPE_PROMISCUOUS \
+			|USB_CDC_PACKET_TYPE_DIRECTED)
+
+/* variant of gether_setup that allows customizing network device name */
+struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname);
+#ifdef CONFIG_USB_MV_HSIC_UDC
+struct eth_dev *gether_setup_name_hsic(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname);
+#endif
+/* netdev setup/teardown as directed by the gadget driver */
+/* gether_setup - initialize one ethernet-over-usb link
+ * @g: gadget to associated with these links
+ * @ethaddr: NULL, or a buffer in which the ethernet address of the
+ *	host side of the link is recorded
+ * Context: may sleep
+ *
+ * This sets up the single network link that may be exported by a
+ * gadget driver using this framework.  The link layer addresses are
+ * set up using module parameters.
+ *
+ * Returns negative errno, or zero on success
+ */
+static inline struct eth_dev *gether_setup(struct usb_gadget *g,
+		u8 ethaddr[ETH_ALEN])
+{
+	return gether_setup_name(g, ethaddr, "usb");
+}
+
+void gether_cleanup(struct eth_dev *dev);
+
+/* connect/disconnect is handled by individual functions */
+struct net_device *gether_connect(struct gether *);
+void gether_disconnect(struct gether *);
+
+/* Some controllers can't support CDC Ethernet (ECM) ... */
+static inline bool can_support_ecm(struct usb_gadget *gadget)
+{
+	if (!gadget_supports_altsettings(gadget))
+		return false;
+
+	/* Everything else is *presumably* fine ... but this is a bit
+	 * chancy, so be **CERTAIN** there are no hardware issues with
+	 * your controller.  Add it above if it can't handle CDC.
+	 */
+	return true;
+}
+
+/* each configuration may bind one instance of an ethernet link */
+int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		struct eth_dev *dev);
+int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		struct eth_dev *dev);
+int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		struct eth_dev *dev);
+int eem_bind_config(struct usb_configuration *c, struct eth_dev *dev);
+
+#ifdef USB_ETH_RNDIS
+
+int rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		u32 vendorID, const char *manufacturer, struct eth_dev *dev);
+
+#ifdef CONFIG_USB_MV_HSIC_UDC
+int rndis_bind_config_vendor_hsic(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		u32 vendorID, const char *manufacturer, struct eth_dev *dev);
+#endif
+#else
+
+static inline int
+rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+		u32 vendorID, const char *manufacturer, struct eth_dev *dev)
+{
+	return 0;
+}
+
+#endif
+
+/**
+ * rndis_bind_config - add RNDIS network link to a configuration
+ * @c: the configuration to support the network link
+ * @ethaddr: a buffer in which the ethernet address of the host side
+ *	side of the link was recorded
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gether_setup().  Caller is also responsible
+ * for calling @gether_cleanup() before module unload.
+ */
+static inline int rndis_bind_config(struct usb_configuration *c,
+		u8 ethaddr[ETH_ALEN], struct eth_dev *dev)
+{
+	return rndis_bind_config_vendor(c, ethaddr, 0, NULL, dev);
+}
+
+#endif /* __U_ETHER_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ether_configfs.h b/marvell/linux/drivers/usb/gadget/function/u_ether_configfs.h
new file mode 100644
index 0000000..5b1d477
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ether_configfs.h
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_ether_configfs.h
+ *
+ * Utility definitions for configfs support in USB Ethernet functions
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef __U_ETHER_CONFIGFS_H
+#define __U_ETHER_CONFIGFS_H
+
+#define USB_ETHERNET_CONFIGFS_ITEM(_f_)					\
+	static void _f_##_attr_release(struct config_item *item)	\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+									\
+		usb_put_function_instance(&opts->func_inst);		\
+	}								\
+									\
+	static struct configfs_item_operations _f_##_item_ops = {	\
+		.release	= _f_##_attr_release,			\
+	}
+
+#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(_f_)			\
+	static ssize_t _f_##_opts_dev_addr_show(struct config_item *item, \
+						char *page)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int result;						\
+									\
+		mutex_lock(&opts->lock);				\
+		result = gether_get_dev_addr(opts->net, page, PAGE_SIZE); \
+		mutex_unlock(&opts->lock);				\
+									\
+		return result;						\
+	}								\
+									\
+	static ssize_t _f_##_opts_dev_addr_store(struct config_item *item, \
+						 const char *page, size_t len)\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int ret;						\
+									\
+		mutex_lock(&opts->lock);				\
+		if (opts->refcnt) {					\
+			mutex_unlock(&opts->lock);			\
+			return -EBUSY;					\
+		}							\
+									\
+		ret = gether_set_dev_addr(opts->net, page);		\
+		mutex_unlock(&opts->lock);				\
+		if (!ret)						\
+			ret = len;					\
+		return ret;						\
+	}								\
+									\
+	CONFIGFS_ATTR(_f_##_opts_, dev_addr)
+
+#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(_f_)			\
+	static ssize_t _f_##_opts_host_addr_show(struct config_item *item, \
+						 char *page)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int result;						\
+									\
+		mutex_lock(&opts->lock);				\
+		result = gether_get_host_addr(opts->net, page, PAGE_SIZE); \
+		mutex_unlock(&opts->lock);				\
+									\
+		return result;						\
+	}								\
+									\
+	static ssize_t _f_##_opts_host_addr_store(struct config_item *item, \
+						  const char *page, size_t len)\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int ret;						\
+									\
+		mutex_lock(&opts->lock);				\
+		if (opts->refcnt) {					\
+			mutex_unlock(&opts->lock);			\
+			return -EBUSY;					\
+		}							\
+									\
+		ret = gether_set_host_addr(opts->net, page);		\
+		mutex_unlock(&opts->lock);				\
+		if (!ret)						\
+			ret = len;					\
+		return ret;						\
+	}								\
+									\
+	CONFIGFS_ATTR(_f_##_opts_, host_addr)
+
+#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(_f_)			\
+	static ssize_t _f_##_opts_qmult_show(struct config_item *item,	\
+					     char *page)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		unsigned qmult;						\
+									\
+		mutex_lock(&opts->lock);				\
+		qmult = gether_get_qmult(opts->net);			\
+		mutex_unlock(&opts->lock);				\
+		return sprintf(page, "%d\n", qmult);			\
+	}								\
+									\
+	static ssize_t _f_##_opts_qmult_store(struct config_item *item, \
+					      const char *page, size_t len)\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		u8 val;							\
+		int ret;						\
+									\
+		mutex_lock(&opts->lock);				\
+		if (opts->refcnt) {					\
+			ret = -EBUSY;					\
+			goto out;					\
+		}							\
+									\
+		ret = kstrtou8(page, 0, &val);				\
+		if (ret)						\
+			goto out;					\
+									\
+		gether_set_qmult(opts->net, val);			\
+		ret = len;						\
+out:									\
+		mutex_unlock(&opts->lock);				\
+		return ret;						\
+	}								\
+									\
+	CONFIGFS_ATTR(_f_##_opts_, qmult)
+
+#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(_f_)			\
+	static ssize_t _f_##_opts_ifname_show(struct config_item *item, \
+					      char *page)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int ret;						\
+									\
+		mutex_lock(&opts->lock);				\
+		ret = gether_get_ifname(opts->net, page, PAGE_SIZE);	\
+		mutex_unlock(&opts->lock);				\
+									\
+		return ret;						\
+	}								\
+									\
+	CONFIGFS_ATTR_RO(_f_##_opts_, ifname)
+
+#define USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(_f_, _n_)			\
+	static ssize_t _f_##_opts_##_n_##_show(struct config_item *item,\
+					       char *page)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int ret;						\
+									\
+		mutex_lock(&opts->lock);				\
+		ret = sprintf(page, "%02x\n", opts->_n_);		\
+		mutex_unlock(&opts->lock);				\
+									\
+		return ret;						\
+	}								\
+									\
+	static ssize_t _f_##_opts_##_n_##_store(struct config_item *item,\
+						const char *page,	\
+						size_t len)		\
+	{								\
+		struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item);	\
+		int ret = -EINVAL;					\
+		u8 val;							\
+									\
+		mutex_lock(&opts->lock);				\
+		if (sscanf(page, "%02hhx", &val) > 0) {			\
+			opts->_n_ = val;				\
+			ret = len;					\
+		}							\
+		mutex_unlock(&opts->lock);				\
+									\
+		return ret;						\
+	}								\
+									\
+	CONFIGFS_ATTR(_f_##_opts_, _n_)
+
+#endif /* __U_ETHER_CONFIGFS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ether_toe.c b/marvell/linux/drivers/usb/gadget/function/u_ether_toe.c
new file mode 100644
index 0000000..745c1ad
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ether_toe.c
@@ -0,0 +1,2725 @@
+/*
+ * u_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+#include <linux/ctype.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_vlan.h>
+#include <linux/interrupt.h>
+#include <linux/if_vlan.h>
+#include <linux/pm_qos.h>
+#include <linux/sched.h>
+#include <linux/sched/rt.h>
+#include <linux/kthread.h>
+#include <uapi/linux/sched/types.h>
+#include <linux/suspend.h>
+
+#include "u_ether.h"
+#include "skb_llist.h"
+
+#ifdef CONFIG_ASR_TOE
+#include <linux/toe.h>
+#endif
+
+/*
+ * This component encapsulates the Ethernet link glue needed to provide
+ * one (!) network link through the USB gadget stack, normally "usb0".
+ *
+ * The control and data models are handled by the function driver which
+ * connects to this code; such as CDC Ethernet (ECM or EEM),
+ * "CDC Subset", or RNDIS.  That includes all descriptor and endpoint
+ * management.
+ *
+ * Link level addressing is handled by this component using module
+ * parameters; if no such parameters are provided, random link level
+ * addresses are used.  Each end of the link uses one address.  The
+ * host end address is exported in various ways, and is often recorded
+ * in configuration databases.
+ *
+ * The driver which assembles each configuration using such a link is
+ * responsible for ensuring that each configuration includes at most one
+ * instance of is network link.  (The network layer provides ways for
+ * this single "physical" link to be used by multiple virtual links.)
+ */
+
+#define UETH__VERSION	"29-May-2008"
+
+#define CONFIG_UETHER_SKB_LLIST 1
+#define CONFIG_ASR_TOE_AGGR 1
+#define RX_SMALL_BUFF_SIZE	(1600)
+#ifndef CONFIG_USB_GADGET_DEBUG_FILES
+#define CONFIG_USB_GADGET_DEBUG_FILES
+#endif
+
+struct eth_dev {
+	/* lock is held while accessing port_usb
+	 */
+	spinlock_t		lock;
+	struct gether		*port_usb;
+
+	struct net_device	*net;
+	struct usb_gadget	*gadget;
+
+	spinlock_t		req_lock;	/* guard {rx,tx}_reqs */
+	struct list_head	tx_reqs, rx_reqs;
+	unsigned		tx_qlen;
+	atomic_t		no_tx_req_used;
+	atomic_t		nr_rx_req_free;
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	int			*tx_mult_histogram;
+#endif
+
+#ifdef CONFIG_UETHER_SKB_LLIST
+	struct skb_llist	rx_frames;
+#else
+	struct sk_buff_head	rx_frames;
+#endif
+
+	unsigned		header_len;
+	unsigned int		ul_max_pkts_per_xfer;
+	struct sk_buff		*(*wrap)(struct gether *,
+						struct sk_buff *skb,
+						struct aggr_ctx *aggr_ctx);
+	int			(*unwrap)(struct gether *,
+						struct sk_buff *skb,
+						struct sk_buff_head *list);
+	struct sk_buff		*(*unwrap_fixup)(struct gether *,
+						struct sk_buff *skb);
+
+	struct work_struct	work;
+	struct delayed_work	rx_work;
+	struct tasklet_struct	rx_tl;
+
+	unsigned long		todo;
+#define	WORK_RX_MEMORY		0
+
+	bool			zlp;
+	u8			host_mac[ETH_ALEN];
+	bool		eth_started;
+
+#ifdef CONFIG_DDR_DEVFREQ
+/* for nezhas the boost freq is 355mhz, while 398mhz for nezha3 */
+#ifdef CONFIG_CPU_ASR1901
+#define DDR_BOOST_FREQ		(1600000)
+#else
+#define DDR_BOOST_FREQ		(350000)
+#endif
+
+#define INTERVALS_PER_SEC	(10)
+
+#ifdef CONFIG_CPU_ASR1901
+#define DDR_TX_BOOST_BYTES	((1000000000 / INTERVALS_PER_SEC) >> 3) /* 1000mbps*/
+#elif defined(CONFIG_CPU_ASR18XX)
+#define DDR_TX_BOOST_BYTES	((100000000 / INTERVALS_PER_SEC) >> 3) /* 100mbps*/
+#else
+#define DDR_TX_BOOST_BYTES	((150000000 / INTERVALS_PER_SEC) >> 3) /* 150mbps*/
+#endif
+
+#ifdef CONFIG_CPU_ASR1901
+#define DDR_RX_BOOST_BYTES	(( 200000000 / INTERVALS_PER_SEC) >> 3)  /* 200mbps*/
+#else
+#define DDR_RX_BOOST_BYTES	(( 20000000 / INTERVALS_PER_SEC) >> 3)  /* 20mbps*/
+#endif
+
+	struct task_struct	*usbnet_rx_thread;
+	struct pm_qos_request	ddr_qos_min;
+	struct delayed_work	txrx_monitor_work;
+	bool			dwork_inited;
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	unsigned int		skb_alloc_success;
+	unsigned int		skb_alloc_fail;
+	unsigned int		usbnet_rx_num;
+#endif
+};
+
+/*-------------------------------------------------------------------------*/
+
+#define AGGR_DONE(req)	\
+		(AGGRCTX(req)->pending_skb || \
+		AGGRCTX(req)->total_size == 0)
+
+#define RX_EXTRA	20	/* bytes guarding against rx overflows */
+#define DEFAULT_QLEN	5	/* quintuple buffering by default */
+#define NR_RX_REQ_BURST_NUM (60)
+
+#ifdef CONFIG_ASR_TOE
+static unsigned qmult_rx = 300 * DEFAULT_QLEN;
+static unsigned qmult_tx = 2048;
+#else
+static unsigned qmult_rx = 15;
+static unsigned qmult_tx = 40;
+#endif
+
+static struct net_device *g_usbnet_dev;
+static struct eth_dev	*g_dev;
+static bool usbnet_thrd_should_stop;
+
+/* 
+ * TX_REQ_THRESHOLD is used for two different (but related) optimizations:
+ * 1. IRQ_OPTIMIZATION -> getting complete interrupt every TX_REQ_THRESHOLD requestes.
+ * 2. USB_AGGRIGATION -> Start aggregate only after USB has more then TX_REQ_THRESHOLD requestes.
+ * --> To prevent packet delay the value for #1 and #2 MUST be the same.
+ * NOTE: The value of TX_REQ_THRESHOLD must be smaller then half of the queue size
+ *       e.g. TX_REQ_THRESHOLD_MAX <= DEFAULT_QLEN * (qmult_tx / 2) 
+ */
+#define TX_REQ_THRESHOLD	(DEFAULT_QLEN * 4)
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+static unsigned histogram_size;
+#endif
+#endif
+
+#ifndef CONFIG_USB_G_NCM_NON_SEQUENTIAL_NDPS
+#define USB_RX_USE_WORK
+#endif
+
+module_param(qmult_rx, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qmult_rx, "rx queue length multiplier at high/super speed");
+module_param(qmult_tx, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qmult_tx, "tx queue length multiplier at high/super speed");
+
+#ifdef CONFIG_ASR_TOE
+#ifdef USB_RX_USE_WORK
+extern struct workqueue_struct *system_highpri_wq;
+#endif
+
+static struct uether_rx_aggr u_ether_rx_aggr;
+#endif
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags);
+static void usbnet_rx_fill(void);
+static int usbnet_rx_fill_thread(void *arg);
+static void usb_aggr_free_requests(struct eth_dev *dev, struct uether_rx_aggr *uther_aggr);
+
+
+/* for dual-speed hardware, use deeper queues at high/super speed */
+static inline int qlen(struct usb_gadget *gadget, bool rx)
+{
+	return (rx? qmult_rx : qmult_tx);
+}
+
+int u_ether_rx_qlen(void)
+{
+	return qmult_rx;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* REVISIT there must be a better way than having two sets
+ * of debug calls ...
+ */
+
+#undef DBG
+#undef VDBG
+#undef ERROR
+#undef INFO
+
+#define xprintk(d, level, fmt, args...) \
+	printk(level "%s: " fmt , (d)->net->name , ## args)
+
+#ifdef DEBUG
+#undef DEBUG
+#define DBG(dev, fmt, args...) \
+	xprintk(dev , KERN_DEBUG , fmt , ## args)
+#else
+#define DBG(dev, fmt, args...) \
+	do { } while (0)
+#endif /* DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VDBG	DBG
+#else
+#define VDBG(dev, fmt, args...) \
+	do { } while (0)
+#endif /* DEBUG */
+
+#define ERROR(dev, fmt, args...) \
+	xprintk(dev , KERN_ERR , fmt , ## args)
+#define INFO(dev, fmt, args...) \
+	xprintk(dev , KERN_INFO , fmt , ## args)
+
+#ifdef CONFIG_ASR_TOE
+static ATOMIC_NOTIFIER_HEAD(asr_udc_notifier_list);
+int register_uether_event_notifier(struct notifier_block *nb)
+{
+	int ret = 0;
+
+	ret = atomic_notifier_chain_register(&asr_udc_notifier_list, nb);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int unregister_uether_event_notifier(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(&asr_udc_notifier_list, nb);
+}
+
+static void __maybe_unused uether_send_link_event(struct eth_dev *dev, int event)
+{
+	atomic_notifier_call_chain(&asr_udc_notifier_list, event, NULL);
+}
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+static void histogram_realloc(struct eth_dev *dev)
+{
+	int *tmp;
+
+	tmp = kzalloc(sizeof(int) * 2 * histogram_size, GFP_ATOMIC);
+	if (!tmp){
+		kfree(dev->tx_mult_histogram);
+		dev->tx_mult_histogram = NULL;
+		histogram_size = 0;
+	}
+
+	memcpy(tmp, dev->tx_mult_histogram, sizeof(int) * histogram_size);
+	histogram_size *= 2;
+	kfree(dev->tx_mult_histogram);
+	dev->tx_mult_histogram = tmp;
+}
+
+static ssize_t
+u_ether_histogram_clean(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	if (priv->tx_mult_histogram)
+		memset(priv->tx_mult_histogram, 0, histogram_size * sizeof(int));
+	return size;
+}
+
+static ssize_t
+u_ether_histogram_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	char *fmt = "mult[%d] = %d%\t(%d)\n";
+	int i, ret = 0, total = 0, max_aggr;
+
+	if (!priv->tx_mult_histogram)
+		return 0;
+
+	for (i = 0 ; i < histogram_size; i++)
+		total += priv->tx_mult_histogram[i];
+
+	for (i = histogram_size - 1; i >= 0; i--)
+		if (priv->tx_mult_histogram[i])
+			break;
+	max_aggr = i;
+
+	ret += sprintf(buf + ret, "histogram_size = %d\n", histogram_size);
+	for (i = 0; i <= max_aggr; i++)
+		ret += sprintf(buf + ret, fmt, i, priv->tx_mult_histogram[i] * 100 / total, priv->tx_mult_histogram[i]);
+	return ret;
+}
+
+static
+DEVICE_ATTR(u_ether_tx_mult_histogram, S_IRUGO|S_IWUSR, u_ether_histogram_show, u_ether_histogram_clean);
+#endif
+
+static ssize_t
+usbnet_dbg_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static ssize_t
+usbnet_dbg_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct eth_dev *priv = netdev_priv(to_net_dev(dev));
+	int ret = 0;
+
+	ret += sprintf(buf + ret, "skb_alloc_success = %d\n",
+				priv->skb_alloc_success);
+	ret += sprintf(buf + ret, "skb_alloc_fail = %d\n",
+				priv->skb_alloc_fail);
+	ret += sprintf(buf + ret, "usbnet_rx_num = %d\n",
+				priv->usbnet_rx_num);
+	ret += sprintf(buf + ret, "nr_rx_req_free = %d\n",
+				atomic_read(&priv->nr_rx_req_free));
+	ret += sprintf(buf + ret, "port_usb = %x\n",
+				(u32)priv->port_usb);
+	ret += sprintf(buf + ret, "netif_running = %d\n",
+				netif_running(priv->net));
+	ret += sprintf(buf + ret, "eth_started = %d\n",
+				priv->eth_started);
+
+	return ret;
+}
+static
+DEVICE_ATTR(usbnet_dbg, S_IRUGO|S_IWUSR, usbnet_dbg_show,
+		usbnet_dbg_store);
+
+static struct attribute *u_ether_attrs[] = {
+#ifdef CONFIG_USBNET_HISTOGRAM
+	&dev_attr_u_ether_tx_mult_histogram.attr,
+#endif
+	&dev_attr_usbnet_dbg.attr,
+	NULL,
+};
+static struct attribute_group u_ether_attr_group = {
+	.attrs = u_ether_attrs,
+};
+#endif /*CONFIG_USB_GADGET_DEBUG_FILES*/
+
+static inline void req_aggr_clean(struct usb_request *req)
+{
+	struct sk_buff *skb;
+	BUG_ON(!AGGRCTX(req));
+	BUG_ON(AGGRCTX(req)->pending_skb);
+
+	while ((skb = skb_dequeue(&AGGRCTX(req)->skb_list)))
+		dev_kfree_skb_any(skb);
+
+	INIT_LIST_HEAD(&AGGRCTX(req)->toe_list);
+	AGGRCTX(req)->total_size = 0;
+#ifdef CONFIG_ASR_TOE
+	AGGRCTX(req)->is_toe = false;
+	req->is_toe_req = 0;
+#endif
+	req->length = 0;
+	req->buf = NULL;
+}
+
+#ifdef CONFIG_ASR_TOE
+static inline int req_toe_aggr_clean(struct usb_request *req)
+{
+	struct toe_pkt_desc *desc, *n;
+	int nr_pkts = 0;
+	BUG_ON(!AGGRCTX(req));
+	BUG_ON(list_empty(&AGGRCTX(req)->toe_list));
+	list_for_each_entry_safe(desc, n, &AGGRCTX(req)->toe_list, list) {
+		list_del(&desc->list);
+		bm_free(desc, sizeof(*desc));
+		nr_pkts++;
+	}
+
+	INIT_LIST_HEAD(&AGGRCTX(req)->toe_list);
+	AGGRCTX(req)->total_size = 0;
+	AGGRCTX(req)->is_toe = false;
+	req->length = 0;
+	req->buf = NULL;
+	req->is_toe_req = 0;
+
+	return nr_pkts;
+}
+
+static inline int req_toe_aggr_clean_nolock(struct eth_dev *dev, struct usb_request *req)
+{
+	struct toe_pkt_desc *desc, *n;
+	int nr_pkts = 0;
+	BUG_ON(!AGGRCTX(req));
+	BUG_ON(list_empty(&AGGRCTX(req)->toe_list));
+	list_for_each_entry_safe(desc, n, &AGGRCTX(req)->toe_list, list) {
+		list_del(&desc->list);
+		spin_unlock(&dev->req_lock);
+		bm_free(desc, sizeof(*desc)); /* softirq may be invoked by this function */
+		spin_lock(&dev->req_lock);
+		nr_pkts++;
+	}
+
+	INIT_LIST_HEAD(&AGGRCTX(req)->toe_list);
+	AGGRCTX(req)->total_size = 0;
+	AGGRCTX(req)->is_toe = false;
+	req->length = 0;
+	req->buf = NULL;
+	req->is_toe_req = 0;
+
+	return nr_pkts;
+}
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
+
+static int ueth_change_mtu(struct net_device *net, int new_mtu)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	unsigned long	flags;
+	int		status = 0;
+
+	/* don't change MTU on "live" link (peer won't know) */
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		status = -EBUSY;
+	else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN)
+		status = -ERANGE;
+	else
+		net->mtu = new_mtu;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return status;
+}
+
+static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p)
+{
+	struct eth_dev *dev = netdev_priv(net);
+
+	strlcpy(p->driver, "g_ether", sizeof(p->driver));
+	strlcpy(p->version, UETH__VERSION, sizeof(p->version));
+	strlcpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
+	strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
+}
+
+/* REVISIT can also support:
+ *   - WOL (by tracking suspends and issuing remote wakeup)
+ *   - msglevel (implies updated messaging)
+ *   - ... probably more ethtool ops
+ */
+
+static const struct ethtool_ops ops = {
+	.get_drvinfo = eth_get_drvinfo,
+	.get_link = ethtool_op_get_link,
+};
+
+static void defer_kevent(struct eth_dev *dev, int flag)
+{
+	if (test_and_set_bit(flag, &dev->todo))
+		return;
+	if (!schedule_work(&dev->work))
+		ERROR(dev, "kevent %d may have been dropped\n", flag);
+	else
+		DBG(dev, "kevent %d scheduled\n", flag);
+}
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req);
+static void tx_complete(struct usb_ep *ep, struct usb_request *req);
+
+#ifdef CONFIG_ASR_TOE
+#define ECM_RSV_HEADROM_SIZE	(64)
+#define MBIM_RSV_HEADROOM_SIZE	(64)
+static int
+rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
+{
+	struct sk_buff	*skb;
+	int		retval = -ENOMEM;
+	size_t		size = 0;
+	struct usb_ep	*out;
+	unsigned long	flags;
+
+	if (u_ether_rx_aggr.nr_rx_req >= MAX_RX_REQ_NUMBER) {
+		retval = -EAGAIN;
+		return retval;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		pr_err("%s: usb disconnected\n", __func__);
+		return -ENOTCONN;
+	}
+
+	/* Padding up to RX_EXTRA handles minor disagreements with host.
+	 * Normally we use the USB "terminate on short read" convention;
+	 * so allow up to (N*maxpacket), since that memory is normally
+	 * already allocated.  Some hardware doesn't deal well with short
+	 * reads (e.g. DMA must be N*maxpacket), so for now don't trim a
+	 * byte off the end (to force hardware errors on overflow).
+	 *
+	 * RNDIS uses internal framing, and explicitly allows senders to
+	 * pad to end-of-packet.  That's potentially nice for speed, but
+	 * means receivers can't recover lost synch on their own (because
+	 * new packets don't only start after a short RX).
+	 */
+	size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
+	size += dev->port_usb->header_len;
+	size += out->maxpacket - 1;
+	size -= size % out->maxpacket;
+
+	if (dev->ul_max_pkts_per_xfer)
+		size *= dev->ul_max_pkts_per_xfer;
+
+	if (dev->port_usb->is_fixed)
+		size = max_t(size_t, size, dev->port_usb->fixed_out_len);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+#ifdef CONFIG_ASR_TOE_USE_SMALL_MEMORY
+	/* set it as 2048 to make it 1024 bytes aligned */
+	if (unlikely(size > 0x10000)) {
+		ERROR(dev, "skb size error: %d\n", size);
+		BUG();
+	}
+	size = 2048;
+
+	if (dev->port_usb->ueth_type == UETHER_NCM || 
+			dev->port_usb->ueth_type == UETHER_MBIM)
+		req->real_length = dev->port_usb->fixed_out_len;
+	else
+		req->real_length = RX_SMALL_BUFF_SIZE;
+
+	skb = bm_usb_alloc_skb(req->real_length, gfp_flags);
+#else
+	req->real_length = 0;
+	pr_debug("%s: size: %zu", __func__, size);
+	skb = bm_usb_alloc_skb(size + NET_IP_ALIGN, gfp_flags);
+#endif
+
+	if (skb == NULL) {
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+		dev->skb_alloc_fail++;
+#endif
+		DBG(dev, "no rx skb\n");
+		retval = -ENOMEM;
+		goto enomem;
+	} else {
+		if (unlikely(!skb->data)) {
+			ERROR(dev, "skb data NULL\n");
+			BUG();
+		}
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+		dev->skb_alloc_success++;
+#endif
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		pr_err("%s: usb disconnected\n", __func__);
+                retval = -ENOTCONN;
+		goto enomem;
+	}
+
+	/* Some platforms perform better when IP packets are aligned,
+	 * but on at least one, checksumming fails otherwise.  Note:
+	 * RNDIS headers involve variable numbers of LE32 values.
+	 */
+	skb_reserve(skb, NET_IP_ALIGN);
+
+	/* extra headroom for toe header etc */
+	if (dev->port_usb->ueth_type == UETHER_ECM)
+		skb_reserve(skb, ECM_RSV_HEADROM_SIZE);
+	else if (dev->port_usb->ueth_type == UETHER_MBIM ||
+				dev->port_usb->ueth_type == UETHER_NCM)
+		skb_reserve(skb, MBIM_RSV_HEADROOM_SIZE);
+
+	req->buf = skb->data;
+	req->length = size;
+	req->context = skb;
+
+#ifndef CONFIG_USB_DWC3
+	/* Always active, handled in the low level driver*/
+	req->no_interrupt = 1;
+#endif
+
+	if (likely(is_bm_va(skb->data)))
+		req->is_toe_req = 1;
+	else
+		req->is_toe_req = 0;
+
+	if (u_ether_rx_aggr.nr_rx_req < MAX_RX_REQ_NUMBER) {
+		u_ether_rx_aggr.rx_req_array[u_ether_rx_aggr.nr_rx_req]
+				= req;
+		u_ether_rx_aggr.nr_rx_req++;
+		retval = 0;
+	} else {
+		retval = -EAGAIN;
+	}
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+enomem:
+	if (retval) {
+		if (retval != -ENOMEM)
+			pr_err_ratelimited("%s: rx submit --> %d\n", __func__, retval);
+		if (skb)
+			dev_kfree_skb_any(skb);
+	}
+	return retval;
+}
+#else
+static int
+rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
+{
+	struct sk_buff	*skb;
+	int		retval = -ENOMEM;
+	size_t		size = 0;
+	struct usb_ep	*out;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return -ENOTCONN;
+	}
+
+	/* Padding up to RX_EXTRA handles minor disagreements with host.
+	 * Normally we use the USB "terminate on short read" convention;
+	 * so allow up to (N*maxpacket), since that memory is normally
+	 * already allocated.  Some hardware doesn't deal well with short
+	 * reads (e.g. DMA must be N*maxpacket), so for now don't trim a
+	 * byte off the end (to force hardware errors on overflow).
+	 *
+	 * RNDIS uses internal framing, and explicitly allows senders to
+	 * pad to end-of-packet.  That's potentially nice for speed, but
+	 * means receivers can't recover lost synch on their own (because
+	 * new packets don't only start after a short RX).
+	 */
+	size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
+	size += dev->port_usb->header_len;
+	size += out->maxpacket - 1;
+	size -= size % out->maxpacket;
+
+	if (dev->ul_max_pkts_per_xfer)
+		size *= dev->ul_max_pkts_per_xfer;
+
+	if (dev->port_usb->is_fixed)
+		size = max_t(size_t, size, dev->port_usb->fixed_out_len);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	pr_debug("%s: size: %zu", __func__, size);
+	skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags);
+	if (skb == NULL) {
+		ERROR(dev, "no rx skb\n");
+		goto enomem;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb)
+		out = dev->port_usb->out_ep;
+	else
+		out = NULL;
+
+	if (!out) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		goto enomem;
+	}
+
+	/* Some platforms perform better when IP packets are aligned,
+	 * but on at least one, checksumming fails otherwise.  Note:
+	 * RNDIS headers involve variable numbers of LE32 values.
+	 */
+	skb_reserve(skb, NET_IP_ALIGN);
+
+	req->buf = skb->data;
+	req->length = size;
+	req->context = skb;
+
+#ifndef CONFIG_USB_DWC3
+	/* Always active, handled in the low level driver*/
+	req->no_interrupt = 1;
+#endif
+
+	req->is_toe_req = 0;
+	retval = usb_ep_queue(out, req, gfp_flags);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (retval == -ENOMEM)
+enomem:
+		defer_kevent(dev, WORK_RX_MEMORY);
+	if (retval) {
+		ERROR(dev, "rx submit --> %d\n", retval);
+		if (skb)
+			dev_kfree_skb_any(skb);
+	}
+	return retval;
+}
+#endif
+static void rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sk_buff	*skb = req->context;
+	struct eth_dev	*dev = ep->driver_data;
+	int		status = req->status;
+	bool		queue = false;
+	static bool	drop_next_pkt = false;
+#ifdef CONFIG_ASR_TOE_USE_SMALL_MEMORY
+	int		rx_max_size = RX_SMALL_BUFF_SIZE;
+#endif
+
+	if (unlikely(drop_next_pkt)) {
+		ERROR(dev, "drop this error pkt\n");
+		drop_next_pkt = false;
+		dev_kfree_skb_any(skb);
+		dev->net->stats.rx_errors++;
+		goto clean;
+	}
+	switch (status) {
+
+	/* normal completion */
+	case 0:
+#ifdef CONFIG_ASR_TOE_USE_SMALL_MEMORY
+		if (dev->port_usb->ueth_type == UETHER_NCM || 
+				dev->port_usb->ueth_type == UETHER_MBIM)
+            rx_max_size = dev->port_usb->fixed_out_len;
+
+		if (unlikely(req->actual > rx_max_size)) {
+			ERROR(dev, "req actual: %d\n", req->actual);
+			WARN_ON(1);
+			drop_next_pkt = true;
+			dev->net->stats.rx_errors++;
+			goto quiesce;
+		}
+#endif
+		skb_put(skb, req->actual);
+
+#ifndef CONFIG_ASR_TOE
+		if (dev->unwrap) {
+			unsigned long	flags;
+
+			spin_lock_irqsave(&dev->lock, flags);
+			if (dev->port_usb) {
+				status = dev->unwrap(dev->port_usb,
+							skb,
+							&dev->rx_frames);
+				if (status == -EINVAL)
+					dev->net->stats.rx_errors++;
+				else if (status == -EOVERFLOW)
+					dev->net->stats.rx_over_errors++;
+			} else {
+				dev_kfree_skb_any(skb);
+				status = -ENOTCONN;
+			}
+			spin_unlock_irqrestore(&dev->lock, flags);
+		} else {
+			skb_queue_tail(&dev->rx_frames, skb);
+		}
+#else
+		if (unlikely((system_is_rdp_mode() ||
+			(dev->port_usb->ueth_type == UETHER_ECM) ||
+			(dev->port_usb->ueth_type == UETHER_MBIM) ||
+			(dev->port_usb->ueth_type == UETHER_NCM)) && dev->unwrap)) {
+#ifdef CONFIG_UETHER_SKB_LLIST
+			struct sk_buff_head	rx_frames;
+
+			skb_queue_head_init(&rx_frames);
+			if (dev->port_usb) {
+				status = dev->unwrap(dev->port_usb,
+							skb,
+							&rx_frames);
+				if (status == -EINVAL)
+					dev->net->stats.rx_errors++;
+				else if (status == -EOVERFLOW)
+					dev->net->stats.rx_over_errors++;
+			} else {
+				dev_kfree_skb_any(skb);
+				status = -ENOTCONN;
+			}
+			while ((skb = skb_dequeue(&rx_frames)))
+				skb_llist_enqueue(&dev->rx_frames, skb);
+#else
+			if (dev->port_usb) {
+				status = dev->unwrap(dev->port_usb,
+							skb,
+							&dev->rx_frames);
+				if (status == -EINVAL)
+					dev->net->stats.rx_errors++;
+				else if (status == -EOVERFLOW)
+					dev->net->stats.rx_over_errors++;
+			} else {
+				dev_kfree_skb_any(skb);
+				status = -ENOTCONN;
+			}
+#endif
+		} else {
+#ifdef CONFIG_UETHER_SKB_LLIST
+			skb_llist_enqueue(&dev->rx_frames, skb);
+#else
+			skb_queue_tail(&dev->rx_frames, skb);
+#endif
+		}
+#endif
+
+		if (!status)
+			queue = true;
+		break;
+
+	/* software-driven interface shutdown */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		VDBG(dev, "rx shutdown, code %d\n", status);
+		goto quiesce;
+
+	/* for hardware automagic (such as pxa) */
+	case -ECONNABORTED:		/* endpoint reset */
+		ERROR(dev, "rx %s reset\n", ep->name);
+		defer_kevent(dev, WORK_RX_MEMORY);
+quiesce:
+		dev_kfree_skb_any(skb);
+		goto clean;
+
+	/* data overrun */
+	case -EOVERFLOW:
+		dev->net->stats.rx_over_errors++;
+		/* FALLTHROUGH */
+
+	default:
+		queue = true;
+		dev_kfree_skb_any(skb);
+		dev->net->stats.rx_errors++;
+		DBG(dev, "rx status %d\n", status);
+		break;
+	}
+
+clean:
+	spin_lock(&dev->req_lock);
+	list_add(&req->list, &dev->rx_reqs);
+	atomic_inc(&dev->nr_rx_req_free);
+	spin_unlock(&dev->req_lock);
+#ifndef USB_RX_USE_WORK
+	if (queue)
+		tasklet_schedule(&dev->rx_tl);
+#else
+#ifdef CONFIG_ASR_TOE
+	if (queue) {
+		if (nr_cpu_ids > 1)
+			queue_delayed_work_on((nr_cpu_ids - 2),
+				system_highpri_wq, &dev->rx_work, 0);
+		else
+			queue_delayed_work_on(0, system_highpri_wq, &dev->rx_work, 0);
+	}
+#else
+	if (queue)
+		schedule_delayed_work(&dev->rx_work, 0);
+#endif
+#endif
+
+#if 0
+	if (netif_running(dev->net) && atomic_read(&dev->nr_rx_req_free) >= MAX_RX_REQ_NUMBER)
+		rx_fill(dev, GFP_ATOMIC);
+#endif
+}
+
+static inline void usb_ep_free_request_tx_mult(struct usb_ep *ep,
+					        struct usb_request *req)
+{
+#ifdef CONFIG_ASR_TOE
+	if (AGGRCTX(req)->is_toe)
+		req_toe_aggr_clean(req);
+	else
+#endif
+		req_aggr_clean(req);
+
+	BUG_ON(!AGGRCTX(req));
+	kfree(AGGRCTX(req));
+	req->context = NULL;
+	usb_ep_free_request(ep, req);
+}
+
+static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
+{
+	unsigned		i;
+	struct usb_request	*req;
+	bool			usb_in;
+
+	if (!n)
+		return -ENOMEM;
+
+	if (ep->desc->bEndpointAddress & USB_DIR_IN)
+		usb_in = true;
+	else
+		usb_in = false;
+
+	/* queue/recycle up to N requests */
+	i = n;
+	list_for_each_entry(req, list, list) {
+		if (i-- == 0)
+			goto extra;
+	}
+
+
+	while (i--) {
+		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+		if (!req)
+			return list_empty(list) ? -ENOMEM : 0;
+		/* update completion handler */
+		if (usb_in){
+			req->complete = tx_complete;
+			req->context = (void*)kzalloc(sizeof(struct aggr_ctx), GFP_ATOMIC);
+			if (!req->context){
+				usb_ep_free_request(ep, req);
+				pr_err("%s:%d: error: only %d reqs allocated\n",
+				           __func__, __LINE__, (n - i));
+				return list_empty(list) ? -ENOMEM : 0;
+			}
+			skb_queue_head_init(&AGGRCTX(req)->skb_list);
+#ifdef CONFIG_ASR_TOE
+			INIT_LIST_HEAD(&AGGRCTX(req)->toe_list);
+			AGGRCTX(req)->is_toe = false;
+#endif
+			AGGRCTX(req)->total_size = 0;
+		} else
+			req->complete = rx_complete;
+		list_add(&req->list, list);
+	}
+
+	return 0;
+
+extra:
+	/* free extras */
+	for (;;) {
+		struct list_head	*next;
+
+		next = req->list.next;
+		list_del(&req->list);
+		if (usb_in) {
+			usb_ep_free_request_tx_mult(ep, req);
+		} else
+			usb_ep_free_request(ep, req);
+
+		if (next == list)
+			break;
+
+		req = container_of(next, struct usb_request, list);
+	}
+	return 0;
+}
+
+static int alloc_requests(struct eth_dev *dev, struct gether *link,
+			   unsigned n_rx, unsigned n_tx)
+{
+	int	status;
+
+	spin_lock(&dev->req_lock);
+	status = prealloc(&dev->tx_reqs, link->in_ep, n_tx);
+	if (status < 0)
+		goto fail;
+	status = prealloc(&dev->rx_reqs, link->out_ep, n_rx);
+	if (status < 0)
+		goto fail;
+	goto done;
+fail:
+	INFO(dev, "can't alloc requests\n");
+done:
+	spin_unlock(&dev->req_lock);
+	return status;
+}
+#ifdef CONFIG_ASR_TOE
+static void usb_aggr_free_requests(struct eth_dev *dev, struct uether_rx_aggr *uther_aggr)
+{
+	int i;
+	unsigned long		flags;
+
+	spin_lock_irqsave(&dev->req_lock, flags);
+
+	if (uther_aggr->nr_rx_req == 0) {
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		return;
+	}
+
+	for (i = 0; i < uther_aggr->nr_rx_req; i++) {
+		if (uther_aggr->rx_req_array[i]) {
+			list_add(&uther_aggr->rx_req_array[i]->list, &dev->rx_reqs);
+			dev_kfree_skb_any((struct sk_buff *)uther_aggr->rx_req_array[i]->context);
+			atomic_inc(&dev->nr_rx_req_free);
+		}
+	}
+	uther_aggr->nr_rx_req = 0;
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+}
+
+static void usb_aggr_wait_free(struct eth_dev *dev, struct uether_rx_aggr *uther_aggr)
+{
+	int timeout;
+
+
+	if (uther_aggr->nr_rx_req == 0)
+		return;
+
+	timeout = 1000;
+	while (uther_aggr->nr_rx_req && timeout--)
+		mdelay(1);
+
+	if (timeout <= 0)
+		pr_err("!!! ether_aggr not freed: %d\n", uther_aggr->nr_rx_req);
+}
+
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
+{
+	struct usb_request	*req;
+	unsigned long		flags;
+	int			req_cnt = 0;
+	static unsigned g_qlen = 0;
+	int ret = 0, ret_queue = 0;
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	dev->usbnet_rx_num++;
+#endif
+	/* fill unused rxq slots with some skb */
+	spin_lock_irqsave(&dev->req_lock, flags);
+	if (unlikely(g_qlen == 0))
+		g_qlen = qlen(dev->gadget, true);
+	while (!list_empty(&dev->rx_reqs)) {
+		/* break the nexus of continuous completion and re-submission*/
+		if (++req_cnt > g_qlen)
+			break;
+
+		req = container_of(dev->rx_reqs.next,
+				struct usb_request, list);
+		list_del_init(&req->list);
+		atomic_dec(&dev->nr_rx_req_free);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+
+		ret = rx_submit(dev, req, gfp_flags);
+		if (ret < 0) {
+			if (ret == -ENOMEM || ret == -EAGAIN) {
+				spin_lock_irqsave(&dev->req_lock, flags);
+				list_add(&req->list, &dev->rx_reqs);
+				atomic_inc(&dev->nr_rx_req_free);
+				spin_unlock_irqrestore(&dev->req_lock, flags);
+				req_cnt--;
+				if (u_ether_rx_aggr.nr_rx_req && dev->port_usb) {
+					ret_queue = usb_ep_queue_mult(dev->port_usb->out_ep, GFP_ATOMIC,
+						(void *)&u_ether_rx_aggr);
+				} else {
+					ret_queue = 0;
+				}
+				if (u_ether_rx_aggr.nr_rx_req != 0 || (!netif_carrier_ok(dev->net))
+					|| (!dev->eth_started)) {
+					usb_aggr_free_requests(dev, &u_ether_rx_aggr);
+					ERROR(dev, "%s, usb_ep_queue_mult error, ret: %d, ret_queue: %d\n", __func__, ret, ret_queue);
+					return;
+				}
+				if (ret_queue == 0 && ret == -ENOMEM) {
+					DBG(dev, "defer rx fill\n");
+					#if 0 /* there is delayed rx fill */
+					defer_kevent(dev, WORK_RX_MEMORY);
+					#endif
+					return;
+				}
+			} else {
+				if (u_ether_rx_aggr.nr_rx_req != 0)
+                			usb_aggr_free_requests(dev, &u_ether_rx_aggr);
+				ERROR(dev, "%s, unknown error: %d\n", __func__, ret);
+				return;
+			}
+		}
+		spin_lock_irqsave(&dev->req_lock, flags);
+	}
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	if (u_ether_rx_aggr.nr_rx_req && dev->port_usb) {
+		ret_queue = usb_ep_queue_mult(dev->port_usb->out_ep, GFP_ATOMIC,
+			(void *)&u_ether_rx_aggr);
+	}
+
+	if (u_ether_rx_aggr.nr_rx_req != 0) {
+		usb_aggr_free_requests(dev, &u_ether_rx_aggr);
+		ERROR(dev, "%s, usb_ep_queue_mult2 error, ret: %d, ret_queue: %d\n", __func__, ret, ret_queue);
+	}
+}
+#else
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
+{
+	struct usb_request	*req;
+	unsigned long		flags;
+	int			req_cnt = 0;
+
+	/* fill unused rxq slots with some skb */
+	spin_lock_irqsave(&dev->req_lock, flags);
+	while (!list_empty(&dev->rx_reqs)) {
+		/* break the nexus of continuous completion and re-submission*/
+		if (++req_cnt > qlen(dev->gadget, true))
+			break;
+
+		req = container_of(dev->rx_reqs.next,
+				struct usb_request, list);
+		list_del_init(&req->list);
+		atomic_dec(&dev->nr_rx_req_free);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+
+		if (rx_submit(dev, req, gfp_flags) < 0) {
+			spin_lock_irqsave(&dev->req_lock, flags);
+			list_add(&req->list, &dev->rx_reqs);
+			atomic_inc(&dev->nr_rx_req_free);
+			spin_unlock_irqrestore(&dev->req_lock, flags);
+			defer_kevent(dev, WORK_RX_MEMORY);
+			return;
+		}
+
+		spin_lock_irqsave(&dev->req_lock, flags);
+	}
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+}
+#endif
+
+static void process_rx_tl(unsigned long priv)
+{
+	struct eth_dev	*dev = (struct eth_dev *)priv;
+	struct sk_buff	*skb;
+	int		status = 0;
+#ifdef CONFIG_ASR_TOE_AGGR
+	int cur_index;
+	int len;
+	//static u64 old_jiffies;
+#endif
+
+	if (!dev->port_usb)
+		return;
+
+#ifdef CONFIG_ASR_TOE_AGGR
+
+	/* disable the timer to wait for more packets */
+#if 0
+	if (likely(!system_is_rdp_mode())) {
+		if (old_jiffies == 0)
+			old_jiffies = jiffies;
+	#ifdef CONFIG_UETHER_SKB_LLIST
+		if (skb_llist_len(&dev->rx_frames) < NR_RX_REQ_BURST_NUM && jiffies == old_jiffies) {
+	#else
+		if (skb_queue_len(&dev->rx_frames) < NR_RX_REQ_BURST_NUM && jiffies == old_jiffies) {
+	#endif
+			if (nr_cpu_ids > 1)
+				queue_delayed_work_on((nr_cpu_ids - 2),
+					system_highpri_wq, &dev->rx_work, 1);
+			else
+				queue_delayed_work_on(0, system_highpri_wq, &dev->rx_work, 1);
+			return;
+		}
+		old_jiffies = jiffies;
+	}
+#endif
+
+restart:
+	if (likely(!system_is_rdp_mode()) && likely(is_bm_va(skb->data)))
+		cur_index = toe_rndis_start_input();
+	len = 0;
+
+#ifdef CONFIG_UETHER_SKB_LLIST
+	while ((skb = skb_llist_dequeue(&dev->rx_frames))) {
+#else
+	while ((skb = skb_dequeue(&dev->rx_frames))) {
+#endif
+		if (dev->unwrap_fixup) {
+			struct sk_buff *new =
+				dev->unwrap_fixup(dev->port_usb, skb);
+			if (!new) {
+				pr_info("unwrap_fixup failed\n");
+				dev_kfree_skb_any(skb);
+				continue;
+			}
+
+			dev_kfree_skb_any(skb);
+			skb = new;
+			WARN_ON(!skb_mac_header_was_set(skb));
+		}
+
+		if (likely(is_bm_va(skb->data))) {
+			len++;
+			dev->net->stats.rx_packets++;
+			dev->net->stats.rx_bytes += (skb->len - sizeof(struct rndis_packet_msg_type));
+			if (toe_rndis_add_input(skb, dev->net)) {
+				toe_rndis_trigger_input();
+				goto restart;
+			}
+		} else {
+			if (likely(!system_is_rdp_mode()))
+				skb_pull(skb, sizeof(struct rndis_packet_msg_type));
+			skb->protocol = eth_type_trans(skb, dev->net);
+			dev->net->stats.rx_packets++;
+			dev->net->stats.rx_bytes += skb->len;
+			status = netif_rx(skb);
+		}
+
+		if (status < 0) {
+			dev->net->stats.rx_errors++;
+			dev->net->stats.rx_length_errors++;
+			INFO(dev, "rx length %d\n", skb->len);
+			dev_kfree_skb_any(skb);
+			continue;
+		}
+	}
+
+	if (len)
+		toe_rndis_trigger_input();
+#else
+#ifdef CONFIG_UETHER_SKB_LLIST
+	while ((skb = skb_llist_dequeue(&dev->rx_frames))) {
+#else
+	while ((skb = skb_dequeue(&dev->rx_frames))) {
+#endif
+#ifdef CONFIG_ASR_TOE
+		if (status < 0) {
+#else
+		if (status < 0
+				|| ETH_HLEN > skb->len
+				|| skb->len > VLAN_ETH_FRAME_LEN) {
+#endif
+			dev->net->stats.rx_errors++;
+			dev->net->stats.rx_length_errors++;
+			INFO(dev, "rx length %d\n", skb->len);
+			dev_kfree_skb_any(skb);
+			continue;
+		}
+
+		if (dev->unwrap_fixup) {
+			struct sk_buff *new =
+				dev->unwrap_fixup(dev->port_usb, skb);
+			if (!new) {
+				pr_info("unwrap_fixup failed\n");
+				dev_kfree_skb_any(skb);
+				continue;
+			}
+
+			dev_kfree_skb_any(skb);
+			skb = new;
+			WARN_ON(!skb_mac_header_was_set(skb));
+		}
+
+#ifdef CONFIG_ASR_TOE
+		dev->net->stats.rx_packets++;
+		dev->net->stats.rx_bytes += (skb->len - sizeof(struct rndis_packet_msg_type));
+		status = toe_rndis_input(skb, dev->net);
+#else
+		skb->protocol = eth_type_trans(skb, dev->net);
+		dev->net->stats.rx_packets++;
+		dev->net->stats.rx_bytes += skb->len;
+		status = netif_rx(skb);
+#endif
+	}
+#endif
+
+#if 0
+	if (netif_running(dev->net) && atomic_read(&dev->nr_rx_req_free) >= MAX_RX_REQ_NUMBER)
+		rx_fill(dev, GFP_ATOMIC);
+#endif
+}
+
+#ifdef USB_RX_USE_WORK
+static DEFINE_MUTEX(rx_work_lock);
+
+static void process_rx_work(struct work_struct *data)
+{
+	struct delayed_work *dw = to_delayed_work(data);
+	struct eth_dev	*dev = container_of(dw, struct eth_dev, rx_work);
+
+	mutex_lock(&rx_work_lock);
+	local_bh_disable();
+	process_rx_tl((unsigned long)dev);
+	local_bh_enable();
+	mutex_unlock(&rx_work_lock);
+}
+#endif
+
+#if defined(USB_RX_USE_WORK) && defined(CONFIG_USB_MV_HSIC_UDC)
+static DEFINE_MUTEX(hsic_rx_work_lock);
+
+static void process_hsic_rx_work(struct work_struct *data)
+{
+	struct eth_dev	*dev = container_of(data, struct eth_dev, rx_work);
+
+	mutex_lock(&hsic_rx_work_lock);
+	local_bh_disable();
+	process_rx_tl((unsigned long)dev);
+	local_bh_enable();
+	mutex_unlock(&hsic_rx_work_lock);
+}
+#endif
+
+static void eth_work(struct work_struct *work)
+{
+	struct eth_dev	*dev = container_of(work, struct eth_dev, work);
+
+	if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) {
+		if (netif_running(dev->net))
+			rx_fill(dev, GFP_KERNEL);
+	}
+
+	if (dev->todo)
+		DBG(dev, "work done, flags = 0x%lx\n", dev->todo);
+}
+
+static void tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct eth_dev	*dev;
+	struct net_device *net;
+	struct usb_request *new_req;
+	struct usb_ep *in;
+	int length;
+	int retval;
+	int skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+
+	if (!ep->driver_data) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	dev = ep->driver_data;
+	net = dev->net;
+
+	if (!dev->port_usb) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	switch (req->status) {
+	default:
+		dev->net->stats.tx_errors += skb_qlen;
+		VDBG(dev, "tx err %d\n", req->status);
+		/* FALLTHROUGH */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		break;
+	case 0:
+		if (!req->zero)
+			dev->net->stats.tx_bytes += req->length-1;
+		else
+			dev->net->stats.tx_bytes += req->length;
+		dev->net->stats.tx_packets += skb_qlen;
+	}
+
+	spin_lock(&dev->req_lock);
+	req_aggr_clean(req);
+
+	list_add_tail(&req->list, &dev->tx_reqs);
+	atomic_dec(&dev->no_tx_req_used);
+	in = dev->port_usb->in_ep;
+
+	if (0 /* !list_empty(&dev->tx_reqs) */) {
+		new_req = container_of(dev->tx_reqs.next,
+				struct usb_request, list);
+		list_del(&new_req->list);
+		spin_unlock(&dev->req_lock);
+		if (AGGRCTX(new_req)->total_size > 0) {
+			length = AGGRCTX(new_req)->total_size;
+
+			/* NCM requires no zlp if transfer is
+			 * dwNtbInMaxSize */
+			if (dev->port_usb->is_fixed &&
+				length == dev->port_usb->fixed_in_len &&
+				(length % in->maxpacket) == 0)
+				new_req->zero = 0;
+			else
+				new_req->zero = 1;
+
+			/* use zlp framing on tx for strict CDC-Ether
+			 * conformance, though any robust network rx
+			 * path ignores extra padding. and some hardware
+			 * doesn't like to write zlps.
+			 */
+			if (new_req->zero && !dev->zlp &&
+					(length % in->maxpacket) == 0) {
+				new_req->zero = 0;
+				length++;
+			}
+			new_req->length = length;
+			new_req->is_toe_req = 0;
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+			skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+			if (skb_qlen > histogram_size) {
+				histogram_realloc(dev);
+				pr_err("%s: %d histogram buffer to small, realloc to %d, "
+				      "hold_count=%d\n", __func__, __LINE__, histogram_size,
+				      skb_qlen);
+			}
+			if (dev->tx_mult_histogram)
+				dev->tx_mult_histogram[skb_qlen - 1]++;
+#endif
+#endif
+			retval = usb_ep_queue(in, new_req, GFP_ATOMIC);
+			switch (retval) {
+			default:
+				DBG(dev, "tx queue err %d\n", retval);
+				pr_err_ratelimited("tx queue err1: %d", retval);
+				dev->net->stats.tx_dropped +=
+					              skb_queue_len(&AGGRCTX(new_req)->skb_list);
+				req_aggr_clean(req);
+				spin_lock(&dev->req_lock);
+				list_add_tail(&new_req->list,
+						&dev->tx_reqs);
+				spin_unlock(&dev->req_lock);
+				break;
+			case 0:
+				atomic_inc(&dev->no_tx_req_used);
+				/* net->trans_start = jiffies; */
+			}
+		} else {
+			spin_lock(&dev->req_lock);
+			/*
+			 * Put the idle request at the back of the
+			 * queue. The xmit function will put the
+			 * unfinished request at the beginning of the
+			 * queue.
+			 */
+			list_add_tail(&new_req->list, &dev->tx_reqs);
+			spin_unlock(&dev->req_lock);
+		}
+	} else {
+		spin_unlock(&dev->req_lock);
+	}
+
+	if (netif_carrier_ok(dev->net))
+		netif_wake_queue(dev->net);
+}
+
+#ifdef CONFIG_ASR_TOE
+static void toe_tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct eth_dev	*dev;
+	struct net_device *net;
+	int skb_qlen = 1;
+
+	if (!ep->driver_data) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	dev = ep->driver_data;
+	net = dev->net;
+
+	if (!dev->port_usb) {
+		usb_ep_free_request_tx_mult(ep, req);
+		return;
+	}
+
+	switch (req->status) {
+	default:
+		dev->net->stats.tx_errors += skb_qlen;
+		INFO(dev, "tx err %d\n", req->status);
+		/* FALLTHROUGH */
+	case -ECONNRESET:		/* unlink */
+	case -ESHUTDOWN:		/* disconnect etc */
+		break;
+	case 0:
+		if (!req->zero)
+			dev->net->stats.tx_bytes += req->length-1;
+		else
+			dev->net->stats.tx_bytes += req->length;
+		dev->net->stats.tx_packets += skb_qlen;
+	}
+
+	BUG_ON(AGGRCTX(req)->is_toe == false);
+	spin_lock(&dev->req_lock);
+	skb_qlen = req_toe_aggr_clean_nolock(dev, req);
+	BUG_ON(skb_qlen == 0);
+	list_add_tail(&req->list, &dev->tx_reqs);
+	atomic_dec(&dev->no_tx_req_used);
+	spin_unlock(&dev->req_lock);
+
+	if (netif_carrier_ok(dev->net))
+		netif_wake_queue(dev->net);
+}
+#endif
+
+static inline int is_promisc(u16 cdc_filter)
+{
+	return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS;
+}
+
+#ifdef CONFIG_ASR_TOE
+static inline struct toe_pkt_desc *toe_desc_dock(struct toe_pkt_desc *toe_desc, struct aggr_ctx *aggr_ctx)
+{
+	struct toe_pkt_desc *first_desc;
+
+	BUG_ON(!aggr_ctx);
+	BUG_ON(!toe_desc);
+	if (likely(list_empty(&aggr_ctx->toe_list))) {
+		aggr_ctx->total_size = toe_desc->len;
+		list_add_tail(&toe_desc->list, &aggr_ctx->toe_list);
+		aggr_ctx->is_toe = true;
+		return toe_desc;
+	}
+
+	BUG_ON(aggr_ctx->is_toe == false);
+	first_desc = list_first_entry(&aggr_ctx->toe_list, struct toe_pkt_desc, list);
+	aggr_ctx->pending_skb = (struct sk_buff *)toe_desc;
+	return first_desc;
+}
+#endif
+
+static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
+					struct net_device *net)
+{
+	struct eth_dev		*dev = netdev_priv(net);
+	int			skb_qlen = 0;
+	int			length = skb->len;
+	struct 		sk_buff *pending_skb = NULL;
+	int			retval;
+	struct usb_request	*req = NULL;
+	unsigned long		flags;
+	struct usb_ep		*in;
+	u16			cdc_filter;
+	struct toe_pkt_desc *toe_desc = NULL;
+#ifdef CONFIG_ASR_TOE
+	bool	 is_toe = false;
+
+	is_toe = is_bm_va(skb);
+	if (is_toe) {
+		toe_desc = (struct toe_pkt_desc *)skb;
+
+		if (unlikely(toe_desc->pdata < PAGE_OFFSET))
+			pr_err_ratelimited("wrong toe adr: 0x%x-0x%x\n",
+				toe_desc->pdata,
+				toe_desc->len);
+	}
+#endif
+
+	if (unlikely(!skb))
+		return NETDEV_TX_OK;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb) {
+		in = dev->port_usb->in_ep;
+		cdc_filter = dev->port_usb->cdc_filter;
+	} else {
+		in = NULL;
+		cdc_filter = 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	if (!in) {
+#ifdef CONFIG_ASR_TOE
+		if (is_toe) {
+			bm_free(toe_desc, sizeof(*toe_desc));
+			return NETDEV_TX_OK;
+		} else {
+			dev_kfree_skb_any(skb);
+			return NETDEV_TX_OK;		
+		}
+#else
+		dev_kfree_skb_any(skb);
+		return NETDEV_TX_OK;
+#endif
+	}
+
+#ifdef CONFIG_ASR_TOE
+	/* apply outgoing CDC or RNDIS filters */
+	if (!is_promisc(cdc_filter) && (!is_toe)) {
+#else
+	/* apply outgoing CDC or RNDIS filters */
+	if (!is_promisc(cdc_filter)) {
+#endif
+		u8		*dest = skb->data;
+		if (is_multicast_ether_addr(dest)) {
+			u16	type;
+
+			/* ignores USB_CDC_PACKET_TYPE_MULTICAST and host
+			 * SET_ETHERNET_MULTICAST_FILTERS requests
+			 */
+			if (is_broadcast_ether_addr(dest))
+				type = USB_CDC_PACKET_TYPE_BROADCAST;
+			else
+				type = USB_CDC_PACKET_TYPE_ALL_MULTICAST;
+			if (!(cdc_filter & type)) {
+				dev_kfree_skb_any(skb);
+				return NETDEV_TX_OK;
+			}
+		}
+		/* ignores USB_CDC_PACKET_TYPE_DIRECTED */
+	}
+
+	spin_lock_irqsave(&dev->req_lock, flags);
+	/*
+	 * this freelist can be empty if an interrupt triggered disconnect()
+	 * and reconfigured the gadget (shutting down this queue) after the
+	 * network stack decided to xmit but before we got the spinlock.
+	 */
+	if (list_empty(&dev->tx_reqs)) {
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		return NETDEV_TX_BUSY;
+	}
+
+	req = container_of(dev->tx_reqs.next, struct usb_request, list);
+	list_del(&req->list);
+
+	/* temporarily stop TX queue when the freelist empties */
+	if (list_empty(&dev->tx_reqs))
+		netif_stop_queue(net);
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	BUG_ON(!AGGRCTX(req));
+
+#ifdef CONFIG_ASR_TOE
+	if (!is_toe)
+#endif
+
+#if 0
+	BUG_ON(skb->next || skb->prev);
+#else
+	BUG_ON(skb->next);
+#endif
+
+#ifdef CONFIG_ASR_TOE
+	if (is_toe) {
+		spin_lock(&dev->req_lock);
+		toe_desc = toe_desc_dock(toe_desc, AGGRCTX(req));
+		spin_unlock(&dev->req_lock);
+		goto skip_wrap;
+	}
+#endif
+	/* no buffer copies needed, unless the network stack did it
+	 * or the hardware can't use skb buffers.
+	 * or there's not enough space for extra headers we need
+	 */
+	if (dev->wrap) {
+		unsigned long	flags;
+
+		spin_lock_irqsave(&dev->lock, flags);
+		if (dev->port_usb)
+			skb = dev->wrap(dev->port_usb, skb, AGGRCTX(req));
+		spin_unlock_irqrestore(&dev->lock, flags);
+		if (!skb)
+			goto drop;
+#ifdef CONFIG_ASR_TOE
+		is_toe = AGGRCTX(req)->is_toe;
+		if (is_toe) {
+			toe_desc = (struct toe_pkt_desc *)skb;
+			goto skip_wrap;
+		}
+#endif
+	}
+
+#ifdef CONFIG_ASR_TOE
+skip_wrap:
+	if (is_toe) {
+		req->buf = (void *)toe_desc->pdata;
+		req->complete = toe_tx_complete;
+		req->is_toe_req = 1;
+		if (AGGRCTX(req)->total_size > 0) {
+			BUG_ON(list_empty(&AGGRCTX(req)->toe_list));
+			length = AGGRCTX(req)->total_size;
+		} else {
+			BUG_ON(!list_empty(&AGGRCTX(req)->toe_list));
+			list_add_tail(&toe_desc->list, &AGGRCTX(req)->toe_list);
+			length = toe_desc->len;
+		}
+	} else {
+		req->buf = skb->data;
+		req->complete = tx_complete;
+		req->is_toe_req = 0;
+		if (AGGRCTX(req)->total_size > 0) {
+			BUG_ON(skb_queue_empty(&AGGRCTX(req)->skb_list));
+			length = AGGRCTX(req)->total_size;
+		} else {
+			BUG_ON(!skb_queue_empty(&AGGRCTX(req)->skb_list));
+			skb_queue_tail(&AGGRCTX(req)->skb_list, skb);
+			length = skb->len;
+		}
+	}
+	atomic_inc(&dev->no_tx_req_used);
+
+	/* NCM requires no zlp if transfer is dwNtbInMaxSize */
+	if (dev->port_usb->is_fixed &&
+	length == dev->port_usb->fixed_in_len &&
+	(length % in->maxpacket) == 0)
+		req->zero = 0;
+	else
+		req->zero = 1;
+	
+	/* use zlp framing on tx for strict CDC-Ether conformance,
+	 * though any robust network rx path ignores extra padding.
+	 * and some hardware doesn't like to write zlps.
+	 */
+	if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) {
+		req->zero = 0;
+		length++;
+	}
+	req->length = length;
+	
+	/* throttle highspeed IRQ rate back slightly */
+#if defined(CONFIG_USB_DWC3) || defined(CONFIG_USB_DWC2)
+	req->no_interrupt = 0;
+#else
+	if (gadget_is_dualspeed(dev->gadget) &&
+		(dev->gadget->speed >= USB_SPEED_HIGH)) {
+		dev->tx_qlen++;
+		if (dev->tx_qlen == TX_REQ_THRESHOLD) {
+			req->no_interrupt = 0;
+			dev->tx_qlen = 0;
+		} else {
+			req->no_interrupt = 0;
+		}
+	} else {
+		req->no_interrupt = 0;
+	}
+#endif
+	pending_skb = AGGRCTX(req)->pending_skb;
+	AGGRCTX(req)->pending_skb = NULL;
+#else
+	req->buf = skb->data;
+
+	if (AGGRCTX(req)->total_size > 0) {
+		BUG_ON(skb_queue_empty(&AGGRCTX(req)->skb_list));
+		length = AGGRCTX(req)->total_size;
+	} else {
+		BUG_ON(!skb_queue_empty(&AGGRCTX(req)->skb_list));
+		skb_queue_tail(&AGGRCTX(req)->skb_list, skb);
+		length = skb->len;
+	}
+	if (!AGGR_DONE(req) &&
+		      atomic_read(&dev->no_tx_req_used) > TX_REQ_THRESHOLD) {
+
+		BUG_ON(!AGGRCTX(req)->total_size);
+		spin_lock_irqsave(&dev->req_lock, flags);
+		list_add(&req->list, &dev->tx_reqs);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		goto success;
+	}
+
+	atomic_inc(&dev->no_tx_req_used);
+
+	/* NCM requires no zlp if transfer is dwNtbInMaxSize */
+	if (dev->port_usb->is_fixed &&
+	length == dev->port_usb->fixed_in_len &&
+	(length % in->maxpacket) == 0)
+		req->zero = 0;
+	else
+		req->zero = 1;
+	
+	/* use zlp framing on tx for strict CDC-Ether conformance,
+	 * though any robust network rx path ignores extra padding.
+	 * and some hardware doesn't like to write zlps.
+	 */
+	if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) {
+		req->zero = 0;
+		length++;
+	}
+	req->length = length;
+	
+	/* throttle highspeed IRQ rate back slightly */
+	if (gadget_is_dualspeed(dev->gadget) &&
+		(dev->gadget->speed >= USB_SPEED_HIGH)) {
+		dev->tx_qlen++;
+		if (dev->tx_qlen == TX_REQ_THRESHOLD) {
+			req->no_interrupt = 0;
+			dev->tx_qlen = 0;
+		} else {
+			req->no_interrupt = 0;
+		}
+	} else {
+		req->no_interrupt = 0;
+	}
+	req->is_toe_req = 0;
+	pending_skb = AGGRCTX(req)->pending_skb;
+	AGGRCTX(req)->pending_skb = NULL;
+#endif
+
+	retval = usb_ep_queue(in, req, GFP_ATOMIC);
+
+	spin_lock_irqsave(&dev->req_lock, flags);
+	if (likely((retval == 0) && (req->context != NULL))) {
+		skb_qlen = skb_queue_len(&AGGRCTX(req)->skb_list);
+	} else if (unlikely(req->context == NULL)) {
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+		pr_err_ratelimited("req is already freed\n");
+		return NETDEV_TX_OK;
+	}
+	spin_unlock_irqrestore(&dev->req_lock, flags);
+
+	switch (retval) {
+	case 0:
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+		if (skb_qlen > histogram_size) {
+			histogram_realloc(dev);
+			pr_err("%s: %d histogram buffer to small, realloc to %d, "
+				  "hold_count=%d\n", __func__, __LINE__, histogram_size,
+				  skb_qlen);
+		}
+		if (dev->tx_mult_histogram)
+			dev->tx_mult_histogram[skb_qlen - 1]++;
+#endif
+#endif
+		/* net->trans_start = jiffies; */
+		break;
+	default:
+		DBG(dev, "tx queue err %d\n", retval);
+		pr_err_ratelimited("tx queue err2: %d", retval);
+		break;
+	}
+
+	if (retval) {
+		atomic_dec(&dev->no_tx_req_used);
+drop:
+#ifdef CONFIG_ASR_TOE
+		if (is_toe) {
+			dev->net->stats.tx_dropped += 1;
+			req_toe_aggr_clean(req);
+		} else {
+			dev->net->stats.tx_dropped += (skb_qlen == 0) ? 1 : skb_qlen;
+			req_aggr_clean(req);
+		}
+#else
+		dev->net->stats.tx_dropped += (skb_qlen == 0) ? 1 : skb_qlen;
+		req_aggr_clean(req);
+#endif
+		spin_lock_irqsave(&dev->req_lock, flags);
+		if (list_empty(&dev->tx_reqs))
+			netif_start_queue(net);
+		list_add_tail(&req->list, &dev->tx_reqs);
+		spin_unlock_irqrestore(&dev->req_lock, flags);
+	}
+
+	if (pending_skb)
+		return eth_start_xmit(pending_skb, net);
+
+#ifndef CONFIG_ASR_TOE
+success:
+#endif
+	return NETDEV_TX_OK;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
+{
+	DBG(dev, "%s\n", __func__);
+
+	/* fill the rx queue */
+	/* rx_fill(dev, gfp_flags); */
+
+	/* and open the tx floodgates */
+	dev->tx_qlen = 0;
+	netif_wake_queue(dev->net);
+}
+
+static int eth_open(struct net_device *net)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	struct gether	*link;
+
+	DBG(dev, "%s\n", __func__);
+	if (netif_carrier_ok(dev->net))
+		eth_start(dev, GFP_KERNEL);
+
+	spin_lock_irq(&dev->lock);
+	link = dev->port_usb;
+	if (link && link->open)
+		link->open(link);
+	spin_unlock_irq(&dev->lock);
+
+	dev->eth_started = true;
+
+	return 0;
+}
+
+static int eth_stop(struct net_device *net)
+{
+	struct eth_dev	*dev = netdev_priv(net);
+	unsigned long	flags;
+
+	dev->eth_started = false;
+	VDBG(dev, "%s\n", __func__);
+	netif_stop_queue(net);
+
+	INFO(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n",
+		dev->net->stats.rx_packets, dev->net->stats.tx_packets,
+		dev->net->stats.rx_errors, dev->net->stats.tx_errors
+		);
+
+	/* ensure there are no more active requests */
+	spin_lock_irqsave(&dev->lock, flags);
+	if (dev->port_usb) {
+		struct gether	*link = dev->port_usb;
+		const struct usb_endpoint_descriptor *in;
+		const struct usb_endpoint_descriptor *out;
+
+		if (link->close)
+			link->close(link);
+
+		/* NOTE:  we have no abort-queue primitive we could use
+		 * to cancel all pending I/O.  Instead, we disable then
+		 * reenable the endpoints ... this idiom may leave toggle
+		 * wrong, but that's a self-correcting error.
+		 *
+		 * REVISIT:  we *COULD* just let the transfers complete at
+		 * their own pace; the network stack can handle old packets.
+		 * For the moment we leave this here, since it works.
+		 */
+		in = link->in_ep->desc;
+		out = link->out_ep->desc;
+		/* usb_ep_disable(link->in_ep);
+		usb_ep_disable(link->out_ep); */
+		if (netif_carrier_ok(net)) {
+			INFO(dev, "host still using in/out endpoints\n");
+			link->in_ep->desc = in;
+			link->out_ep->desc = out;
+			/* usb_ep_enable(link->in_ep);
+			usb_ep_enable(link->out_ep); */
+		}
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static u8 host_ethaddr[ETH_ALEN];
+
+/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
+static char *dev_addr;
+module_param(dev_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(dev_addr, "Device Ethernet Address");
+
+#ifdef CONFIG_USB_MV_HSIC_UDC
+static u8 host_ethaddr_hsic[ETH_ALEN];
+static char *dev_addr_hsic;
+module_param(dev_addr_hsic, charp, S_IRUGO);
+MODULE_PARM_DESC(dev_addr_hsic, "HSIC Device Ethernet Addr");
+#endif
+
+/* this address is invisible to ifconfig */
+static char *host_addr;
+module_param(host_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(host_addr, "Host Ethernet Address");
+
+void save_usbnet_host_ethaddr(u8 addr[])
+{
+	memcpy(host_ethaddr, addr, ETH_ALEN);
+}
+
+static int get_ether_addr(const char *str, u8 *dev_addr)
+{
+	if (str) {
+		unsigned	i;
+
+		for (i = 0; i < 6; i++) {
+			unsigned char num;
+
+			if ((*str == '.') || (*str == ':'))
+				str++;
+			num = hex_to_bin(*str++) << 4;
+			num |= hex_to_bin(*str++);
+			dev_addr [i] = num;
+		}
+		if (is_valid_ether_addr(dev_addr))
+			return 0;
+	}
+	eth_random_addr(dev_addr);
+	return 1;
+}
+
+static int get_host_ether_addr(u8 *str, u8 *dev_addr)
+{
+	memcpy(dev_addr, str, ETH_ALEN);
+	if (is_valid_ether_addr(dev_addr))
+		return 0;
+
+	random_ether_addr(dev_addr);
+	memcpy(str, dev_addr, ETH_ALEN);
+	return 1;
+}
+
+static const struct net_device_ops eth_netdev_ops = {
+	.ndo_open		= eth_open,
+	.ndo_stop		= eth_stop,
+	.ndo_start_xmit		= eth_start_xmit,
+	.ndo_change_mtu		= ueth_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+static struct device_type gadget_type = {
+	.name	= "gadget",
+};
+
+/**
+ * gether_setup_name - initialize one ethernet-over-usb link
+ * @g: gadget to associated with these links
+ * @ethaddr: NULL, or a buffer in which the ethernet address of the
+ *	host side of the link is recorded
+ * @netname: name for network device (for example, "usb")
+ * Context: may sleep
+ *
+ * This sets up the single network link that may be exported by a
+ * gadget driver using this framework.  The link layer addresses are
+ * set up using module parameters.
+ *
+ * Returns negative errno, or zero on success
+ */
+struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname)
+{
+	struct eth_dev		*dev;
+	struct net_device	*net;
+	int			status;
+
+	net = alloc_etherdev(sizeof *dev);
+	if (!net)
+		return ERR_PTR(-ENOMEM);
+#if 0
+	net->element_used = 0;
+	for (i = 0; i < DEBUG_ELEMENT_CNT; i++) {
+		net->caller_elments[i].func_address = 0;
+		net->caller_elments[i].cnt = 0;
+	}
+#endif
+	dev = netdev_priv(net);
+	spin_lock_init(&dev->lock);
+	spin_lock_init(&dev->req_lock);
+	INIT_WORK(&dev->work, eth_work);
+#ifdef USB_RX_USE_WORK
+	INIT_DELAYED_WORK(&dev->rx_work, process_rx_work);
+#else
+	tasklet_init(&dev->rx_tl, process_rx_tl,(unsigned long)dev);
+#endif
+	INIT_LIST_HEAD(&dev->tx_reqs);
+	INIT_LIST_HEAD(&dev->rx_reqs);
+
+	/* by default we always have a random MAC address */
+	net->addr_assign_type = NET_ADDR_RANDOM;
+
+#ifdef CONFIG_UETHER_SKB_LLIST
+	skb_llist_init(&dev->rx_frames);
+#else
+	skb_queue_head_init(&dev->rx_frames);
+#endif
+	/* network device setup */
+	dev->net = net;
+	snprintf(net->name, sizeof(net->name), "%s%%d", netname);
+
+	if (get_ether_addr(dev_addr, net->dev_addr)) {
+		net->addr_assign_type = NET_ADDR_RANDOM;
+		dev_warn(&g->dev,
+			"using random %s ethernet address\n", "self");
+	} else {
+		net->addr_assign_type = NET_ADDR_SET;
+	}
+
+	if (get_host_ether_addr(host_ethaddr, dev->host_mac))
+		dev_warn(&g->dev, "using random %s ethernet address\n", "host");
+	else
+		dev_warn(&g->dev, "using previous %s ethernet address\n", "host");
+
+	if (ethaddr)
+		memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+
+	net->netdev_ops = &eth_netdev_ops;
+
+	//SET_ETHTOOL_OPS(net, &ops);
+	net->ethtool_ops = &ops;
+
+	dev->gadget = g;
+	SET_NETDEV_DEV(net, &g->dev);
+	SET_NETDEV_DEVTYPE(net, &gadget_type);
+
+	status = register_netdev(net);
+	if (status < 0) {
+		dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
+		free_netdev(net);
+		dev = ERR_PTR(status);
+		g_usbnet_dev = NULL;
+		g_dev = NULL;
+	} else {
+		INFO(dev, "MAC %pM\n", net->dev_addr);
+		INFO(dev, "HOST MAC %pM\n", dev->host_mac);
+
+		/* two kinds of host-initiated state changes:
+		 *  - iff DATA transfer is active, carrier is "on"
+		 *  - tx queueing enabled if open *and* carrier is "on"
+		 */
+		netif_carrier_off(net);
+		g_usbnet_dev = net;
+		g_dev = dev;
+	}
+
+#ifdef CONFIG_DDR_DEVFREQ
+	dev->ddr_qos_min.name = net->name;
+	pm_qos_add_request(&dev->ddr_qos_min,
+		PM_QOS_DDR_DEVFREQ_MIN, PM_QOS_DEFAULT_VALUE);
+#endif
+	if (!dev->usbnet_rx_thread) {
+		usbnet_thrd_should_stop = false;
+		if (nr_cpu_ids == 4)
+			dev->usbnet_rx_thread = kthread_create_on_cpu(usbnet_rx_fill_thread,
+				dev, 2, "usbnet_rx_thread");
+		else
+			dev->usbnet_rx_thread = kthread_create_on_cpu(usbnet_rx_fill_thread,
+				dev, 0, "usbnet_rx_thread");
+		if (!dev->usbnet_rx_thread)
+			pr_err("!!!create usbnet_rx_thread failed\n");
+		else
+			wake_up_process(dev->usbnet_rx_thread);
+	}
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	if (sysfs_create_group(&dev->net->dev.kobj, &u_ether_attr_group))
+		pr_err("%s:%d: fail to register sysfs attr group\n", __func__,
+							__LINE__);
+#endif
+
+	return dev;
+}
+
+#ifdef CONFIG_USB_MV_HSIC_UDC
+struct eth_dev *gether_setup_name_hsic(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+		const char *netname)
+{
+	struct eth_dev		*dev;
+	struct net_device	*net;
+	int			status;
+
+	net = alloc_etherdev(sizeof *dev);
+	if (!net)
+		return ERR_PTR(-ENOMEM);
+#if 0
+	net->element_used = 0;
+	for (i = 0; i < DEBUG_ELEMENT_CNT; i++) {
+		net->caller_elments[i].func_address = 0;
+		net->caller_elments[i].cnt = 0;
+	}
+#endif
+	dev = netdev_priv(net);
+	spin_lock_init(&dev->lock);
+	spin_lock_init(&dev->req_lock);
+	INIT_WORK(&dev->work, eth_work);
+#ifdef USB_RX_USE_WORK
+	INIT_DELAYED_WORK(&dev->rx_work, process_hsic_rx_work);
+#else
+	tasklet_init(&dev->rx_tl, process_rx_tl,(unsigned long)dev);
+#endif
+	INIT_LIST_HEAD(&dev->tx_reqs);
+	INIT_LIST_HEAD(&dev->rx_reqs);
+
+	/* by default we always have a random MAC address */
+	net->addr_assign_type = NET_ADDR_RANDOM;
+
+#ifdef CONFIG_UETHER_SKB_LLIST
+	skb_llist_init(&dev->rx_frames);
+#else
+	skb_queue_head_init(&dev->rx_frames);
+#endif
+	/* network device setup */
+	dev->net = net;
+	snprintf(net->name, sizeof(net->name), "%s%%d", netname);
+
+	if (get_ether_addr(dev_addr, net->dev_addr)) {
+		net->addr_assign_type = NET_ADDR_RANDOM;
+		dev_warn(&g->dev,
+			"using random %s ethernet address\n", "self");
+	} else {
+		net->addr_assign_type = NET_ADDR_SET;
+	}
+
+	if (get_host_ether_addr(host_ethaddr_hsic, dev->host_mac))
+		dev_warn(&g->dev, "using random %s ethernet address\n", "host");
+	else
+		dev_warn(&g->dev, "using previous %s ethernet address\n", "host");
+
+	if (ethaddr)
+		memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+
+	net->netdev_ops = &eth_netdev_ops;
+
+	SET_ETHTOOL_OPS(net, &ops);
+
+	dev->gadget = g;
+	SET_NETDEV_DEV(net, &g->dev);
+	SET_NETDEV_DEVTYPE(net, &gadget_type);
+
+	status = register_netdev(net);
+	if (status < 0) {
+		dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
+		free_netdev(net);
+		dev = ERR_PTR(status);
+	} else {
+		INFO(dev, "MAC %pM\n", net->dev_addr);
+		INFO(dev, "HOST MAC %pM\n", dev->host_mac);
+
+		/* two kinds of host-initiated state changes:
+		 *  - iff DATA transfer is active, carrier is "on"
+		 *  - tx queueing enabled if open *and* carrier is "on"
+		 */
+		netif_carrier_off(net);
+	}
+
+#ifdef CONFIG_DDR_DEVFREQ
+	dev->ddr_qos_min.name = net->name;
+	pm_qos_add_request(&dev->ddr_qos_min,
+		PM_QOS_DDR_DEVFREQ_MIN, PM_QOS_DEFAULT_VALUE);
+#endif
+	return dev;
+}
+
+#endif
+
+#if defined(CONFIG_CPU_ASR18XX) || defined(CONFIG_CPU_ASR1901)
+static bool is_netifd_online(void)
+{
+	struct task_struct *g, *p;
+
+	do_each_thread(g, p) {
+		if (!strcmp("netifd", p->comm))
+			return true;
+	} while_each_thread(g, p);
+
+	return false;
+}
+
+/* wait until the network is configured by netifd */
+void wait_usbnet_br_up_and_brigded(void)
+{
+	struct net_device *dev;
+	int timeout = 300;
+
+	might_sleep();
+	/* just return when netfid is not lauched up */
+	if (unlikely(!is_netifd_online())) {
+		return;
+	} else {
+		/* first wait for usbnet to be up */
+		if (g_usbnet_dev == NULL) {
+			pr_info("!!!!!!!!! usbnet regsiter failed\n");
+			WARN_ON(1);
+			return;
+		} else {
+			/* there may be issue for netifd if system is not wakeup */
+			timeout = 200;
+			while (pm_suspend_target_state != PM_SUSPEND_ON) {
+				msleep(10);
+				timeout--;
+				if (timeout == 0) {
+					pr_info("!!!!! wait system resume timeout\n");
+					WARN_ON(1);
+					return;
+				}
+			}
+			timeout = 300;
+			while (!((g_usbnet_dev->flags & IFF_UP) &&
+				(g_usbnet_dev->priv_flags & IFF_BRIDGE_PORT))) {
+				timeout--;
+				msleep(30);
+				if (timeout == 0) {
+					pr_info("!!!!! wait netifd timeout\n");
+					WARN_ON(1);
+					return;
+				}
+			}
+		}
+
+		/* wait for br-lan to be up */
+		timeout = 150;
+		dev = dev_get_by_name(&init_net, "br-lan");
+		/* network regsiter failed we just return true to move on */
+		if (dev == NULL) {
+			pr_info("!!!!!!!!! br-lan get failed\n");
+			WARN_ON(1);
+			return;
+		} else {
+			while (!(dev->flags & IFF_UP)) {
+				timeout--;
+				msleep(30);
+				if (timeout == 0) {
+					pr_info("!!!!! wait netifd timeout\n");
+					WARN_ON(1);
+					dev_put(dev);
+					return;
+				}
+			}
+			dev_put(dev);
+			return;
+		}
+	}
+}
+
+void wait_usbnet_if_down(void)
+{
+	int timeout = 60;
+
+	might_sleep();
+	/* just return when netfid is not lauched up */
+	if (unlikely(!is_netifd_online())) {
+		return;
+	} else {
+		while (!get_usbnet_ifdown_flag()) {
+			timeout--;
+			msleep(30);
+			if (timeout == 0) {
+				pr_info("!!!!! wait usbnet if_down timeout\n");
+				WARN_ON(1);
+				return;
+			}
+		}
+	}
+}
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+static void txrx_monitor_work(struct work_struct *work)
+{
+	static unsigned long old_rx_bytes = 0;
+	static unsigned long old_tx_bytes = 0;
+	unsigned long rx_bytes, tx_bytes;
+
+	struct eth_dev	*dev = container_of(work, struct eth_dev,
+						txrx_monitor_work.work);
+
+	if (!dev || !dev->net) {
+		pr_err("%s: dev or net is not ready\n", __func__);
+		return;
+	}
+
+	if (likely(dev->net->stats.rx_bytes > old_rx_bytes))
+		rx_bytes = dev->net->stats.rx_bytes - old_rx_bytes;
+	else
+		rx_bytes = ULONG_MAX - old_rx_bytes + dev->net->stats.rx_bytes + 1;
+
+	if (likely(dev->net->stats.tx_bytes > old_tx_bytes))
+		tx_bytes = dev->net->stats.tx_bytes - old_tx_bytes;
+	else
+		tx_bytes = ULONG_MAX - old_tx_bytes + dev->net->stats.tx_bytes + 1;
+
+	old_rx_bytes = dev->net->stats.rx_bytes;
+	old_tx_bytes = dev->net->stats.tx_bytes;
+
+	/* first check the tx side and boost ddr_freq to 398MHZ */
+	if (tx_bytes >= DDR_TX_BOOST_BYTES) {
+			pm_qos_update_request_timeout(&dev->ddr_qos_min,
+					DDR_BOOST_FREQ,
+					(2 * USEC_PER_SEC / INTERVALS_PER_SEC));
+		goto out;
+	}
+
+	if (rx_bytes >= DDR_RX_BOOST_BYTES) {
+			pm_qos_update_request_timeout(&dev->ddr_qos_min,
+					DDR_BOOST_FREQ,
+					(2 * USEC_PER_SEC / INTERVALS_PER_SEC));
+		goto out;
+	}
+
+	if (tx_bytes < DDR_TX_BOOST_BYTES && rx_bytes < DDR_RX_BOOST_BYTES)
+		pm_qos_update_request(&dev->ddr_qos_min, PM_QOS_DEFAULT_VALUE);
+out:
+	schedule_delayed_work(&dev->txrx_monitor_work, HZ / INTERVALS_PER_SEC);
+}
+#endif
+
+/*
+ * usbnet rx fill worker
+ */
+static int usbnet_rx_fill_thread(void *arg)
+{
+	struct eth_dev	*dev = (struct eth_dev *)arg;
+	struct sched_param param = { .sched_priority = 1 };
+
+	sched_setscheduler(current, SCHED_FIFO, &param);
+	pr_info("starting usbnet_rx_fill_thread: cpu=%d pid=%d\n", smp_processor_id(), task_pid_nr(current));
+
+	while (!kthread_should_stop()) {
+		if (usbnet_thrd_should_stop) {
+			pr_err("usbnet_rx_fill_thread stopped\n");
+			break;
+		}
+
+		if (dev && dev->port_usb && (!netif_queue_stopped(dev->net)))
+			usbnet_rx_fill();
+		usleep_range(500, 500);
+	}
+
+	return 0;
+}
+
+/**
+ * gether_cleanup - remove Ethernet-over-USB device
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gether_setup().
+ */
+void gether_cleanup(struct eth_dev *dev)
+{
+	if (!dev)
+		return;
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+	sysfs_remove_group(&dev->net->dev.kobj, &u_ether_attr_group);
+#endif
+
+	usbnet_thrd_should_stop = true;
+	if (dev->usbnet_rx_thread) {
+		kthread_stop(dev->usbnet_rx_thread);
+		pr_info("stop usbnet kthread\n");
+		dev->usbnet_rx_thread = NULL;
+	}
+
+#ifndef USB_RX_USE_WORK
+	tasklet_kill(&dev->rx_tl);
+#endif
+	unregister_netdev(dev->net);
+	flush_work(&dev->work);
+#ifdef USB_RX_USE_WORK
+	flush_delayed_work(&dev->rx_work);
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+	if (dev->dwork_inited)
+		flush_delayed_work(&dev->txrx_monitor_work);
+	pm_qos_remove_request(&dev->ddr_qos_min);
+#endif
+
+	free_netdev(dev->net);
+}
+
+/**
+ * gether_connect - notify network layer that USB link is active
+ * @link: the USB link, set up with endpoints, descriptors matching
+ *	current device speed, and any framing wrapper(s) set up.
+ * Context: irqs blocked
+ *
+ * This is called to activate endpoints and let the network layer know
+ * the connection is active ("carrier detect").  It may cause the I/O
+ * queues to open and start letting network packets flow, but will in
+ * any case activate the endpoints so that they respond properly to the
+ * USB host.
+ *
+ * Verify net_device pointer returned using IS_ERR().  If it doesn't
+ * indicate some error code (negative errno), ep->driver_data values
+ * have been overwritten.
+ */
+struct net_device *gether_connect(struct gether *link)
+{
+	struct eth_dev		*dev = link->ioport;
+	int			result = 0;
+
+	if (!dev)
+		return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_ASR_TOE
+	pr_info("%s: rx_aggr.nr_rx_req: %d, ueth_type: %d\n", __func__, u_ether_rx_aggr.nr_rx_req, link->ueth_type);
+#endif
+
+	link->in_ep->driver_data = dev;
+	result = usb_ep_enable(link->in_ep);
+	if (result != 0) {
+		ERROR(dev, "enable %s --> %d\n",
+			link->in_ep->name, result);
+		goto fail0;
+	}
+
+	link->out_ep->driver_data = dev;
+	result = usb_ep_enable(link->out_ep);
+	if (result != 0) {
+		ERROR(dev, "enable %s --> %d\n",
+			link->out_ep->name, result);
+		goto fail1;
+	}
+	if (result == 0)
+		result = alloc_requests(dev, link, qlen(dev->gadget, true),
+					qlen(dev->gadget, false));
+
+	if (result == 0) {
+		dev->zlp = link->is_zlp_ok;
+		DBG(dev, "qlen_rx %d qlen_tx %d\n", qlen(dev->gadget, true),
+		    qlen(dev->gadget, false));
+
+#ifdef CONFIG_DDR_DEVFREQ
+		if (false == dev->dwork_inited) {
+			INIT_DELAYED_WORK(&dev->txrx_monitor_work, txrx_monitor_work);
+			dev->dwork_inited = true;
+		}
+#endif
+		dev->header_len = link->header_len;
+		dev->unwrap = link->unwrap;
+		dev->wrap = link->wrap;
+		dev->unwrap_fixup = link->unwrap_fixup;
+		dev->ul_max_pkts_per_xfer = link->ul_max_pkts_per_xfer;
+		spin_lock(&dev->lock);
+		atomic_set(&dev->no_tx_req_used, 0);
+		atomic_set(&dev->nr_rx_req_free, qlen(dev->gadget, true));
+		dev->port_usb = link;
+		if (netif_running(dev->net)) {
+			if (link->open)
+				link->open(link);
+		} else {
+			if (link->close)
+				link->close(link);
+		}
+		spin_unlock(&dev->lock);
+
+		netif_carrier_on(dev->net);
+		if (netif_running(dev->net))
+			eth_start(dev, GFP_ATOMIC);
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+		/*allocate tx multiple packets histogram pointer:*/
+		BUG_ON(dev->tx_mult_histogram);
+		histogram_size = 16;
+		dev->tx_mult_histogram = kzalloc(sizeof(int) * histogram_size, GFP_ATOMIC);
+		if (!dev->tx_mult_histogram) {
+			histogram_size = 0;
+			pr_err("u_ether: failed to alloc tx_mult_histogram\n");
+		}
+#endif
+		dev->skb_alloc_success = 0;
+		dev->skb_alloc_fail = 0;
+		dev->usbnet_rx_num = 0;
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+		/* the delay set as 10ms */
+		schedule_delayed_work(&dev->txrx_monitor_work, HZ / 100);
+#endif
+
+	/* on error, disable any endpoints  */
+	} else {
+		(void) usb_ep_disable(link->out_ep);
+fail1:
+		(void) usb_ep_disable(link->in_ep);
+	}
+fail0:
+	/* caller is responsible for cleanup on error */
+	if (result < 0) {
+		pr_err("%s failed %d\n", __func__, result);
+		return ERR_PTR(result);
+	}
+
+#ifdef CONFIG_ASR_TOE
+	if (result == 0) {
+		pr_err("uther con, eth_type: %d\n", link->ueth_type);
+		uether_send_link_event(dev, link->ueth_type);
+	}
+#endif
+
+	return dev->net;
+}
+
+/**
+ * gether_disconnect - notify network layer that USB link is inactive
+ * @link: the USB link, on which gether_connect() was called
+ * Context: irqs blocked
+ *
+ * This is called to deactivate endpoints and let the network layer know
+ * the connection went inactive ("no carrier").
+ *
+ * On return, the state is as if gether_connect() had never been called.
+ * The endpoints are inactive, and accordingly without active USB I/O.
+ * Pointers to endpoint descriptors and endpoint private data are nulled.
+ */
+void gether_disconnect(struct gether *link)
+{
+	struct eth_dev		*dev = link->ioport;
+	struct usb_request	*req;
+	struct sk_buff		*skb;
+
+	WARN_ON(!dev);
+	if (!dev)
+		return;
+
+	DBG(dev, "%s\n", __func__);
+	netif_stop_queue(dev->net);
+	netif_carrier_off(dev->net);
+
+#ifdef CONFIG_ASR_TOE
+	/* send UETHER_UNKNOWN to toe to drop packets on usb disconnection */
+	pr_err("uther discon, eth_type: %d\n", link->ueth_type);
+	uether_send_link_event(dev, UETHER_UNKNOWN);
+	pr_err("uther notifier finish\n");
+#endif
+
+#ifdef CONFIG_DDR_DEVFREQ
+	cancel_delayed_work_sync(&dev->txrx_monitor_work);
+#endif
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+#ifdef CONFIG_USBNET_HISTOGRAM
+	if (dev->tx_mult_histogram) {
+		kfree(dev->tx_mult_histogram);
+		dev->tx_mult_histogram = NULL;
+	}
+#endif
+#endif
+
+#ifdef CONFIG_ASR_TOE
+	usb_aggr_wait_free(dev, &u_ether_rx_aggr);
+#endif
+
+	/* disable endpoints, forcing (synchronous) completion
+	 * of all pending i/o.  then free the request objects
+	 * and forget about the endpoints.
+	 */
+	usb_ep_disable(link->in_ep);
+	spin_lock(&dev->req_lock);
+	while (!list_empty(&dev->tx_reqs)) {
+		req = container_of(dev->tx_reqs.next,
+					struct usb_request, list);
+		list_del(&req->list);
+		spin_unlock(&dev->req_lock);
+		usb_ep_free_request_tx_mult(link->in_ep, req);
+		spin_lock(&dev->req_lock);
+	}
+	spin_unlock(&dev->req_lock);
+	link->in_ep->driver_data = NULL;
+	link->in_ep->desc = NULL;
+
+	usb_ep_disable(link->out_ep);
+	spin_lock(&dev->req_lock);
+	while (!list_empty(&dev->rx_reqs)) {
+		req = container_of(dev->rx_reqs.next,
+					struct usb_request, list);
+		list_del(&req->list);
+		spin_unlock(&dev->req_lock);
+		usb_ep_free_request(link->out_ep, req);
+		spin_lock(&dev->req_lock);
+	}
+	spin_unlock(&dev->req_lock);
+
+#ifdef CONFIG_UETHER_SKB_LLIST
+	while ((skb = skb_llist_dequeue(&dev->rx_frames)))
+		dev_kfree_skb_any(skb);
+#else
+	spin_lock(&dev->rx_frames.lock);
+	while ((skb = __skb_dequeue(&dev->rx_frames)))
+		dev_kfree_skb_any(skb);
+	spin_unlock(&dev->rx_frames.lock);
+#endif
+
+	link->out_ep->driver_data = NULL;
+	link->out_ep->desc = NULL;
+
+	/* finish forgetting about this USB link episode */
+	dev->header_len = 0;
+	dev->unwrap = NULL;
+	dev->wrap = NULL;
+
+	spin_lock(&dev->lock);
+	dev->port_usb = NULL;
+	spin_unlock(&dev->lock);
+}
+
+u8 *eth_get_host_mac(struct net_device *net)
+{
+	struct eth_dev *eth = netdev_priv(net);
+	return eth->host_mac;
+}
+EXPORT_SYMBOL(eth_get_host_mac);
+
+static void usbnet_rx_fill(void)
+{
+	if (g_dev && g_dev->eth_started && netif_running(g_dev->net) &&
+		atomic_read(&g_dev->nr_rx_req_free) >= MAX_RX_REQ_NUMBER) {
+		rx_fill(g_dev, GFP_ATOMIC);
+	}
+}
+
+MODULE_DESCRIPTION("ethernet over USB driver");
+MODULE_LICENSE("GPL v2");
diff --git a/marvell/linux/drivers/usb/gadget/function/u_fs.h b/marvell/linux/drivers/usb/gadget/function/u_fs.h
new file mode 100644
index 0000000..f9b0cf6
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_fs.h
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_fs.h
+ *
+ * Utility definitions for the FunctionFS
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_FFS_H
+#define U_FFS_H
+
+#include <linux/usb/composite.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/refcount.h>
+
+#ifdef VERBOSE_DEBUG
+#ifndef pr_vdebug
+#  define pr_vdebug pr_debug
+#endif /* pr_vdebug */
+#  define ffs_dump_mem(prefix, ptr, len) \
+	print_hex_dump_bytes(pr_fmt(prefix ": "), DUMP_PREFIX_NONE, ptr, len)
+#else
+#ifndef pr_vdebug
+#  define pr_vdebug(...)                 do { } while (0)
+#endif /* pr_vdebug */
+#  define ffs_dump_mem(prefix, ptr, len) do { } while (0)
+#endif /* VERBOSE_DEBUG */
+
+#define ENTER()    pr_vdebug("%s()\n", __func__)
+
+struct f_fs_opts;
+
+struct ffs_dev {
+	struct ffs_data *ffs_data;
+	struct f_fs_opts *opts;
+	struct list_head entry;
+
+	char name[41];
+
+	bool mounted;
+	bool desc_ready;
+	bool single;
+
+	int (*ffs_ready_callback)(struct ffs_data *ffs);
+	void (*ffs_closed_callback)(struct ffs_data *ffs);
+	void *(*ffs_acquire_dev_callback)(struct ffs_dev *dev);
+	void (*ffs_release_dev_callback)(struct ffs_dev *dev);
+};
+
+extern struct mutex ffs_lock;
+
+static inline void ffs_dev_lock(void)
+{
+	mutex_lock(&ffs_lock);
+}
+
+static inline void ffs_dev_unlock(void)
+{
+	mutex_unlock(&ffs_lock);
+}
+
+int ffs_name_dev(struct ffs_dev *dev, const char *name);
+int ffs_single_dev(struct ffs_dev *dev);
+
+struct ffs_epfile;
+struct ffs_function;
+
+enum ffs_state {
+	/*
+	 * Waiting for descriptors and strings.
+	 *
+	 * In this state no open(2), read(2) or write(2) on epfiles
+	 * may succeed (which should not be the problem as there
+	 * should be no such files opened in the first place).
+	 */
+	FFS_READ_DESCRIPTORS,
+	FFS_READ_STRINGS,
+
+	/*
+	 * We've got descriptors and strings.  We are or have called
+	 * functionfs_ready_callback().  functionfs_bind() may have
+	 * been called but we don't know.
+	 *
+	 * This is the only state in which operations on epfiles may
+	 * succeed.
+	 */
+	FFS_ACTIVE,
+
+	/*
+	 * Function is visible to host, but it's not functional. All
+	 * setup requests are stalled and transfers on another endpoints
+	 * are refused. All epfiles, except ep0, are deleted so there
+	 * is no way to perform any operations on them.
+	 *
+	 * This state is set after closing all functionfs files, when
+	 * mount parameter "no_disconnect=1" has been set. Function will
+	 * remain in deactivated state until filesystem is umounted or
+	 * ep0 is opened again. In the second case functionfs state will
+	 * be reset, and it will be ready for descriptors and strings
+	 * writing.
+	 *
+	 * This is useful only when functionfs is composed to gadget
+	 * with another function which can perform some critical
+	 * operations, and it's strongly desired to have this operations
+	 * completed, even after functionfs files closure.
+	 */
+	FFS_DEACTIVATED,
+
+	/*
+	 * All endpoints have been closed.  This state is also set if
+	 * we encounter an unrecoverable error.  The only
+	 * unrecoverable error is situation when after reading strings
+	 * from user space we fail to initialise epfiles or
+	 * functionfs_ready_callback() returns with error (<0).
+	 *
+	 * In this state no open(2), read(2) or write(2) (both on ep0
+	 * as well as epfile) may succeed (at this point epfiles are
+	 * unlinked and all closed so this is not a problem; ep0 is
+	 * also closed but ep0 file exists and so open(2) on ep0 must
+	 * fail).
+	 */
+	FFS_CLOSING
+};
+
+enum ffs_setup_state {
+	/* There is no setup request pending. */
+	FFS_NO_SETUP,
+	/*
+	 * User has read events and there was a setup request event
+	 * there.  The next read/write on ep0 will handle the
+	 * request.
+	 */
+	FFS_SETUP_PENDING,
+	/*
+	 * There was event pending but before user space handled it
+	 * some other event was introduced which canceled existing
+	 * setup.  If this state is set read/write on ep0 return
+	 * -EIDRM.  This state is only set when adding event.
+	 */
+	FFS_SETUP_CANCELLED
+};
+
+struct ffs_data {
+	struct usb_gadget		*gadget;
+
+	/*
+	 * Protect access read/write operations, only one read/write
+	 * at a time.  As a consequence protects ep0req and company.
+	 * While setup request is being processed (queued) this is
+	 * held.
+	 */
+	struct mutex			mutex;
+
+	/*
+	 * Protect access to endpoint related structures (basically
+	 * usb_ep_queue(), usb_ep_dequeue(), etc. calls) except for
+	 * endpoint zero.
+	 */
+	spinlock_t			eps_lock;
+
+	/*
+	 * XXX REVISIT do we need our own request? Since we are not
+	 * handling setup requests immediately user space may be so
+	 * slow that another setup will be sent to the gadget but this
+	 * time not to us but another function and then there could be
+	 * a race.  Is that the case? Or maybe we can use cdev->req
+	 * after all, maybe we just need some spinlock for that?
+	 */
+	struct usb_request		*ep0req;		/* P: mutex */
+	struct completion		ep0req_completion;	/* P: mutex */
+
+	/* reference counter */
+	refcount_t			ref;
+	/* how many files are opened (EP0 and others) */
+	atomic_t			opened;
+
+	/* EP0 state */
+	enum ffs_state			state;
+
+	/*
+	 * Possible transitions:
+	 * + FFS_NO_SETUP        -> FFS_SETUP_PENDING  -- P: ev.waitq.lock
+	 *               happens only in ep0 read which is P: mutex
+	 * + FFS_SETUP_PENDING   -> FFS_NO_SETUP       -- P: ev.waitq.lock
+	 *               happens only in ep0 i/o  which is P: mutex
+	 * + FFS_SETUP_PENDING   -> FFS_SETUP_CANCELLED -- P: ev.waitq.lock
+	 * + FFS_SETUP_CANCELLED -> FFS_NO_SETUP        -- cmpxchg
+	 *
+	 * This field should never be accessed directly and instead
+	 * ffs_setup_state_clear_cancelled function should be used.
+	 */
+	enum ffs_setup_state		setup_state;
+
+	/* Events & such. */
+	struct {
+		u8				types[4];
+		unsigned short			count;
+		/* XXX REVISIT need to update it in some places, or do we? */
+		unsigned short			can_stall;
+		struct usb_ctrlrequest		setup;
+
+		wait_queue_head_t		waitq;
+	} ev; /* the whole structure, P: ev.waitq.lock */
+
+	/* Flags */
+	unsigned long			flags;
+#define FFS_FL_CALL_CLOSED_CALLBACK 0
+#define FFS_FL_BOUND                1
+
+	/* For waking up blocked threads when function is enabled. */
+	wait_queue_head_t		wait;
+
+	/* Active function */
+	struct ffs_function		*func;
+
+	/*
+	 * Device name, write once when file system is mounted.
+	 * Intended for user to read if she wants.
+	 */
+	const char			*dev_name;
+	/* Private data for our user (ie. gadget).  Managed by user. */
+	void				*private_data;
+
+	/* filled by __ffs_data_got_descs() */
+	/*
+	 * raw_descs is what you kfree, real_descs points inside of raw_descs,
+	 * where full speed, high speed and super speed descriptors start.
+	 * real_descs_length is the length of all those descriptors.
+	 */
+	const void			*raw_descs_data;
+	const void			*raw_descs;
+	unsigned			raw_descs_length;
+	unsigned			fs_descs_count;
+	unsigned			hs_descs_count;
+	unsigned			ss_descs_count;
+	unsigned			ms_os_descs_count;
+	unsigned			ms_os_descs_ext_prop_count;
+	unsigned			ms_os_descs_ext_prop_name_len;
+	unsigned			ms_os_descs_ext_prop_data_len;
+	void				*ms_os_descs_ext_prop_avail;
+	void				*ms_os_descs_ext_prop_name_avail;
+	void				*ms_os_descs_ext_prop_data_avail;
+
+	unsigned			user_flags;
+
+#define FFS_MAX_EPS_COUNT 31
+	u8				eps_addrmap[FFS_MAX_EPS_COUNT];
+
+	unsigned short			strings_count;
+	unsigned short			interfaces_count;
+	unsigned short			eps_count;
+	unsigned short			_pad1;
+
+	/* filled by __ffs_data_got_strings() */
+	/* ids in stringtabs are set in functionfs_bind() */
+	const void			*raw_strings;
+	struct usb_gadget_strings	**stringtabs;
+
+	/*
+	 * File system's super block, write once when file system is
+	 * mounted.
+	 */
+	struct super_block		*sb;
+
+	/* File permissions, written once when fs is mounted */
+	struct ffs_file_perms {
+		umode_t				mode;
+		kuid_t				uid;
+		kgid_t				gid;
+	}				file_perms;
+
+	struct eventfd_ctx *ffs_eventfd;
+	struct workqueue_struct *io_completion_wq;
+	bool no_disconnect;
+	struct work_struct reset_work;
+
+	/*
+	 * The endpoint files, filled by ffs_epfiles_create(),
+	 * destroyed by ffs_epfiles_destroy().
+	 */
+	struct ffs_epfile		*epfiles;
+};
+
+
+struct f_fs_opts {
+	struct usb_function_instance	func_inst;
+	struct ffs_dev			*dev;
+	unsigned			refcnt;
+	bool				no_configfs;
+};
+
+static inline struct f_fs_opts *to_f_fs_opts(struct usb_function_instance *fi)
+{
+	return container_of(fi, struct f_fs_opts, func_inst);
+}
+
+#endif /* U_FFS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_gether.h b/marvell/linux/drivers/usb/gadget/function/u_gether.h
new file mode 100644
index 0000000..ce4f076
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_gether.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_gether.h
+ *
+ * Utility definitions for the subset function
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_GETHER_H
+#define U_GETHER_H
+
+#include <linux/usb/composite.h>
+
+struct f_gether_opts {
+	struct usb_function_instance	func_inst;
+	struct net_device		*net;
+	bool				bound;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_GETHER_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_hid.h b/marvell/linux/drivers/usb/gadget/function/u_hid.h
new file mode 100644
index 0000000..90d8b1c
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_hid.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_hid.h
+ *
+ * Utility definitions for the hid function
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_HID_H
+#define U_HID_H
+
+#include <linux/usb/composite.h>
+
+struct f_hid_opts {
+	struct usb_function_instance	func_inst;
+	int				minor;
+	unsigned char			subclass;
+	unsigned char			protocol;
+	unsigned char			no_out_endpoint;
+	unsigned short			report_length;
+	unsigned short			report_desc_length;
+	unsigned char			*report_desc;
+	bool				report_desc_alloc;
+
+	/*
+	 * Protect the data form concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	 struct mutex			lock;
+	 int				refcnt;
+};
+
+int ghid_setup(struct usb_gadget *g, int count);
+void ghid_cleanup(void);
+
+#endif /* U_HID_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_midi.h b/marvell/linux/drivers/usb/gadget/function/u_midi.h
new file mode 100644
index 0000000..29bf006
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_midi.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_midi.h
+ *
+ * Utility definitions for the midi function
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_MIDI_H
+#define U_MIDI_H
+
+#include <linux/usb/composite.h>
+
+struct f_midi_opts {
+	struct usb_function_instance	func_inst;
+	int				index;
+	char				*id;
+	bool				id_allocated;
+	unsigned int			in_ports;
+	unsigned int			out_ports;
+	unsigned int			buflen;
+	unsigned int			qlen;
+
+	/*
+	 * Protect the data form concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	 struct mutex			lock;
+	 int				refcnt;
+};
+
+#endif /* U_MIDI_H */
+
diff --git a/marvell/linux/drivers/usb/gadget/function/u_ncm.h b/marvell/linux/drivers/usb/gadget/function/u_ncm.h
new file mode 100644
index 0000000..70da320
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_ncm.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_ncm.h
+ *
+ * Utility definitions for the ncm function
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_NCM_H
+#define U_NCM_H
+
+#include <linux/usb/composite.h>
+
+struct f_ncm_opts {
+	struct usb_function_instance	func_inst;
+	struct net_device		*net;
+	bool				bound;
+
+	struct config_group		*ncm_interf_group;
+	struct usb_os_desc		ncm_os_desc;
+	char				ncm_ext_compat_id[16];
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_NCM_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_phonet.h b/marvell/linux/drivers/usb/gadget/function/u_phonet.h
new file mode 100644
index 0000000..12fb613
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_phonet.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_phonet.h - interface to Phonet
+ *
+ * Copyright (C) 2007-2008 by Nokia Corporation
+ */
+
+#ifndef __U_PHONET_H
+#define __U_PHONET_H
+
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+struct f_phonet_opts {
+	struct usb_function_instance func_inst;
+	bool bound;
+	struct net_device *net;
+};
+
+struct net_device *gphonet_setup_default(void);
+void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g);
+int gphonet_register_netdev(struct net_device *net);
+int phonet_bind_config(struct usb_configuration *c, struct net_device *dev);
+void gphonet_cleanup(struct net_device *dev);
+
+#endif /* __U_PHONET_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_printer.h b/marvell/linux/drivers/usb/gadget/function/u_printer.h
new file mode 100644
index 0000000..7879776
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_printer.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_printer.h
+ *
+ * Utility definitions for the printer function
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_PRINTER_H
+#define U_PRINTER_H
+
+#include <linux/usb/composite.h>
+
+struct f_printer_opts {
+	struct usb_function_instance	func_inst;
+	int				minor;
+	char				*pnp_string;
+	bool				pnp_string_allocated;
+	unsigned			q_len;
+
+	/*
+	 * Protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_PRINTER_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_rndis.h b/marvell/linux/drivers/usb/gadget/function/u_rndis.h
new file mode 100644
index 0000000..1e148b7
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_rndis.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_rndis.h
+ *
+ * Utility definitions for the subset function
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_RNDIS_H
+#define U_RNDIS_H
+
+#include <linux/usb/composite.h>
+
+struct f_rndis_opts {
+	struct usb_function_instance	func_inst;
+	u32				vendor_id;
+	const char			*manufacturer;
+	struct net_device		*net;
+	bool				bound;
+	bool				borrowed_net;
+
+	struct config_group		*rndis_interf_group;
+	struct usb_os_desc		rndis_os_desc;
+	char				rndis_ext_compat_id[16];
+
+	u8				class;
+	u8				subclass;
+	u8				protocol;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This is to protect the data from concurrent access by read/write
+	 * and create symlink/remove symlink.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net);
+
+#endif /* U_RNDIS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_serial.c b/marvell/linux/drivers/usb/gadget/function/u_serial.c
new file mode 100644
index 0000000..741d0c0
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_serial.c
@@ -0,0 +1,1551 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_serial.c - utilities for USB gadget "serial port"/TTY support
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This code also borrows from usbserial.c, which is
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com)
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/kthread.h>
+#include <linux/workqueue.h>
+#include <linux/kfifo.h>
+
+#include "u_serial.h"
+
+
+/*
+ * This component encapsulates the TTY layer glue needed to provide basic
+ * "serial port" functionality through the USB gadget stack.  Each such
+ * port is exposed through a /dev/ttyGS* node.
+ *
+ * After this module has been loaded, the individual TTY port can be requested
+ * (gserial_alloc_line()) and it will stay available until they are removed
+ * (gserial_free_line()). Each one may be connected to a USB function
+ * (gserial_connect), or disconnected (with gserial_disconnect) when the USB
+ * host issues a config change event. Data can only flow when the port is
+ * connected to the host.
+ *
+ * A given TTY port can be made available in multiple configurations.
+ * For example, each one might expose a ttyGS0 node which provides a
+ * login application.  In one case that might use CDC ACM interface 0,
+ * while another configuration might use interface 3 for that.  The
+ * work to handle that (including descriptor management) is not part
+ * of this component.
+ *
+ * Configurations may expose more than one TTY port.  For example, if
+ * ttyGS0 provides login service, then ttyGS1 might provide dialer access
+ * for a telephone or fax link.  And ttyGS2 might be something that just
+ * needs a simple byte stream interface for some messaging protocol that
+ * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
+ *
+ *
+ * gserial is the lifecycle interface, used by USB functions
+ * gs_port is the I/O nexus, used by the tty driver
+ * tty_struct links to the tty/filesystem framework
+ *
+ * gserial <---> gs_port ... links will be null when the USB link is
+ * inactive; managed by gserial_{connect,disconnect}().  each gserial
+ * instance can wrap its own USB control protocol.
+ *	gserial->ioport == usb_ep->driver_data ... gs_port
+ *	gs_port->port_usb ... gserial
+ *
+ * gs_port <---> tty_struct ... links will be null when the TTY file
+ * isn't opened; managed by gs_open()/gs_close()
+ *	gserial->port_tty ... tty_struct
+ *	tty_struct->driver_data ... gserial
+ */
+
+/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the
+ * next layer of buffering.  For TX that's a circular buffer; for RX
+ * consider it a NOP.  A third layer is provided by the TTY code.
+ */
+#define QUEUE_SIZE		16
+#define WRITE_BUF_SIZE		8192		/* TX only */
+#define CONFIG_USERIAL_TX_RX_DBG	1
+
+/* console info */
+struct gscons_info {
+	struct gs_port		*port;
+	struct task_struct	*console_thread;
+	struct kfifo		con_buf;
+	/* protect the buf and busy flag */
+	spinlock_t		con_lock;
+	int			req_busy;
+	struct usb_request	*console_req;
+};
+
+/*
+ * The port structure holds info for each port, one for each minor number
+ * (and thus for each /dev/ node).
+ */
+struct gs_port {
+	struct tty_port		port;
+	spinlock_t		port_lock;	/* guard port_* access */
+
+	struct gserial		*port_usb;
+
+	bool			openclose;	/* open/close in progress */
+	u8			port_num;
+
+	struct list_head	read_pool;
+	int read_started;
+	int read_allocated;
+	struct list_head	read_queue;
+	unsigned		n_read;
+	struct delayed_work	push;
+
+	struct list_head	write_pool;
+	int write_started;
+	int write_allocated;
+	struct kfifo		port_write_buf;
+	wait_queue_head_t	drain_wait;	/* wait while writes drain */
+	bool                    write_busy;
+	wait_queue_head_t	close_wait;
+
+	/* REVISIT this state ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 8-N-1 etc */
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+	/*BIT0: control RX DEBUG 
+	* BIT1: control TX DEBUG
+	*/
+	unsigned long dbg_flag;
+#endif
+};
+
+static struct portmaster {
+	struct mutex	lock;			/* protect open/close */
+	struct gs_port	*port;
+} ports[MAX_U_SERIAL_PORTS];
+
+#define GS_CLOSE_TIMEOUT		15		/* seconds */
+
+
+
+#ifdef VERBOSE_DEBUG
+#ifndef pr_vdebug
+#define pr_vdebug(fmt, arg...) \
+	pr_debug(fmt, ##arg)
+#endif /* pr_vdebug */
+#else
+#ifndef pr_vdebug
+#define pr_vdebug(fmt, arg...) \
+	({ if (0) pr_debug(fmt, ##arg); })
+#endif /* pr_vdebug */
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+/* I/O glue between TTY (upper) and USB function (lower) driver layers */
+
+/*
+ * gs_alloc_req
+ *
+ * Allocate a usb_request and its buffer.  Returns a pointer to the
+ * usb_request or NULL if there is an error.
+ */
+struct usb_request *
+gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+	if (req != NULL) {
+		req->length = len;
+		req->buf = kmalloc(len, kmalloc_flags);
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			return NULL;
+		}
+	}
+
+	return req;
+}
+EXPORT_SYMBOL_GPL(gs_alloc_req);
+
+/*
+ * gs_free_req
+ *
+ * Free a usb_request and its buffer.
+ */
+void gs_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	kfree(req->buf);
+	usb_ep_free_request(ep, req);
+}
+EXPORT_SYMBOL_GPL(gs_free_req);
+
+/*
+ * gs_send_packet
+ *
+ * If there is data to send, a packet is built in the given
+ * buffer and the size is returned.  If there is no data to
+ * send, 0 is returned.
+ *
+ * Called with port_lock held.
+ */
+static unsigned
+gs_send_packet(struct gs_port *port, char *packet, unsigned size)
+{
+	unsigned len;
+
+	len = kfifo_len(&port->port_write_buf);
+	if (len < size)
+		size = len;
+	if (size != 0)
+		size = kfifo_out(&port->port_write_buf, packet, size);
+	return size;
+}
+
+/*
+ * gs_start_tx
+ *
+ * This function finds available write requests, calls
+ * gs_send_packet to fill these packets with data, and
+ * continues until either there are no more write requests
+ * available or no more data to send.  This function is
+ * run whenever data arrives or write requests are available.
+ *
+ * Context: caller owns port_lock; port_usb is non-null.
+ */
+static int gs_start_tx(struct gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+	struct list_head	*pool = &port->write_pool;
+	struct usb_ep		*in;
+	int			status = 0;
+	bool			do_tty_wake = false;
+
+	if (!port->port_usb)
+		return status;
+
+	in = port->port_usb->in;
+
+	while (!port->write_busy && !list_empty(pool)) {
+		struct usb_request	*req;
+		int			len;
+
+		if (port->write_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		len = gs_send_packet(port, req->buf, in->maxpacket);
+		if (len == 0) {
+			wake_up_interruptible(&port->drain_wait);
+			break;
+		}
+		do_tty_wake = true;
+
+		req->length = len;
+		list_del(&req->list);
+		req->zero = kfifo_is_empty(&port->port_write_buf);
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+		if (unlikely(port->dbg_flag & (0x1 << 1)))
+			pr_info("%d: tx len=%d, %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+				port->port_num, len, *((u8 *)req->buf),
+				*((u8 *)req->buf+1), *((u8 *)req->buf+2),
+				*((u8 *)req->buf+3), *((u8 *)req->buf+4),
+				*((u8 *)req->buf+5), *((u8 *)req->buf+6),
+				*((u8 *)req->buf+7), *((u8 *)req->buf+8),
+				*((u8 *)req->buf+9), *((u8 *)req->buf+10),
+				*((u8 *)req->buf+11), *((u8 *)req->buf+12),
+				*((u8 *)req->buf+13), *((u8 *)req->buf+14),
+				*((u8 *)req->buf+15));
+		else
+#endif
+		pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
+			  port->port_num, len, *((u8 *)req->buf),
+			  *((u8 *)req->buf+1), *((u8 *)req->buf+2));
+
+		/* Drop lock while we call out of driver; completions
+		 * could be issued while we do so.  Disconnection may
+		 * happen too; maybe immediately before we queue this!
+		 *
+		 * NOTE that we may keep sending data for a while after
+		 * the TTY closed (dev->ioport->port_tty is NULL).
+		 */
+		port->write_busy = true;
+		spin_unlock(&port->port_lock);
+		status = usb_ep_queue(in, req, GFP_ATOMIC);
+		spin_lock(&port->port_lock);
+		port->write_busy = false;
+
+		if (status) {
+			pr_err("%s: %s %s err %d\n",
+					__func__, "queue", in->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+
+		port->write_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+
+	if (do_tty_wake && port->port.tty)
+		tty_wakeup(port->port.tty);
+	return status;
+}
+
+/*
+ * Context: caller owns port_lock, and port_usb is set
+ */
+static unsigned gs_start_rx(struct gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+	struct list_head	*pool = &port->read_pool;
+	struct usb_ep		*out = port->port_usb->out;
+
+	while (!list_empty(pool)) {
+		struct usb_request	*req;
+		int			status;
+		struct tty_struct	*tty;
+
+		/* no more rx if closed */
+		tty = port->port.tty;
+		if (!tty)
+			break;
+
+		if (port->read_started >= QUEUE_SIZE)
+			break;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		list_del(&req->list);
+		req->length = out->maxpacket;
+
+		/* drop lock while we call out; the controller driver
+		 * may need to call us back (e.g. for disconnect)
+		 */
+		spin_unlock(&port->port_lock);
+		status = usb_ep_queue(out, req, GFP_ATOMIC);
+		spin_lock(&port->port_lock);
+
+		if (status) {
+			pr_err("%s: %s %s err %d\n",
+					__func__, "queue", out->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+		port->read_started++;
+
+		/* abort immediately after disconnect */
+		if (!port->port_usb)
+			break;
+	}
+	return port->read_started;
+}
+
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void gs_rx_push(struct work_struct *work)
+{
+	struct delayed_work	*w = to_delayed_work(work);
+	struct gs_port		*port = container_of(w, struct gs_port, push);
+	struct tty_struct	*tty;
+	struct list_head	*queue = &port->read_queue;
+	bool			disconnect = false;
+	bool			do_push = false;
+
+	/* hand any queued data to the tty */
+	spin_lock_irq(&port->port_lock);
+	tty = port->port.tty;
+	while (!list_empty(queue)) {
+		struct usb_request	*req;
+
+		req = list_first_entry(queue, struct usb_request, list);
+
+		/* leave data queued if tty was rx throttled */
+		if (tty && tty_throttled(tty))
+			break;
+
+		switch (req->status) {
+		case -ESHUTDOWN:
+			disconnect = true;
+			pr_vdebug("ttyGS%d: shutdown\n", port->port_num);
+			break;
+
+		default:
+			if (-ECONNRESET == req->status)
+				printk_ratelimited(KERN_DEBUG "ttyGS%d: unexpected RX status %d\n",
+				port->port_num, req->status);
+			else
+				/* presumably a transient fault */
+				pr_warn_ratelimited("ttyGS%d: unexpected RX status %d\n",
+					port->port_num, req->status);
+			/* FALLTHROUGH */
+		case 0:
+			/* normal completion */
+			break;
+		}
+
+		/* push data to (open) tty */
+		if (req->actual && tty) {
+			char		*packet = req->buf;
+			unsigned	size = req->actual;
+			unsigned	n;
+			int		count;
+
+			/* we may have pushed part of this packet already... */
+			n = port->n_read;
+			if (n) {
+				packet += n;
+				size -= n;
+			}
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+			if (unlikely(port->dbg_flag & (0x1 << 0)))
+				pr_info("%d: rx len=%d, %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+					port->port_num, size, *((u8 *)req->buf),
+					*((u8 *)req->buf+1), *((u8 *)req->buf+2),
+					*((u8 *)req->buf+3), *((u8 *)req->buf+4),
+					*((u8 *)req->buf+5), *((u8 *)req->buf+6),
+					*((u8 *)req->buf+7), *((u8 *)req->buf+8),
+					*((u8 *)req->buf+9), *((u8 *)req->buf+10),
+					*((u8 *)req->buf+11), *((u8 *)req->buf+12),
+					*((u8 *)req->buf+13), *((u8 *)req->buf+14),
+					*((u8 *)req->buf+15));
+#endif
+			count = tty_insert_flip_string(&port->port, packet,
+					size);
+			if (count)
+				do_push = true;
+			if (count != size) {
+				/* stop pushing; TTY layer can't handle more */
+				port->n_read += count;
+				pr_vdebug("ttyGS%d: rx block %d/%d\n",
+					  port->port_num, count, req->actual);
+				break;
+			}
+			port->n_read = 0;
+		}
+
+		list_move(&req->list, &port->read_pool);
+		port->read_started--;
+	}
+
+	/* Push from tty to ldisc; this is handled by a workqueue,
+	 * so we won't get callbacks and can hold port_lock
+	 */
+	if (do_push)
+		tty_flip_buffer_push(&port->port);
+
+
+	/* We want our data queue to become empty ASAP, keeping data
+	 * in the tty and ldisc (not here).  If we couldn't push any
+	 * this time around, RX may be starved, so wait until next jiffy.
+	 *
+	 * We may leave non-empty queue only when there is a tty, and
+	 * either it is throttled or there is no more room in flip buffer.
+	 */
+	if (!list_empty(queue) && !tty_throttled(tty))
+		schedule_delayed_work(&port->push, 1);
+
+	/* If we're still connected, refill the USB RX queue. */
+	if (!disconnect && port->port_usb)
+		gs_start_rx(port);
+
+	spin_unlock_irq(&port->port_lock);
+}
+
+static void gs_read_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct gs_port	*port = ep->driver_data;
+
+	/* Queue all received data until the tty layer is ready for it. */
+	spin_lock(&port->port_lock);
+	list_add_tail(&req->list, &port->read_queue);
+	schedule_delayed_work(&port->push, 0);
+	spin_unlock(&port->port_lock);
+}
+
+static void gs_write_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct gs_port	*port = ep->driver_data;
+
+	spin_lock(&port->port_lock);
+	list_add(&req->list, &port->write_pool);
+	port->write_started--;
+
+	switch (req->status) {
+	default:
+		/* presumably a transient fault */
+		if (-ECONNRESET == req->status)
+			printk_ratelimited(KERN_DEBUG "%s: unexpected %s status %d\n",
+			__func__, ep->name, req->status);
+		else
+			pr_warn("%s: unexpected %s status %d\n",
+			__func__, ep->name, req->status);
+		/* FALL THROUGH */
+	case 0:
+		/* normal completion */
+		gs_start_tx(port);
+		break;
+
+	case -ESHUTDOWN:
+		/* disconnect */
+		pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
+		break;
+	}
+
+	spin_unlock(&port->port_lock);
+}
+
+static void gs_free_requests(struct usb_ep *ep, struct list_head *head,
+							 int *allocated)
+{
+	struct usb_request	*req;
+
+	while (!list_empty(head)) {
+		req = list_entry(head->next, struct usb_request, list);
+		list_del(&req->list);
+		gs_free_req(ep, req);
+		if (allocated)
+			(*allocated)--;
+	}
+}
+
+static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head,
+		void (*fn)(struct usb_ep *, struct usb_request *),
+		int *allocated)
+{
+	int			i;
+	struct usb_request	*req;
+	int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE;
+
+	/* Pre-allocate up to QUEUE_SIZE transfers, but if we can't
+	 * do quite that many this time, don't fail ... we just won't
+	 * be as speedy as we might otherwise be.
+	 */
+	for (i = 0; i < n; i++) {
+		req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
+		if (!req)
+			return list_empty(head) ? -ENOMEM : 0;
+		req->complete = fn;
+		list_add_tail(&req->list, head);
+		if (allocated)
+			(*allocated)++;
+	}
+	return 0;
+}
+
+/**
+ * gs_start_io - start USB I/O streams
+ * @dev: encapsulates endpoints to use
+ * Context: holding port_lock; port_tty and port_usb are non-null
+ *
+ * We only start I/O when something is connected to both sides of
+ * this port.  If nothing is listening on the host side, we may
+ * be pointlessly filling up our TX buffers and FIFO.
+ */
+static int gs_start_io(struct gs_port *port)
+{
+	struct list_head	*head = &port->read_pool;
+	struct usb_ep		*ep = port->port_usb->out;
+	int			status;
+	unsigned		started;
+
+	/* Allocate RX and TX I/O buffers.  We can't easily do this much
+	 * earlier (with GFP_KERNEL) because the requests are coupled to
+	 * endpoints, as are the packet sizes we'll be using.  Different
+	 * configurations may use different endpoints with a given port;
+	 * and high speed vs full speed changes packet sizes too.
+	 */
+	status = gs_alloc_requests(ep, head, gs_read_complete,
+		&port->read_allocated);
+	if (status)
+		return status;
+
+	status = gs_alloc_requests(port->port_usb->in, &port->write_pool,
+			gs_write_complete, &port->write_allocated);
+	if (status) {
+		gs_free_requests(ep, head, &port->read_allocated);
+		return status;
+	}
+
+	/* queue read requests */
+	port->n_read = 0;
+	started = gs_start_rx(port);
+
+	if (started) {
+		gs_start_tx(port);
+		/* Unblock any pending writes into our circular buffer, in case
+		 * we didn't in gs_start_tx() */
+		tty_wakeup(port->port.tty);
+	} else {
+		/* Free reqs only if we are still connected */
+		if (port->port_usb) {
+			gs_free_requests(ep, head, &port->read_allocated);
+			gs_free_requests(port->port_usb->in, &port->write_pool,
+				&port->write_allocated);
+		}
+		status = -EIO;
+	}
+
+	return status;
+}
+
+/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+static int gserial_dbg_show(struct device *dev, struct device_attribute *attr,
+                                char *buf)
+{
+        return sprintf(buf, "%ld\n", ports[0].port->dbg_flag);
+}
+
+static int gserial_dbg_store(struct device *dev, struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+        unsigned long val;
+        int i;
+
+        if (kstrtoul(buf, 10, &val)) {
+                pr_err("Invalid Arg!\n");
+                return count;
+        }
+
+        for (i = 0; i < MAX_U_SERIAL_PORTS; i++) {
+                if (ports[i].port && ports[i].port->port_usb)
+                        ports[i].port->dbg_flag = val;
+        }
+
+        return count;
+}
+static DEVICE_ATTR(dbg_flag, 0644, gserial_dbg_show, gserial_dbg_store);
+#endif
+
+/* TTY Driver */
+
+/*
+ * gs_open sets up the link between a gs_port and its associated TTY.
+ * That link is broken *only* by TTY close(), and all driver methods
+ * know that.
+ */
+static int gs_open(struct tty_struct *tty, struct file *file)
+{
+	int		port_num = tty->index;
+	struct gs_port	*port;
+	int		status;
+
+	do {
+		mutex_lock(&ports[port_num].lock);
+		port = ports[port_num].port;
+		if (!port)
+			status = -ENODEV;
+		else {
+			spin_lock_irq(&port->port_lock);
+
+			/* already open?  Great. */
+			if (port->port.count) {
+				status = 0;
+				port->port.count++;
+
+			/* currently opening/closing? wait ... */
+			} else if (port->openclose) {
+				status = -EBUSY;
+
+			/* ... else we do the work */
+			} else {
+				status = -EAGAIN;
+				port->openclose = true;
+			}
+			spin_unlock_irq(&port->port_lock);
+		}
+		mutex_unlock(&ports[port_num].lock);
+
+		switch (status) {
+		default:
+			/* fully handled */
+			return status;
+		case -EAGAIN:
+			/* must do the work */
+			break;
+		case -EBUSY:
+			/* wait for EAGAIN task to finish */
+			msleep(1);
+			/* REVISIT could have a waitchannel here, if
+			 * concurrent open performance is important
+			 */
+			break;
+		}
+	} while (status != -EAGAIN);
+
+	/* Do the "real open" */
+	spin_lock_irq(&port->port_lock);
+
+	/* allocate circular buffer on first open */
+	if (!kfifo_initialized(&port->port_write_buf)) {
+
+		spin_unlock_irq(&port->port_lock);
+		status = kfifo_alloc(&port->port_write_buf,
+				     WRITE_BUF_SIZE, GFP_KERNEL);
+		spin_lock_irq(&port->port_lock);
+
+		if (status) {
+			pr_err("gs_open: ttyGS%d (%p,%p) no buffer\n",
+				port->port_num, tty, file);
+			port->openclose = false;
+			goto exit_unlock_port;
+		}
+	}
+
+	/* REVISIT if REMOVED (ports[].port NULL), abort the open
+	 * to let rmmod work faster (but this way isn't wrong).
+	 */
+
+	/* REVISIT maybe wait for "carrier detect" */
+
+	tty->driver_data = port;
+	port->port.tty = tty;
+
+	port->port.count = 1;
+	port->openclose = false;
+
+	/* if connected, start the I/O stream */
+	if (port->port_usb) {
+		struct gserial	*gser = port->port_usb;
+
+		pr_debug("gs_open: start ttyGS%d\n", port->port_num);
+		gs_start_io(port);
+
+		if (gser->connect)
+			gser->connect(gser);
+	}
+
+	pr_info("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
+
+	status = 0;
+
+exit_unlock_port:
+	spin_unlock_irq(&port->port_lock);
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+	if (0 == status && port->port.tty && port->port.tty->dev) {
+		status = device_create_file(port->port.tty->dev, &dev_attr_dbg_flag);
+		if (status < 0)
+			pr_err("%s: create dbg file error\n", __func__);
+	}
+#endif
+	return status;
+}
+
+static int gs_writes_finished(struct gs_port *p)
+{
+	int cond;
+
+	/* return true on disconnect or empty buffer */
+	spin_lock_irq(&p->port_lock);
+	cond = (p->port_usb == NULL) || !kfifo_len(&p->port_write_buf);
+	spin_unlock_irq(&p->port_lock);
+
+	return cond;
+}
+
+static void gs_close(struct tty_struct *tty, struct file *file)
+{
+	struct gs_port *port = tty->driver_data;
+	struct gserial	*gser;
+
+	spin_lock_irq(&port->port_lock);
+
+	if (port->port.count != 1) {
+		if (port->port.count == 0)
+			WARN_ON(1);
+		else
+			--port->port.count;
+		goto exit;
+	}
+#ifdef CONFIG_USERIAL_TX_RX_DBG
+	spin_unlock_irq(&port->port_lock);
+	device_remove_file(port->port.tty->dev, &dev_attr_dbg_flag);
+	spin_lock_irq(&port->port_lock);
+#endif
+	pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file);
+
+	/* mark port as closing but in use; we can drop port lock
+	 * and sleep if necessary
+	 */
+	port->openclose = true;
+	port->port.count = 0;
+
+	gser = port->port_usb;
+	if (gser && gser->disconnect)
+		gser->disconnect(gser);
+
+	/* wait for circular write buffer to drain, disconnect, or at
+	 * most GS_CLOSE_TIMEOUT seconds; then discard the rest
+	 */
+	if (kfifo_len(&port->port_write_buf) > 0 && gser) {
+		spin_unlock_irq(&port->port_lock);
+		wait_event_interruptible_timeout(port->drain_wait,
+					gs_writes_finished(port),
+					GS_CLOSE_TIMEOUT * HZ);
+		spin_lock_irq(&port->port_lock);
+		gser = port->port_usb;
+	}
+
+	/* Iff we're disconnected, there can be no I/O in flight so it's
+	 * ok to free the circular buffer; else just scrub it.  And don't
+	 * let the push tasklet fire again until we're re-opened.
+	 */
+	if (gser == NULL)
+		kfifo_free(&port->port_write_buf);
+	else
+		kfifo_reset(&port->port_write_buf);
+
+	port->port.tty = NULL;
+
+	port->openclose = false;
+
+	pr_info("gs_close: ttyGS%d (%p,%p) done!\n",
+			port->port_num, tty, file);
+
+	wake_up(&port->close_wait);
+exit:
+	spin_unlock_irq(&port->port_lock);
+}
+
+static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	struct gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+
+	pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n",
+			port->port_num, tty, count);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (count)
+		count = kfifo_in(&port->port_write_buf, buf, count);
+	/* treat count == 0 as flush_chars() */
+	if (port->port_usb)
+		gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return count;
+}
+
+static int gs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		status;
+
+	pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %ps\n",
+		port->port_num, tty, ch, __builtin_return_address(0));
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	status = kfifo_put(&port->port_write_buf, ch);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return status;
+}
+
+static void gs_flush_chars(struct tty_struct *tty)
+{
+	struct gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+
+	if (unlikely(!port)) {
+		pr_err("%s: port closed\n", __func__);
+		return;
+	}
+	pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty);
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		gs_start_tx(port);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int gs_write_room(struct tty_struct *tty)
+{
+	struct gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		room = 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb)
+		room = kfifo_avail(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_write_room: (%d,%p) room=%d\n",
+		port->port_num, tty, room);
+
+	return room;
+}
+
+static int gs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct gs_port	*port = tty->driver_data;
+	unsigned long	flags;
+	int		chars = 0;
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	chars = kfifo_len(&port->port_write_buf);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n",
+		port->port_num, tty, chars);
+
+	return chars;
+}
+
+/* undo side effects of setting TTY_THROTTLED */
+static void gs_unthrottle(struct tty_struct *tty)
+{
+	struct gs_port		*port = tty->driver_data;
+	unsigned long		flags;
+
+	/* skip the push tasklet as gs_close is invoked */
+	if (unlikely(!port)) {
+		pr_err("%s: err NULL port\n", __func__);
+		return;
+	}
+
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port_usb) {
+		/* Kickstart read queue processing.  We don't do xon/xoff,
+		 * rts/cts, or other handshaking with the host, but if the
+		 * read queue backs up enough we'll be NAKing OUT packets.
+		 */
+		pr_vdebug("ttyGS%d: unthrottle\n", port->port_num);
+		schedule_delayed_work(&port->push, 0);
+	}
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int gs_break_ctl(struct tty_struct *tty, int duration)
+{
+	struct gs_port	*port = tty->driver_data;
+	int		status = 0;
+	struct gserial	*gser;
+
+	pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d) \n",
+			port->port_num, duration);
+
+	spin_lock_irq(&port->port_lock);
+	gser = port->port_usb;
+	if (gser && gser->send_break)
+		status = gser->send_break(gser, duration);
+	spin_unlock_irq(&port->port_lock);
+
+	return status;
+}
+
+static const struct tty_operations gs_tty_ops = {
+	.open =			gs_open,
+	.close =		gs_close,
+	.write =		gs_write,
+	.put_char =		gs_put_char,
+	.flush_chars =		gs_flush_chars,
+	.write_room =		gs_write_room,
+	.chars_in_buffer =	gs_chars_in_buffer,
+	.unthrottle =		gs_unthrottle,
+	.break_ctl =		gs_break_ctl,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct tty_driver *gs_tty_driver;
+
+#ifdef CONFIG_U_SERIAL_CONSOLE
+
+static struct gscons_info gscons_info;
+static struct console gserial_cons;
+
+static struct usb_request *gs_request_new(struct usb_ep *ep)
+{
+	struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+	if (!req)
+		return NULL;
+
+	req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC);
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+
+	return req;
+}
+
+static void gs_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+	if (!req)
+		return;
+
+	kfree(req->buf);
+	usb_ep_free_request(ep, req);
+}
+
+static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+	struct gscons_info *info = &gscons_info;
+
+	switch (req->status) {
+	default:
+		if (-ECONNRESET == req->status)
+			printk_ratelimited(KERN_DEBUG "%s: unexpected %s status %d\n",
+			__func__, ep->name, req->status);
+		else
+			pr_warn("%s: unexpected %s status %d\n",
+				__func__, ep->name, req->status);
+		/* fall through */
+	case 0:
+		/* normal completion */
+		spin_lock(&info->con_lock);
+		info->req_busy = 0;
+		spin_unlock(&info->con_lock);
+
+		wake_up_process(info->console_thread);
+		break;
+	case -ESHUTDOWN:
+		/* disconnect */
+		pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
+		break;
+	}
+}
+
+static int gs_console_connect(int port_num)
+{
+	struct gscons_info *info = &gscons_info;
+	struct gs_port *port;
+	struct usb_ep *ep;
+
+	if (port_num != gserial_cons.index) {
+		pr_err("%s: port num [%d] is not support console\n",
+		       __func__, port_num);
+		return -ENXIO;
+	}
+
+	port = ports[port_num].port;
+	ep = port->port_usb->in;
+	if (!info->console_req) {
+		info->console_req = gs_request_new(ep);
+		if (!info->console_req)
+			return -ENOMEM;
+		info->console_req->complete = gs_complete_out;
+	}
+
+	info->port = port;
+	spin_lock(&info->con_lock);
+	info->req_busy = 0;
+	spin_unlock(&info->con_lock);
+	pr_vdebug("port[%d] console connect!\n", port_num);
+	return 0;
+}
+
+static void gs_console_disconnect(struct usb_ep *ep)
+{
+	struct gscons_info *info = &gscons_info;
+	struct usb_request *req = info->console_req;
+
+	gs_request_free(req, ep);
+	info->console_req = NULL;
+}
+
+static int gs_console_thread(void *data)
+{
+	struct gscons_info *info = &gscons_info;
+	struct gs_port *port;
+	struct usb_request *req;
+	struct usb_ep *ep;
+	int xfer, ret, count, size;
+
+	do {
+		port = info->port;
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (!port || !port->port_usb
+		    || !port->port_usb->in || !info->console_req)
+			goto sched;
+
+		req = info->console_req;
+		ep = port->port_usb->in;
+
+		spin_lock_irq(&info->con_lock);
+		count = kfifo_len(&info->con_buf);
+		size = ep->maxpacket;
+
+		if (count > 0 && !info->req_busy) {
+			set_current_state(TASK_RUNNING);
+			if (count < size)
+				size = count;
+
+			xfer = kfifo_out(&info->con_buf, req->buf, size);
+			req->length = xfer;
+
+			spin_unlock(&info->con_lock);
+			ret = usb_ep_queue(ep, req, GFP_ATOMIC);
+			spin_lock(&info->con_lock);
+			if (ret < 0)
+				info->req_busy = 0;
+			else
+				info->req_busy = 1;
+
+			spin_unlock_irq(&info->con_lock);
+		} else {
+			spin_unlock_irq(&info->con_lock);
+sched:
+			if (kthread_should_stop()) {
+				set_current_state(TASK_RUNNING);
+				break;
+			}
+			schedule();
+		}
+	} while (1);
+
+	return 0;
+}
+
+static int gs_console_setup(struct console *co, char *options)
+{
+	struct gscons_info *info = &gscons_info;
+	int status;
+
+	info->port = NULL;
+	info->console_req = NULL;
+	info->req_busy = 0;
+	spin_lock_init(&info->con_lock);
+
+	status = kfifo_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE, GFP_KERNEL);
+	if (status) {
+		pr_err("%s: allocate console buffer failed\n", __func__);
+		return status;
+	}
+
+	info->console_thread = kthread_create(gs_console_thread,
+					      co, "gs_console");
+	if (IS_ERR(info->console_thread)) {
+		pr_err("%s: cannot create console thread\n", __func__);
+		kfifo_free(&info->con_buf);
+		return PTR_ERR(info->console_thread);
+	}
+	wake_up_process(info->console_thread);
+
+	return 0;
+}
+
+static void gs_console_write(struct console *co,
+			     const char *buf, unsigned count)
+{
+	struct gscons_info *info = &gscons_info;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->con_lock, flags);
+	kfifo_in(&info->con_buf, buf, count);
+	spin_unlock_irqrestore(&info->con_lock, flags);
+
+	wake_up_process(info->console_thread);
+}
+
+static struct tty_driver *gs_console_device(struct console *co, int *index)
+{
+	struct tty_driver **p = (struct tty_driver **)co->data;
+
+	if (!*p)
+		return NULL;
+
+	*index = co->index;
+	return *p;
+}
+
+static struct console gserial_cons = {
+	.name =		"ttyGS",
+	.write =	gs_console_write,
+	.device =	gs_console_device,
+	.setup =	gs_console_setup,
+	.flags =	CON_PRINTBUFFER,
+	.index =	-1,
+	.data =		&gs_tty_driver,
+};
+
+static void gserial_console_init(void)
+{
+	register_console(&gserial_cons);
+}
+
+static void gserial_console_exit(void)
+{
+	struct gscons_info *info = &gscons_info;
+
+	unregister_console(&gserial_cons);
+	if (!IS_ERR_OR_NULL(info->console_thread))
+		kthread_stop(info->console_thread);
+	kfifo_free(&info->con_buf);
+}
+
+#else
+
+static int gs_console_connect(int port_num)
+{
+	return 0;
+}
+
+static void gs_console_disconnect(struct usb_ep *ep)
+{
+}
+
+static void gserial_console_init(void)
+{
+}
+
+static void gserial_console_exit(void)
+{
+}
+
+#endif
+
+static int
+gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
+{
+	struct gs_port	*port;
+	int		ret = 0;
+
+	mutex_lock(&ports[port_num].lock);
+	if (ports[port_num].port) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	port = kzalloc(sizeof(struct gs_port), GFP_KERNEL);
+	if (port == NULL) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	tty_port_init(&port->port);
+	spin_lock_init(&port->port_lock);
+	init_waitqueue_head(&port->drain_wait);
+	init_waitqueue_head(&port->close_wait);
+
+	INIT_DELAYED_WORK(&port->push, gs_rx_push);
+
+	INIT_LIST_HEAD(&port->read_pool);
+	INIT_LIST_HEAD(&port->read_queue);
+	INIT_LIST_HEAD(&port->write_pool);
+
+	port->port_num = port_num;
+	port->port_line_coding = *coding;
+
+	ports[port_num].port = port;
+out:
+	mutex_unlock(&ports[port_num].lock);
+	return ret;
+}
+
+static int gs_closed(struct gs_port *port)
+{
+	int cond;
+
+	spin_lock_irq(&port->port_lock);
+	cond = (port->port.count == 0) && !port->openclose;
+	spin_unlock_irq(&port->port_lock);
+	return cond;
+}
+
+static void gserial_free_port(struct gs_port *port)
+{
+	cancel_delayed_work_sync(&port->push);
+	/* wait for old opens to finish */
+	wait_event(port->close_wait, gs_closed(port));
+	WARN_ON(port->port_usb != NULL);
+	tty_port_destroy(&port->port);
+	kfree(port);
+}
+
+void gserial_free_line(unsigned char port_num)
+{
+	struct gs_port	*port;
+
+	mutex_lock(&ports[port_num].lock);
+	if (WARN_ON(!ports[port_num].port)) {
+		mutex_unlock(&ports[port_num].lock);
+		return;
+	}
+	port = ports[port_num].port;
+	ports[port_num].port = NULL;
+	mutex_unlock(&ports[port_num].lock);
+
+	gserial_free_port(port);
+	tty_unregister_device(gs_tty_driver, port_num);
+	gserial_console_exit();
+}
+EXPORT_SYMBOL_GPL(gserial_free_line);
+
+int gserial_alloc_line(unsigned char *line_num)
+{
+	struct usb_cdc_line_coding	coding;
+	struct device			*tty_dev;
+	int				ret;
+	int				port_num;
+
+	coding.dwDTERate = cpu_to_le32(9600);
+	coding.bCharFormat = 8;
+	coding.bParityType = USB_CDC_NO_PARITY;
+	coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+	for (port_num = 0; port_num < MAX_U_SERIAL_PORTS; port_num++) {
+		ret = gs_port_alloc(port_num, &coding);
+		if (ret == -EBUSY)
+			continue;
+		if (ret)
+			return ret;
+		break;
+	}
+	if (ret)
+		return ret;
+
+	/* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
+
+	tty_dev = tty_port_register_device(&ports[port_num].port->port,
+			gs_tty_driver, port_num, NULL);
+	if (IS_ERR(tty_dev)) {
+		struct gs_port	*port;
+		pr_err("%s: failed to register tty for port %d, err %ld\n",
+				__func__, port_num, PTR_ERR(tty_dev));
+
+		ret = PTR_ERR(tty_dev);
+		mutex_lock(&ports[port_num].lock);
+		port = ports[port_num].port;
+		ports[port_num].port = NULL;
+		mutex_unlock(&ports[port_num].lock);
+		gserial_free_port(port);
+		goto err;
+	}
+	*line_num = port_num;
+	gserial_console_init();
+err:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gserial_alloc_line);
+
+/**
+ * gserial_connect - notify TTY I/O glue that USB link is active
+ * @gser: the function, set up with endpoints and descriptors
+ * @port_num: which port is active
+ * Context: any (usually from irq)
+ *
+ * This is called activate endpoints and let the TTY layer know that
+ * the connection is active ... not unlike "carrier detect".  It won't
+ * necessarily start I/O queues; unless the TTY is held open by any
+ * task, there would be no point.  However, the endpoints will be
+ * activated so the USB host can perform I/O, subject to basic USB
+ * hardware flow control.
+ *
+ * Caller needs to have set up the endpoints and USB function in @dev
+ * before calling this, as well as the appropriate (speed-specific)
+ * endpoint descriptors, and also have allocate @port_num by calling
+ * @gserial_alloc_line().
+ *
+ * Returns negative errno or zero.
+ * On success, ep->driver_data will be overwritten.
+ */
+int gserial_connect(struct gserial *gser, u8 port_num)
+{
+	struct gs_port	*port;
+	unsigned long	flags;
+	int		status;
+
+	if (port_num >= MAX_U_SERIAL_PORTS)
+		return -ENXIO;
+
+	port = ports[port_num].port;
+	if (!port) {
+		pr_err("serial line %d not allocated.\n", port_num);
+		return -EINVAL;
+	}
+	if (port->port_usb) {
+		pr_err("serial line %d is in use.\n", port_num);
+		return -EBUSY;
+	}
+
+	/* activate the endpoints */
+	status = usb_ep_enable(gser->in);
+	if (status < 0)
+		return status;
+	gser->in->driver_data = port;
+
+	status = usb_ep_enable(gser->out);
+	if (status < 0)
+		goto fail_out;
+	gser->out->driver_data = port;
+
+	/* then tell the tty glue that I/O can work */
+	spin_lock_irqsave(&port->port_lock, flags);
+	gser->ioport = port;
+	port->port_usb = gser;
+
+	/* REVISIT unclear how best to handle this state...
+	 * we don't really couple it with the Linux TTY.
+	 */
+	gser->port_line_coding = port->port_line_coding;
+
+	/* REVISIT if waiting on "carrier detect", signal. */
+
+	/* if it's already open, start I/O ... and notify the serial
+	 * protocol about open/close status (connect/disconnect).
+	 */
+	if (port->port.count) {
+		pr_debug("gserial_connect: start ttyGS%d\n", port->port_num);
+		gs_start_io(port);
+		if (gser->connect)
+			gser->connect(gser);
+	} else {
+		if (gser->disconnect)
+			gser->disconnect(gser);
+	}
+
+	status = gs_console_connect(port_num);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	return status;
+
+fail_out:
+	usb_ep_disable(gser->in);
+	return status;
+}
+EXPORT_SYMBOL_GPL(gserial_connect);
+/**
+ * gserial_disconnect - notify TTY I/O glue that USB link is inactive
+ * @gser: the function, on which gserial_connect() was called
+ * Context: any (usually from irq)
+ *
+ * This is called to deactivate endpoints and let the TTY layer know
+ * that the connection went inactive ... not unlike "hangup".
+ *
+ * On return, the state is as if gserial_connect() had never been called;
+ * there is no active USB I/O on these endpoints.
+ */
+void gserial_disconnect(struct gserial *gser)
+{
+	struct gs_port	*port = gser->ioport;
+	unsigned long	flags;
+
+	if (!port)
+		return;
+
+	/* tell the TTY glue not to do I/O here any more */
+	spin_lock_irqsave(&port->port_lock, flags);
+
+	/* REVISIT as above: how best to track this? */
+	port->port_line_coding = gser->port_line_coding;
+
+	port->port_usb = NULL;
+	gser->ioport = NULL;
+	if (port->port.count > 0 || port->openclose) {
+		wake_up_interruptible(&port->drain_wait);
+		if (port->port.tty)
+			tty_hangup(port->port.tty);
+	}
+	spin_unlock_irqrestore(&port->port_lock, flags);
+
+	/* disable endpoints, aborting down any active I/O */
+	usb_ep_disable(gser->out);
+	usb_ep_disable(gser->in);
+
+	/* finally, free any unused/unusable I/O buffers */
+	spin_lock_irqsave(&port->port_lock, flags);
+	if (port->port.count == 0 && !port->openclose)
+		kfifo_free(&port->port_write_buf);
+	gs_free_requests(gser->out, &port->read_pool, NULL);
+	gs_free_requests(gser->out, &port->read_queue, NULL);
+	gs_free_requests(gser->in, &port->write_pool, NULL);
+
+	port->read_allocated = port->read_started =
+		port->write_allocated = port->write_started = 0;
+
+	gs_console_disconnect(gser->in);
+	spin_unlock_irqrestore(&port->port_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gserial_disconnect);
+
+static int userial_init(void)
+{
+	unsigned			i;
+	int				status;
+
+	gs_tty_driver = alloc_tty_driver(MAX_U_SERIAL_PORTS);
+	if (!gs_tty_driver)
+		return -ENOMEM;
+
+	gs_tty_driver->driver_name = "g_serial";
+	gs_tty_driver->name = "ttyGS";
+	/* uses dynamically assigned dev_t values */
+
+	gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	gs_tty_driver->init_termios = tty_std_termios;
+
+	/* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+	 * MS-Windows.  Otherwise, most of these flags shouldn't affect
+	 * anything unless we were to actually hook up to a serial line.
+	 */
+	gs_tty_driver->init_termios.c_cflag =
+			B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	gs_tty_driver->init_termios.c_ispeed = 9600;
+	gs_tty_driver->init_termios.c_ospeed = 9600;
+
+	tty_set_operations(gs_tty_driver, &gs_tty_ops);
+	for (i = 0; i < MAX_U_SERIAL_PORTS; i++)
+		mutex_init(&ports[i].lock);
+
+	/* export the driver ... */
+	status = tty_register_driver(gs_tty_driver);
+	if (status) {
+		pr_err("%s: cannot register, err %d\n",
+				__func__, status);
+		goto fail;
+	}
+
+	pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+			MAX_U_SERIAL_PORTS,
+			(MAX_U_SERIAL_PORTS == 1) ? "" : "s");
+
+	return status;
+fail:
+	put_tty_driver(gs_tty_driver);
+	gs_tty_driver = NULL;
+	return status;
+}
+module_init(userial_init);
+
+static void userial_cleanup(void)
+{
+	tty_unregister_driver(gs_tty_driver);
+	put_tty_driver(gs_tty_driver);
+	gs_tty_driver = NULL;
+}
+module_exit(userial_cleanup);
+
+MODULE_LICENSE("GPL");
diff --git a/marvell/linux/drivers/usb/gadget/function/u_serial.h b/marvell/linux/drivers/usb/gadget/function/u_serial.h
new file mode 100644
index 0000000..9acaac1
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_serial.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_serial.h - interface to USB gadget "serial port"/TTY utilities
+ *
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ */
+
+#ifndef __U_SERIAL_H
+#define __U_SERIAL_H
+
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+#define MAX_U_SERIAL_PORTS	4
+
+struct f_serial_opts {
+	struct usb_function_instance func_inst;
+	u8 port_num;
+};
+
+/*
+ * One non-multiplexed "serial" I/O port ... there can be several of these
+ * on any given USB peripheral device, if it provides enough endpoints.
+ *
+ * The "u_serial" utility component exists to do one thing:  manage TTY
+ * style I/O using the USB peripheral endpoints listed here, including
+ * hookups to sysfs and /dev for each logical "tty" device.
+ *
+ * REVISIT at least ACM could support tiocmget() if needed.
+ *
+ * REVISIT someday, allow multiplexing several TTYs over these endpoints.
+ */
+struct gserial {
+	struct usb_function		func;
+
+	/* port is managed by gserial_{connect,disconnect} */
+	struct gs_port			*ioport;
+
+	struct usb_ep			*in;
+	struct usb_ep			*out;
+
+	/* REVISIT avoid this CDC-ACM support harder ... */
+	struct usb_cdc_line_coding port_line_coding;	/* 9600-8-N-1 etc */
+
+	/* notification callbacks */
+	void (*connect)(struct gserial *p);
+	void (*disconnect)(struct gserial *p);
+	int (*send_break)(struct gserial *p, int duration);
+};
+
+/* utilities to allocate/free request and buffer */
+struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
+void gs_free_req(struct usb_ep *, struct usb_request *req);
+
+/* management of individual TTY ports */
+int gserial_alloc_line(unsigned char *port_line);
+void gserial_free_line(unsigned char port_line);
+
+/* connect/disconnect is handled by individual functions */
+int gserial_connect(struct gserial *, u8 port_num);
+void gserial_disconnect(struct gserial *);
+
+/* functions are bound to configurations by a config or gadget driver */
+int gser_bind_config(struct usb_configuration *c, u8 port_num);
+int obex_bind_config(struct usb_configuration *c, u8 port_num);
+
+#endif /* __U_SERIAL_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_tcm.h b/marvell/linux/drivers/usb/gadget/function/u_tcm.h
new file mode 100644
index 0000000..3f7ccec
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_tcm.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_tcm.h
+ *
+ * Utility definitions for the tcm function
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
+ */
+
+#ifndef U_TCM_H
+#define U_TCM_H
+
+#include <linux/usb/composite.h>
+
+/**
+ * @dependent: optional dependent module. Meant for legacy gadget.
+ * If non-null its refcount will be increased when a tpg is created and
+ * decreased when tpg is dropped.
+ * @dep_lock: lock for dependent module operations.
+ * @ready: true if the dependent module information is set.
+ * @can_attach: true a function can be bound to gadget
+ * @has_dep: true if there is a dependent module
+ *
+ */
+struct f_tcm_opts {
+	struct usb_function_instance	func_inst;
+	struct module			*dependent;
+	struct mutex			dep_lock;
+	bool				ready;
+	bool				can_attach;
+	bool				has_dep;
+
+	/*
+	 * Callbacks to be removed when legacy tcm gadget disappears.
+	 *
+	 * If you use the new function registration interface
+	 * programmatically, you MUST set these callbacks to
+	 * something sensible (e.g. probe/remove the composite).
+	 */
+	int (*tcm_register_callback)(struct usb_function_instance *);
+	void (*tcm_unregister_callback)(struct usb_function_instance *);
+};
+
+#endif /* U_TCM_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_uac1.h b/marvell/linux/drivers/usb/gadget/function/u_uac1.h
new file mode 100644
index 0000000..6f1a9d7
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_uac1.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_uac1.h - Utility definitions for UAC1 function
+ *
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ */
+
+#ifndef __U_UAC1_H
+#define __U_UAC1_H
+
+#include <linux/usb/composite.h>
+
+#define UAC1_OUT_EP_MAX_PACKET_SIZE	200
+#define UAC1_DEF_CCHMASK	0x3
+#define UAC1_DEF_CSRATE		48000
+#define UAC1_DEF_CSSIZE		2
+#define UAC1_DEF_PCHMASK	0x3
+#define UAC1_DEF_PSRATE		48000
+#define UAC1_DEF_PSSIZE		2
+#define UAC1_DEF_REQ_NUM	2
+
+
+struct f_uac1_opts {
+	struct usb_function_instance	func_inst;
+	int				c_chmask;
+	int				c_srate;
+	int				c_ssize;
+	int				p_chmask;
+	int				p_srate;
+	int				p_ssize;
+	int				req_number;
+	unsigned			bound:1;
+
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* __U_UAC1_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.c b/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.c
new file mode 100644
index 0000000..5393e5c
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_uac1.c -- ALSA audio utilities for Gadget stack
+ *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/random.h>
+#include <linux/syscalls.h>
+
+#include "u_uac1_legacy.h"
+
+/*
+ * This component encapsulates the ALSA devices for USB audio gadget
+ */
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Some ALSA internal helper functions
+ */
+static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
+{
+	struct snd_interval t;
+	t.empty = 0;
+	t.min = t.max = val;
+	t.openmin = t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(i, &t);
+}
+
+static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
+				 snd_pcm_hw_param_t var, unsigned int val,
+				 int dir)
+{
+	int changed;
+	if (hw_is_mask(var)) {
+		struct snd_mask *m = hw_param_mask(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_mask_none(m);
+		} else {
+			if (dir > 0)
+				val++;
+			else if (dir < 0)
+				val--;
+			changed = snd_mask_refine_set(
+					hw_param_mask(params, var), val);
+		}
+	} else if (hw_is_interval(var)) {
+		struct snd_interval *i = hw_param_interval(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_interval_none(i);
+		} else if (dir == 0)
+			changed = snd_interval_refine_set(i, val);
+		else {
+			struct snd_interval t;
+			t.openmin = 1;
+			t.openmax = 1;
+			t.empty = 0;
+			t.integer = 0;
+			if (dir < 0) {
+				t.min = val - 1;
+				t.max = val;
+			} else {
+				t.min = val;
+				t.max = val+1;
+			}
+			changed = snd_interval_refine(i, &t);
+		}
+	} else
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Set default hardware params
+ */
+static int playback_default_hw_params(struct gaudio_snd_dev *snd)
+{
+	struct snd_pcm_substream *substream = snd->substream;
+	struct snd_pcm_hw_params *params;
+	snd_pcm_sframes_t result;
+
+       /*
+	* SNDRV_PCM_ACCESS_RW_INTERLEAVED,
+	* SNDRV_PCM_FORMAT_S16_LE
+	* CHANNELS: 2
+	* RATE: 48000
+	*/
+	snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+	snd->format = SNDRV_PCM_FORMAT_S16_LE;
+	snd->channels = 2;
+	snd->rate = 48000;
+
+	params = kzalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	_snd_pcm_hw_params_any(params);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+			snd->access, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+			snd->format, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+			snd->channels, 0);
+	_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+			snd->rate, 0);
+
+	snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+	snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params);
+
+	result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+	if (result < 0) {
+		ERROR(snd->card,
+			"Preparing sound card failed: %d\n", (int)result);
+		kfree(params);
+		return result;
+	}
+
+	/* Store the hardware parameters */
+	snd->access = params_access(params);
+	snd->format = params_format(params);
+	snd->channels = params_channels(params);
+	snd->rate = params_rate(params);
+
+	kfree(params);
+
+	INFO(snd->card,
+		"Hardware params: access %x, format %x, channels %d, rate %d\n",
+		snd->access, snd->format, snd->channels, snd->rate);
+
+	return 0;
+}
+
+/**
+ * Playback audio buffer data by ALSA PCM device
+ */
+size_t u_audio_playback(struct gaudio *card, void *buf, size_t count)
+{
+	struct gaudio_snd_dev	*snd = &card->playback;
+	struct snd_pcm_substream *substream = snd->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	ssize_t result;
+	snd_pcm_sframes_t frames;
+
+try_again:
+	if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+		runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+		result = snd_pcm_kernel_ioctl(substream,
+				SNDRV_PCM_IOCTL_PREPARE, NULL);
+		if (result < 0) {
+			ERROR(card, "Preparing sound card failed: %d\n",
+					(int)result);
+			return result;
+		}
+	}
+
+	frames = bytes_to_frames(runtime, count);
+	result = snd_pcm_kernel_write(snd->substream, buf, frames);
+	if (result != frames) {
+		ERROR(card, "Playback error: %d\n", (int)result);
+		goto try_again;
+	}
+
+	return 0;
+}
+
+int u_audio_get_playback_channels(struct gaudio *card)
+{
+	return card->playback.channels;
+}
+
+int u_audio_get_playback_rate(struct gaudio *card)
+{
+	return card->playback.rate;
+}
+
+/**
+ * Open ALSA PCM and control device files
+ * Initial the PCM or control device
+ */
+static int gaudio_open_snd_dev(struct gaudio *card)
+{
+	struct snd_pcm_file *pcm_file;
+	struct gaudio_snd_dev *snd;
+	struct f_uac1_legacy_opts *opts;
+	char *fn_play, *fn_cap, *fn_cntl;
+
+	opts = container_of(card->func.fi, struct f_uac1_legacy_opts,
+			    func_inst);
+	fn_play = opts->fn_play;
+	fn_cap = opts->fn_cap;
+	fn_cntl = opts->fn_cntl;
+
+	/* Open control device */
+	snd = &card->control;
+	snd->filp = filp_open(fn_cntl, O_RDWR, 0);
+	if (IS_ERR(snd->filp)) {
+		int ret = PTR_ERR(snd->filp);
+		ERROR(card, "unable to open sound control device file: %s\n",
+				fn_cntl);
+		snd->filp = NULL;
+		return ret;
+	}
+	snd->card = card;
+
+	/* Open PCM playback device and setup substream */
+	snd = &card->playback;
+	snd->filp = filp_open(fn_play, O_WRONLY, 0);
+	if (IS_ERR(snd->filp)) {
+		int ret = PTR_ERR(snd->filp);
+
+		ERROR(card, "No such PCM playback device: %s\n", fn_play);
+		snd->filp = NULL;
+		return ret;
+	}
+	pcm_file = snd->filp->private_data;
+	snd->substream = pcm_file->substream;
+	snd->card = card;
+	playback_default_hw_params(snd);
+
+	/* Open PCM capture device and setup substream */
+	snd = &card->capture;
+	snd->filp = filp_open(fn_cap, O_RDONLY, 0);
+	if (IS_ERR(snd->filp)) {
+		ERROR(card, "No such PCM capture device: %s\n", fn_cap);
+		snd->substream = NULL;
+		snd->card = NULL;
+		snd->filp = NULL;
+	} else {
+		pcm_file = snd->filp->private_data;
+		snd->substream = pcm_file->substream;
+		snd->card = card;
+	}
+
+	return 0;
+}
+
+/**
+ * Close ALSA PCM and control device files
+ */
+static int gaudio_close_snd_dev(struct gaudio *gau)
+{
+	struct gaudio_snd_dev	*snd;
+
+	/* Close control device */
+	snd = &gau->control;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	/* Close PCM playback device and setup substream */
+	snd = &gau->playback;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	/* Close PCM capture device and setup substream */
+	snd = &gau->capture;
+	if (snd->filp)
+		filp_close(snd->filp, NULL);
+
+	return 0;
+}
+
+/**
+ * gaudio_setup - setup ALSA interface and preparing for USB transfer
+ *
+ * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using.
+ *
+ * Returns negative errno, or zero on success
+ */
+int gaudio_setup(struct gaudio *card)
+{
+	int	ret;
+
+	ret = gaudio_open_snd_dev(card);
+	if (ret)
+		ERROR(card, "we need at least one control device\n");
+
+	return ret;
+
+}
+
+/**
+ * gaudio_cleanup - remove ALSA device interface
+ *
+ * This is called to free all resources allocated by @gaudio_setup().
+ */
+void gaudio_cleanup(struct gaudio *the_card)
+{
+	if (the_card)
+		gaudio_close_snd_dev(the_card);
+}
+
diff --git a/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.h b/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.h
new file mode 100644
index 0000000..5c1bdf4
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_uac1_legacy.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
+ *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ */
+
+#ifndef __U_UAC1_LEGACY_H
+#define __U_UAC1_LEGACY_H
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/composite.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#define FILE_PCM_PLAYBACK	"/dev/snd/pcmC0D0p"
+#define FILE_PCM_CAPTURE	"/dev/snd/pcmC0D0c"
+#define FILE_CONTROL		"/dev/snd/controlC0"
+
+#define UAC1_OUT_EP_MAX_PACKET_SIZE	200
+#define UAC1_REQ_COUNT			256
+#define UAC1_AUDIO_BUF_SIZE		48000
+
+/*
+ * This represents the USB side of an audio card device, managed by a USB
+ * function which provides control and stream interfaces.
+ */
+
+struct gaudio_snd_dev {
+	struct gaudio			*card;
+	struct file			*filp;
+	struct snd_pcm_substream	*substream;
+	int				access;
+	int				format;
+	int				channels;
+	int				rate;
+};
+
+struct gaudio {
+	struct usb_function		func;
+	struct usb_gadget		*gadget;
+
+	/* ALSA sound device interfaces */
+	struct gaudio_snd_dev		control;
+	struct gaudio_snd_dev		playback;
+	struct gaudio_snd_dev		capture;
+
+	/* TODO */
+};
+
+struct f_uac1_legacy_opts {
+	struct usb_function_instance	func_inst;
+	int				req_buf_size;
+	int				req_count;
+	int				audio_buf_size;
+	char				*fn_play;
+	char				*fn_cap;
+	char				*fn_cntl;
+	unsigned			bound:1;
+	unsigned			fn_play_alloc:1;
+	unsigned			fn_cap_alloc:1;
+	unsigned			fn_cntl_alloc:1;
+	struct mutex			lock;
+	int				refcnt;
+};
+
+int gaudio_setup(struct gaudio *card);
+void gaudio_cleanup(struct gaudio *the_card);
+
+size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
+int u_audio_get_playback_channels(struct gaudio *card);
+int u_audio_get_playback_rate(struct gaudio *card);
+
+#endif /* __U_UAC1_LEGACY_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/u_uac2.h b/marvell/linux/drivers/usb/gadget/function/u_uac2.h
new file mode 100644
index 0000000..8204879
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_uac2.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_uac2.h
+ *
+ * Utility definitions for UAC2 function
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_UAC2_H
+#define U_UAC2_H
+
+#include <linux/usb/composite.h>
+
+#define UAC2_DEF_PCHMASK 0x3
+#define UAC2_DEF_PSRATE 48000
+#define UAC2_DEF_PSSIZE 2
+#define UAC2_DEF_CCHMASK 0x3
+#define UAC2_DEF_CSRATE 64000
+#define UAC2_DEF_CSSIZE 2
+#define UAC2_DEF_REQ_NUM 2
+
+struct f_uac2_opts {
+	struct usb_function_instance	func_inst;
+	int				p_chmask;
+	int				p_srate;
+	int				p_ssize;
+	int				c_chmask;
+	int				c_srate;
+	int				c_ssize;
+	int				req_number;
+	bool				bound;
+
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif
diff --git a/marvell/linux/drivers/usb/gadget/function/u_uvc.h b/marvell/linux/drivers/usb/gadget/function/u_uvc.h
new file mode 100644
index 0000000..16da49a
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/u_uvc.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * u_uvc.h
+ *
+ * Utility definitions for the uvc function
+ *
+ * Copyright (c) 2013-2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef U_UVC_H
+#define U_UVC_H
+
+#include <linux/mutex.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/video.h>
+
+#define fi_to_f_uvc_opts(f)	container_of(f, struct f_uvc_opts, func_inst)
+
+struct f_uvc_opts {
+	struct usb_function_instance			func_inst;
+	unsigned int					streaming_interval;
+	unsigned int					streaming_maxpacket;
+	unsigned int					streaming_maxburst;
+
+	unsigned int					control_interface;
+	unsigned int					streaming_interface;
+
+	/*
+	 * Control descriptors array pointers for full-/high-speed and
+	 * super-speed. They point by default to the uvc_fs_control_cls and
+	 * uvc_ss_control_cls arrays respectively. Legacy gadgets must
+	 * override them in their gadget bind callback.
+	 */
+	const struct uvc_descriptor_header * const	*fs_control;
+	const struct uvc_descriptor_header * const	*ss_control;
+
+	/*
+	 * Streaming descriptors array pointers for full-speed, high-speed and
+	 * super-speed. They will point to the uvc_[fhs]s_streaming_cls arrays
+	 * for configfs-based gadgets. Legacy gadgets must initialize them in
+	 * their gadget bind callback.
+	 */
+	const struct uvc_descriptor_header * const	*fs_streaming;
+	const struct uvc_descriptor_header * const	*hs_streaming;
+	const struct uvc_descriptor_header * const	*ss_streaming;
+
+	/* Default control descriptors for configfs-based gadgets. */
+	struct uvc_camera_terminal_descriptor		uvc_camera_terminal;
+	struct uvc_processing_unit_descriptor		uvc_processing;
+	struct uvc_output_terminal_descriptor		uvc_output_terminal;
+	struct uvc_color_matching_descriptor		uvc_color_matching;
+
+	/*
+	 * Control descriptors pointers arrays for full-/high-speed and
+	 * super-speed. The first element is a configurable control header
+	 * descriptor, the other elements point to the fixed default control
+	 * descriptors. Used by configfs only, must not be touched by legacy
+	 * gadgets.
+	 */
+	struct uvc_descriptor_header			*uvc_fs_control_cls[5];
+	struct uvc_descriptor_header			*uvc_ss_control_cls[5];
+
+	/*
+	 * Streaming descriptors for full-speed, high-speed and super-speed.
+	 * Used by configfs only, must not be touched by legacy gadgets. The
+	 * arrays are allocated at runtime as the number of descriptors isn't
+	 * known in advance.
+	 */
+	struct uvc_descriptor_header			**uvc_fs_streaming_cls;
+	struct uvc_descriptor_header			**uvc_hs_streaming_cls;
+	struct uvc_descriptor_header			**uvc_ss_streaming_cls;
+
+	/*
+	 * Read/write access to configfs attributes is handled by configfs.
+	 *
+	 * This lock protects the descriptors from concurrent access by
+	 * read/write and symlink creation/removal.
+	 */
+	struct mutex			lock;
+	int				refcnt;
+};
+
+#endif /* U_UVC_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc.h b/marvell/linux/drivers/usb/gadget/function/uvc.h
new file mode 100644
index 0000000..1473d25
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc.h
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	uvc_gadget.h  --  USB Video Class Gadget driver
+ *
+ *	Copyright (C) 2009-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#ifndef _UVC_GADGET_H_
+#define _UVC_GADGET_H_
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/usb/composite.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-fh.h>
+
+#include "uvc_queue.h"
+
+struct usb_ep;
+struct usb_request;
+struct uvc_descriptor_header;
+struct uvc_device;
+
+/* ------------------------------------------------------------------------
+ * Debugging, printing and logging
+ */
+
+#define UVC_TRACE_PROBE				(1 << 0)
+#define UVC_TRACE_DESCR				(1 << 1)
+#define UVC_TRACE_CONTROL			(1 << 2)
+#define UVC_TRACE_FORMAT			(1 << 3)
+#define UVC_TRACE_CAPTURE			(1 << 4)
+#define UVC_TRACE_CALLS				(1 << 5)
+#define UVC_TRACE_IOCTL				(1 << 6)
+#define UVC_TRACE_FRAME				(1 << 7)
+#define UVC_TRACE_SUSPEND			(1 << 8)
+#define UVC_TRACE_STATUS			(1 << 9)
+
+#define UVC_WARN_MINMAX				0
+#define UVC_WARN_PROBE_DEF			1
+
+extern unsigned int uvc_gadget_trace_param;
+
+#define uvc_trace(flag, msg...) \
+	do { \
+		if (uvc_gadget_trace_param & flag) \
+			printk(KERN_DEBUG "uvcvideo: " msg); \
+	} while (0)
+
+#define uvcg_dbg(f, fmt, args...) \
+	dev_dbg(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
+#define uvcg_info(f, fmt, args...) \
+	dev_info(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
+#define uvcg_warn(f, fmt, args...) \
+	dev_warn(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
+#define uvcg_err(f, fmt, args...) \
+	dev_err(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args)
+
+/* ------------------------------------------------------------------------
+ * Driver specific constants
+ */
+
+#define UVC_NUM_REQUESTS			4
+#define UVC_MAX_REQUEST_SIZE			64
+#define UVC_MAX_EVENTS				4
+
+/* ------------------------------------------------------------------------
+ * Structures
+ */
+
+struct uvc_video {
+	struct uvc_device *uvc;
+	struct usb_ep *ep;
+
+	/* Frame parameters */
+	u8 bpp;
+	u32 fcc;
+	unsigned int width;
+	unsigned int height;
+	unsigned int imagesize;
+	struct mutex mutex;	/* protects frame parameters */
+
+	/* Requests */
+	unsigned int req_size;
+	struct usb_request *req[UVC_NUM_REQUESTS];
+	__u8 *req_buffer[UVC_NUM_REQUESTS];
+	struct list_head req_free;
+	spinlock_t req_lock;
+
+	void (*encode) (struct usb_request *req, struct uvc_video *video,
+			struct uvc_buffer *buf);
+
+	/* Context data used by the completion handler */
+	__u32 payload_size;
+	__u32 max_payload_size;
+
+	struct uvc_video_queue queue;
+	unsigned int fid;
+};
+
+enum uvc_state {
+	UVC_STATE_DISCONNECTED,
+	UVC_STATE_CONNECTED,
+	UVC_STATE_STREAMING,
+};
+
+struct uvc_device {
+	struct video_device vdev;
+	struct v4l2_device v4l2_dev;
+	enum uvc_state state;
+	struct usb_function func;
+	struct uvc_video video;
+
+	/* Descriptors */
+	struct {
+		const struct uvc_descriptor_header * const *fs_control;
+		const struct uvc_descriptor_header * const *ss_control;
+		const struct uvc_descriptor_header * const *fs_streaming;
+		const struct uvc_descriptor_header * const *hs_streaming;
+		const struct uvc_descriptor_header * const *ss_streaming;
+	} desc;
+
+	unsigned int control_intf;
+	struct usb_ep *control_ep;
+	struct usb_request *control_req;
+	void *control_buf;
+
+	unsigned int streaming_intf;
+
+	/* Events */
+	unsigned int event_length;
+	unsigned int event_setup_out : 1;
+};
+
+static inline struct uvc_device *to_uvc(struct usb_function *f)
+{
+	return container_of(f, struct uvc_device, func);
+}
+
+struct uvc_file_handle {
+	struct v4l2_fh vfh;
+	struct uvc_video *device;
+};
+
+#define to_uvc_file_handle(handle) \
+	container_of(handle, struct uvc_file_handle, vfh)
+
+/* ------------------------------------------------------------------------
+ * Functions
+ */
+
+extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_endpoint_stream(struct uvc_device *dev);
+
+extern void uvc_function_connect(struct uvc_device *uvc);
+extern void uvc_function_disconnect(struct uvc_device *uvc);
+
+#endif /* _UVC_GADGET_H_ */
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_configfs.c b/marvell/linux/drivers/usb/gadget/function/uvc_configfs.c
new file mode 100644
index 0000000..00fb58e
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_configfs.c
@@ -0,0 +1,2467 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * uvc_configfs.c
+ *
+ * Configfs support for the uvc function.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#include <linux/sort.h>
+
+#include "u_uvc.h"
+#include "uvc_configfs.h"
+
+/* -----------------------------------------------------------------------------
+ * Global Utility Structures and Macros
+ */
+
+#define UVCG_STREAMING_CONTROL_SIZE	1
+
+#define UVC_ATTR(prefix, cname, aname) \
+static struct configfs_attribute prefix##attr_##cname = { \
+	.ca_name	= __stringify(aname),				\
+	.ca_mode	= S_IRUGO | S_IWUGO,				\
+	.ca_owner	= THIS_MODULE,					\
+	.show		= prefix##cname##_show,				\
+	.store		= prefix##cname##_store,			\
+}
+
+#define UVC_ATTR_RO(prefix, cname, aname) \
+static struct configfs_attribute prefix##attr_##cname = { \
+	.ca_name	= __stringify(aname),				\
+	.ca_mode	= S_IRUGO,					\
+	.ca_owner	= THIS_MODULE,					\
+	.show		= prefix##cname##_show,				\
+}
+
+#define le8_to_cpu(x)	(x)
+#define cpu_to_le8(x)	(x)
+
+static int uvcg_config_compare_u32(const void *l, const void *r)
+{
+	u32 li = *(const u32 *)l;
+	u32 ri = *(const u32 *)r;
+
+	return li < ri ? -1 : li == ri ? 0 : 1;
+}
+
+static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_uvc_opts,
+			    func_inst.group);
+}
+
+struct uvcg_config_group_type {
+	struct config_item_type type;
+	const char *name;
+	const struct uvcg_config_group_type **children;
+	int (*create_children)(struct config_group *group);
+};
+
+static void uvcg_config_item_release(struct config_item *item)
+{
+	struct config_group *group = to_config_group(item);
+
+	kfree(group);
+}
+
+static struct configfs_item_operations uvcg_config_item_ops = {
+	.release	= uvcg_config_item_release,
+};
+
+static int uvcg_config_create_group(struct config_group *parent,
+				    const struct uvcg_config_group_type *type);
+
+static int uvcg_config_create_children(struct config_group *group,
+				const struct uvcg_config_group_type *type)
+{
+	const struct uvcg_config_group_type **child;
+	int ret;
+
+	if (type->create_children)
+		return type->create_children(group);
+
+	for (child = type->children; child && *child; ++child) {
+		ret = uvcg_config_create_group(group, *child);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int uvcg_config_create_group(struct config_group *parent,
+				    const struct uvcg_config_group_type *type)
+{
+	struct config_group *group;
+
+	group = kzalloc(sizeof(*group), GFP_KERNEL);
+	if (!group)
+		return -ENOMEM;
+
+	config_group_init_type_name(group, type->name, &type->type);
+	configfs_add_default_group(group, parent);
+
+	return uvcg_config_create_children(group, type);
+}
+
+static void uvcg_config_remove_children(struct config_group *group)
+{
+	struct config_group *child, *n;
+
+	list_for_each_entry_safe(child, n, &group->default_groups, group_entry) {
+		list_del(&child->group_entry);
+		uvcg_config_remove_children(child);
+		config_item_put(&child->cg_item);
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * control/header/<NAME>
+ * control/header
+ */
+
+DECLARE_UVC_HEADER_DESCRIPTOR(1);
+
+struct uvcg_control_header {
+	struct config_item		item;
+	struct UVC_HEADER_DESCRIPTOR(1)	desc;
+	unsigned			linked;
+};
+
+static struct uvcg_control_header *to_uvcg_control_header(struct config_item *item)
+{
+	return container_of(item, struct uvcg_control_header, item);
+}
+
+#define UVCG_CTRL_HDR_ATTR(cname, aname, bits, limit)			\
+static ssize_t uvcg_control_header_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct uvcg_control_header *ch = to_uvcg_control_header(item);	\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = ch->item.ci_parent->ci_parent->ci_parent;		\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(ch->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+static ssize_t								\
+uvcg_control_header_##cname##_store(struct config_item *item,		\
+			   const char *page, size_t len)		\
+{									\
+	struct uvcg_control_header *ch = to_uvcg_control_header(item);	\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
+	int ret;							\
+	u##bits num;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = ch->item.ci_parent->ci_parent->ci_parent;		\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	if (ch->linked || opts->refcnt) {				\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou##bits(page, 0, &num);				\
+	if (ret)							\
+		goto end;						\
+									\
+	if (num > limit) {						\
+		ret = -EINVAL;						\
+		goto end;						\
+	}								\
+	ch->desc.aname = cpu_to_le##bits(num);				\
+	ret = len;							\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	mutex_unlock(su_mutex);						\
+	return ret;							\
+}									\
+									\
+UVC_ATTR(uvcg_control_header_, cname, aname)
+
+UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, 16, 0xffff);
+
+UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, 32, 0x7fffffff);
+
+#undef UVCG_CTRL_HDR_ATTR
+
+static struct configfs_attribute *uvcg_control_header_attrs[] = {
+	&uvcg_control_header_attr_bcd_uvc,
+	&uvcg_control_header_attr_dw_clock_frequency,
+	NULL,
+};
+
+static const struct config_item_type uvcg_control_header_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
+	.ct_attrs	= uvcg_control_header_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_item *uvcg_control_header_make(struct config_group *group,
+						    const char *name)
+{
+	struct uvcg_control_header *h;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return ERR_PTR(-ENOMEM);
+
+	h->desc.bLength			= UVC_DT_HEADER_SIZE(1);
+	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
+	h->desc.bDescriptorSubType	= UVC_VC_HEADER;
+	h->desc.bcdUVC			= cpu_to_le16(0x0100);
+	h->desc.dwClockFrequency	= cpu_to_le32(48000000);
+
+	config_item_init_type_name(&h->item, name, &uvcg_control_header_type);
+
+	return &h->item;
+}
+
+static struct configfs_group_operations uvcg_control_header_grp_ops = {
+	.make_item		= uvcg_control_header_make,
+};
+
+static const struct uvcg_config_group_type uvcg_control_header_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_control_header_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "header",
+};
+
+/* -----------------------------------------------------------------------------
+ * control/processing/default
+ */
+
+#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, bits)		\
+static ssize_t uvcg_default_processing_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct config_group *group = to_config_group(item);		\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
+	struct uvc_processing_unit_descriptor *pd;			\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;	\
+	opts = to_f_uvc_opts(opts_item);				\
+	pd = &opts->uvc_processing;					\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(pd->aname));	\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_default_processing_, cname, aname)
+
+UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, 8);
+UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, 8);
+UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, 16);
+UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, 8);
+
+#undef UVCG_DEFAULT_PROCESSING_ATTR
+
+static ssize_t uvcg_default_processing_bm_controls_show(
+	struct config_item *item, char *page)
+{
+	struct config_group *group = to_config_group(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvc_processing_unit_descriptor *pd;
+	int result, i;
+	char *pg = page;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+	pd = &opts->uvc_processing;
+
+	mutex_lock(&opts->lock);
+	for (result = 0, i = 0; i < pd->bControlSize; ++i) {
+		result += sprintf(pg, "%u\n", pd->bmControls[i]);
+		pg = page + result;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return result;
+}
+
+UVC_ATTR_RO(uvcg_default_processing_, bm_controls, bmControls);
+
+static struct configfs_attribute *uvcg_default_processing_attrs[] = {
+	&uvcg_default_processing_attr_b_unit_id,
+	&uvcg_default_processing_attr_b_source_id,
+	&uvcg_default_processing_attr_w_max_multiplier,
+	&uvcg_default_processing_attr_bm_controls,
+	&uvcg_default_processing_attr_i_processing,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_default_processing_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_processing_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
+};
+
+/* -----------------------------------------------------------------------------
+ * control/processing
+ */
+
+static const struct uvcg_config_group_type uvcg_processing_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "processing",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_processing_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * control/terminal/camera/default
+ */
+
+#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, bits)			\
+static ssize_t uvcg_default_camera_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct config_group *group = to_config_group(item);		\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
+	struct uvc_camera_terminal_descriptor *cd;			\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent->	\
+			ci_parent;					\
+	opts = to_f_uvc_opts(opts_item);				\
+	cd = &opts->uvc_camera_terminal;				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname));	\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+									\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_default_camera_, cname, aname)
+
+UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, 8);
+UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, 16);
+UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, 8);
+UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, 8);
+UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin,
+			 16);
+UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax,
+			 16);
+UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength,
+			 16);
+
+#undef UVCG_DEFAULT_CAMERA_ATTR
+
+static ssize_t uvcg_default_camera_bm_controls_show(
+	struct config_item *item, char *page)
+{
+	struct config_group *group = to_config_group(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvc_camera_terminal_descriptor *cd;
+	int result, i;
+	char *pg = page;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent->
+			ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+	cd = &opts->uvc_camera_terminal;
+
+	mutex_lock(&opts->lock);
+	for (result = 0, i = 0; i < cd->bControlSize; ++i) {
+		result += sprintf(pg, "%u\n", cd->bmControls[i]);
+		pg = page + result;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+	return result;
+}
+
+UVC_ATTR_RO(uvcg_default_camera_, bm_controls, bmControls);
+
+static struct configfs_attribute *uvcg_default_camera_attrs[] = {
+	&uvcg_default_camera_attr_b_terminal_id,
+	&uvcg_default_camera_attr_w_terminal_type,
+	&uvcg_default_camera_attr_b_assoc_terminal,
+	&uvcg_default_camera_attr_i_terminal,
+	&uvcg_default_camera_attr_w_objective_focal_length_min,
+	&uvcg_default_camera_attr_w_objective_focal_length_max,
+	&uvcg_default_camera_attr_w_ocular_focal_length,
+	&uvcg_default_camera_attr_bm_controls,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_default_camera_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_camera_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
+};
+
+/* -----------------------------------------------------------------------------
+ * control/terminal/camera
+ */
+
+static const struct uvcg_config_group_type uvcg_camera_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "camera",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_camera_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * control/terminal/output/default
+ */
+
+#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, bits)			\
+static ssize_t uvcg_default_output_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct config_group *group = to_config_group(item);		\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
+	struct uvc_output_terminal_descriptor *cd;			\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = group->cg_item.ci_parent->ci_parent->		\
+			ci_parent->ci_parent;				\
+	opts = to_f_uvc_opts(opts_item);				\
+	cd = &opts->uvc_output_terminal;				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname));	\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+									\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_default_output_, cname, aname)
+
+UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16);
+UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, 8);
+UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8);
+
+#undef UVCG_DEFAULT_OUTPUT_ATTR
+
+static struct configfs_attribute *uvcg_default_output_attrs[] = {
+	&uvcg_default_output_attr_b_terminal_id,
+	&uvcg_default_output_attr_w_terminal_type,
+	&uvcg_default_output_attr_b_assoc_terminal,
+	&uvcg_default_output_attr_b_source_id,
+	&uvcg_default_output_attr_i_terminal,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_default_output_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_output_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
+};
+
+/* -----------------------------------------------------------------------------
+ * control/terminal/output
+ */
+
+static const struct uvcg_config_group_type uvcg_output_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "output",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_output_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * control/terminal
+ */
+
+static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "terminal",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_camera_grp_type,
+		&uvcg_output_grp_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * control/class/{fs|ss}
+ */
+
+struct uvcg_control_class_group {
+	struct config_group group;
+	const char *name;
+};
+
+static inline struct uvc_descriptor_header
+**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
+{
+	struct uvcg_control_class_group *group =
+		container_of(i, struct uvcg_control_class_group,
+			     group.cg_item);
+
+	if (!strcmp(group->name, "fs"))
+		return o->uvc_fs_control_cls;
+
+	if (!strcmp(group->name, "ss"))
+		return o->uvc_ss_control_cls;
+
+	return NULL;
+}
+
+static int uvcg_control_class_allow_link(struct config_item *src,
+					 struct config_item *target)
+{
+	struct config_item *control, *header;
+	struct f_uvc_opts *opts;
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct uvc_descriptor_header **class_array;
+	struct uvcg_control_header *target_hdr;
+	int ret = -EINVAL;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	control = src->ci_parent->ci_parent;
+	header = config_group_find_item(to_config_group(control), "header");
+	if (!header || target->ci_parent != header)
+		goto out;
+
+	opts = to_f_uvc_opts(control->ci_parent);
+
+	mutex_lock(&opts->lock);
+
+	class_array = uvcg_get_ctl_class_arr(src, opts);
+	if (!class_array)
+		goto unlock;
+	if (opts->refcnt || class_array[0]) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	target_hdr = to_uvcg_control_header(target);
+	++target_hdr->linked;
+	class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc;
+	ret = 0;
+
+unlock:
+	mutex_unlock(&opts->lock);
+out:
+	config_item_put(header);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+static void uvcg_control_class_drop_link(struct config_item *src,
+					struct config_item *target)
+{
+	struct config_item *control, *header;
+	struct f_uvc_opts *opts;
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct uvc_descriptor_header **class_array;
+	struct uvcg_control_header *target_hdr;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	control = src->ci_parent->ci_parent;
+	header = config_group_find_item(to_config_group(control), "header");
+	if (!header || target->ci_parent != header)
+		goto out;
+
+	opts = to_f_uvc_opts(control->ci_parent);
+
+	mutex_lock(&opts->lock);
+
+	class_array = uvcg_get_ctl_class_arr(src, opts);
+	if (!class_array || opts->refcnt)
+		goto unlock;
+
+	target_hdr = to_uvcg_control_header(target);
+	--target_hdr->linked;
+	class_array[0] = NULL;
+
+unlock:
+	mutex_unlock(&opts->lock);
+out:
+	config_item_put(header);
+	mutex_unlock(su_mutex);
+}
+
+static struct configfs_item_operations uvcg_control_class_item_ops = {
+	.release	= uvcg_config_item_release,
+	.allow_link	= uvcg_control_class_allow_link,
+	.drop_link	= uvcg_control_class_drop_link,
+};
+
+static const struct config_item_type uvcg_control_class_type = {
+	.ct_item_ops	= &uvcg_control_class_item_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+/* -----------------------------------------------------------------------------
+ * control/class
+ */
+
+static int uvcg_control_class_create_children(struct config_group *parent)
+{
+	static const char * const names[] = { "fs", "ss" };
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(names); ++i) {
+		struct uvcg_control_class_group *group;
+
+		group = kzalloc(sizeof(*group), GFP_KERNEL);
+		if (!group)
+			return -ENOMEM;
+
+		group->name = names[i];
+
+		config_group_init_type_name(&group->group, group->name,
+					    &uvcg_control_class_type);
+		configfs_add_default_group(&group->group, parent);
+	}
+
+	return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_control_class_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "class",
+	.create_children = uvcg_control_class_create_children,
+};
+
+/* -----------------------------------------------------------------------------
+ * control
+ */
+
+static ssize_t uvcg_default_control_b_interface_number_show(
+	struct config_item *item, char *page)
+{
+	struct config_group *group = to_config_group(item);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	int result = 0;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = item->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	result += sprintf(page, "%u\n", opts->control_interface);
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return result;
+}
+
+UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber);
+
+static struct configfs_attribute *uvcg_default_control_attrs[] = {
+	&uvcg_default_control_attr_b_interface_number,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_control_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_control_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "control",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_control_header_grp_type,
+		&uvcg_processing_grp_type,
+		&uvcg_terminal_grp_type,
+		&uvcg_control_class_grp_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/uncompressed
+ * streaming/mjpeg
+ */
+
+static const char * const uvcg_format_names[] = {
+	"uncompressed",
+	"mjpeg",
+};
+
+enum uvcg_format_type {
+	UVCG_UNCOMPRESSED = 0,
+	UVCG_MJPEG,
+};
+
+struct uvcg_format {
+	struct config_group	group;
+	enum uvcg_format_type	type;
+	unsigned		linked;
+	unsigned		num_frames;
+	__u8			bmaControls[UVCG_STREAMING_CONTROL_SIZE];
+};
+
+static struct uvcg_format *to_uvcg_format(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct uvcg_format, group);
+}
+
+static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page)
+{
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &f->group.cg_subsys->su_mutex;
+	int result, i;
+	char *pg = page;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	result = sprintf(pg, "0x");
+	pg += result;
+	for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) {
+		result += sprintf(pg, "%x\n", f->bmaControls[i]);
+		pg = page + result;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+	return result;
+}
+
+static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch,
+					      const char *page, size_t len)
+{
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex;
+	int ret = -EINVAL;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	if (ch->linked || opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	if (len < 4 || *page != '0' ||
+	    (*(page + 1) != 'x' && *(page + 1) != 'X'))
+		goto end;
+	ret = hex2bin(ch->bmaControls, page + 2, 1);
+	if (ret < 0)
+		goto end;
+	ret = len;
+end:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+struct uvcg_format_ptr {
+	struct uvcg_format	*fmt;
+	struct list_head	entry;
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/header/<NAME>
+ * streaming/header
+ */
+
+struct uvcg_streaming_header {
+	struct config_item				item;
+	struct uvc_input_header_descriptor		desc;
+	unsigned					linked;
+	struct list_head				formats;
+	unsigned					num_fmt;
+};
+
+static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item)
+{
+	return container_of(item, struct uvcg_streaming_header, item);
+}
+
+static void uvcg_format_set_indices(struct config_group *fmt);
+
+static int uvcg_streaming_header_allow_link(struct config_item *src,
+					    struct config_item *target)
+{
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	struct uvcg_streaming_header *src_hdr;
+	struct uvcg_format *target_fmt = NULL;
+	struct uvcg_format_ptr *format_ptr;
+	int i, ret = -EINVAL;
+
+	src_hdr = to_uvcg_streaming_header(src);
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = src->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+
+	if (src_hdr->linked) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * Linking is only allowed to direct children of the format nodes
+	 * (streaming/uncompressed or streaming/mjpeg nodes). First check that
+	 * the grand-parent of the target matches the grand-parent of the source
+	 * (the streaming node), and then verify that the target parent is a
+	 * format node.
+	 */
+	if (src->ci_parent->ci_parent != target->ci_parent->ci_parent)
+		goto out;
+
+	for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) {
+		if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i]))
+			break;
+	}
+
+	if (i == ARRAY_SIZE(uvcg_format_names))
+		goto out;
+
+	target_fmt = container_of(to_config_group(target), struct uvcg_format,
+				  group);
+	if (!target_fmt)
+		goto out;
+
+	uvcg_format_set_indices(to_config_group(target));
+
+	format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL);
+	if (!format_ptr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	ret = 0;
+	format_ptr->fmt = target_fmt;
+	list_add_tail(&format_ptr->entry, &src_hdr->formats);
+	++src_hdr->num_fmt;
+	++target_fmt->linked;
+
+out:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+static void uvcg_streaming_header_drop_link(struct config_item *src,
+					   struct config_item *target)
+{
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	struct uvcg_streaming_header *src_hdr;
+	struct uvcg_format *target_fmt = NULL;
+	struct uvcg_format_ptr *format_ptr, *tmp;
+
+	src_hdr = to_uvcg_streaming_header(src);
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = src->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	target_fmt = container_of(to_config_group(target), struct uvcg_format,
+				  group);
+	if (!target_fmt)
+		goto out;
+
+	list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry)
+		if (format_ptr->fmt == target_fmt) {
+			list_del(&format_ptr->entry);
+			kfree(format_ptr);
+			--src_hdr->num_fmt;
+			break;
+		}
+
+	--target_fmt->linked;
+
+out:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+}
+
+static struct configfs_item_operations uvcg_streaming_header_item_ops = {
+	.release	= uvcg_config_item_release,
+	.allow_link	= uvcg_streaming_header_allow_link,
+	.drop_link	= uvcg_streaming_header_drop_link,
+};
+
+#define UVCG_STREAMING_HEADER_ATTR(cname, aname, bits)			\
+static ssize_t uvcg_streaming_header_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct uvcg_streaming_header *sh = to_uvcg_streaming_header(item); \
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = sh->item.ci_parent->ci_parent->ci_parent;		\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(sh->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_streaming_header_, cname, aname)
+
+UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, 8);
+UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, 8);
+UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, 8);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, 8);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, 8);
+
+#undef UVCG_STREAMING_HEADER_ATTR
+
+static struct configfs_attribute *uvcg_streaming_header_attrs[] = {
+	&uvcg_streaming_header_attr_bm_info,
+	&uvcg_streaming_header_attr_b_terminal_link,
+	&uvcg_streaming_header_attr_b_still_capture_method,
+	&uvcg_streaming_header_attr_b_trigger_support,
+	&uvcg_streaming_header_attr_b_trigger_usage,
+	NULL,
+};
+
+static const struct config_item_type uvcg_streaming_header_type = {
+	.ct_item_ops	= &uvcg_streaming_header_item_ops,
+	.ct_attrs	= uvcg_streaming_header_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_item
+*uvcg_streaming_header_make(struct config_group *group, const char *name)
+{
+	struct uvcg_streaming_header *h;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&h->formats);
+	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
+	h->desc.bDescriptorSubType	= UVC_VS_INPUT_HEADER;
+	h->desc.bTerminalLink		= 3;
+	h->desc.bControlSize		= UVCG_STREAMING_CONTROL_SIZE;
+
+	config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type);
+
+	return &h->item;
+}
+
+static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
+	.make_item		= uvcg_streaming_header_make,
+};
+
+static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_streaming_header_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "header",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/<mode>/<format>/<NAME>
+ */
+
+struct uvcg_frame {
+	struct config_item	item;
+	enum uvcg_format_type	fmt_type;
+	struct {
+		u8	b_length;
+		u8	b_descriptor_type;
+		u8	b_descriptor_subtype;
+		u8	b_frame_index;
+		u8	bm_capabilities;
+		u16	w_width;
+		u16	w_height;
+		u32	dw_min_bit_rate;
+		u32	dw_max_bit_rate;
+		u32	dw_max_video_frame_buffer_size;
+		u32	dw_default_frame_interval;
+		u8	b_frame_interval_type;
+	} __attribute__((packed)) frame;
+	u32 *dw_frame_interval;
+};
+
+static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
+{
+	return container_of(item, struct uvcg_frame, item);
+}
+
+#define UVCG_FRAME_ATTR(cname, aname, bits) \
+static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\
+{									\
+	struct uvcg_frame *f = to_uvcg_frame(item);			\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent;	\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", f->frame.cname);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+static ssize_t  uvcg_frame_##cname##_store(struct config_item *item,	\
+					   const char *page, size_t len)\
+{									\
+	struct uvcg_frame *f = to_uvcg_frame(item);			\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct uvcg_format *fmt;					\
+	struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
+	typeof(f->frame.cname) num;					\
+	int ret;							\
+									\
+	ret = kstrtou##bits(page, 0, &num);				\
+	if (ret)							\
+		return ret;						\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent;	\
+	opts = to_f_uvc_opts(opts_item);				\
+	fmt = to_uvcg_format(f->item.ci_parent);			\
+									\
+	mutex_lock(&opts->lock);					\
+	if (fmt->linked || opts->refcnt) {				\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	f->frame.cname = num;						\
+	ret = len;							\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	mutex_unlock(su_mutex);						\
+	return ret;							\
+}									\
+									\
+UVC_ATTR(uvcg_frame_, cname, aname);
+
+static ssize_t uvcg_frame_b_frame_index_show(struct config_item *item,
+					     char *page)
+{
+	struct uvcg_frame *f = to_uvcg_frame(item);
+	struct uvcg_format *fmt;
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct config_item *fmt_item;
+	struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;
+	int result;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	fmt_item = f->item.ci_parent;
+	fmt = to_uvcg_format(fmt_item);
+
+	if (!fmt->linked) {
+		result = -EBUSY;
+		goto out;
+	}
+
+	opts_item = fmt_item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", f->frame.b_frame_index);
+	mutex_unlock(&opts->lock);
+
+out:
+	mutex_unlock(su_mutex);
+	return result;
+}
+
+UVC_ATTR_RO(uvcg_frame_, b_frame_index, bFrameIndex);
+
+UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, 8);
+UVCG_FRAME_ATTR(w_width, wWidth, 16);
+UVCG_FRAME_ATTR(w_height, wHeight, 16);
+UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32);
+UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32);
+UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32);
+UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32);
+
+#undef UVCG_FRAME_ATTR
+
+static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item,
+						 char *page)
+{
+	struct uvcg_frame *frm = to_uvcg_frame(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &frm->item.ci_group->cg_subsys->su_mutex;
+	int result, i;
+	char *pg = page;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) {
+		result += sprintf(pg, "%u\n", frm->dw_frame_interval[i]);
+		pg = page + result;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+	return result;
+}
+
+static inline int __uvcg_count_frm_intrv(char *buf, void *priv)
+{
+	++*((int *)priv);
+	return 0;
+}
+
+static inline int __uvcg_fill_frm_intrv(char *buf, void *priv)
+{
+	u32 num, **interv;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &num);
+	if (ret)
+		return ret;
+
+	interv = priv;
+	**interv = num;
+	++*interv;
+
+	return 0;
+}
+
+static int __uvcg_iter_frm_intrv(const char *page, size_t len,
+				 int (*fun)(char *, void *), void *priv)
+{
+	/* sign, base 2 representation, newline, terminator */
+	char buf[1 + sizeof(u32) * 8 + 1 + 1];
+	const char *pg = page;
+	int i, ret;
+
+	if (!fun)
+		return -EINVAL;
+
+	while (pg - page < len) {
+		i = 0;
+		while (i < sizeof(buf) && (pg - page < len) &&
+				*pg != '\0' && *pg != '\n')
+			buf[i++] = *pg++;
+		if (i == sizeof(buf))
+			return -EINVAL;
+		while ((pg - page < len) && (*pg == '\0' || *pg == '\n'))
+			++pg;
+		buf[i] = '\0';
+		ret = fun(buf, priv);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item,
+						  const char *page, size_t len)
+{
+	struct uvcg_frame *ch = to_uvcg_frame(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct uvcg_format *fmt;
+	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;
+	int ret = 0, n = 0;
+	u32 *frm_intrv, *tmp;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = ch->item.ci_parent->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+	fmt = to_uvcg_format(ch->item.ci_parent);
+
+	mutex_lock(&opts->lock);
+	if (fmt->linked || opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n);
+	if (ret)
+		goto end;
+
+	tmp = frm_intrv = kcalloc(n, sizeof(u32), GFP_KERNEL);
+	if (!frm_intrv) {
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp);
+	if (ret) {
+		kfree(frm_intrv);
+		goto end;
+	}
+
+	kfree(ch->dw_frame_interval);
+	ch->dw_frame_interval = frm_intrv;
+	ch->frame.b_frame_interval_type = n;
+	sort(ch->dw_frame_interval, n, sizeof(*ch->dw_frame_interval),
+	     uvcg_config_compare_u32, NULL);
+	ret = len;
+
+end:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval);
+
+static struct configfs_attribute *uvcg_frame_attrs[] = {
+	&uvcg_frame_attr_b_frame_index,
+	&uvcg_frame_attr_bm_capabilities,
+	&uvcg_frame_attr_w_width,
+	&uvcg_frame_attr_w_height,
+	&uvcg_frame_attr_dw_min_bit_rate,
+	&uvcg_frame_attr_dw_max_bit_rate,
+	&uvcg_frame_attr_dw_max_video_frame_buffer_size,
+	&uvcg_frame_attr_dw_default_frame_interval,
+	&uvcg_frame_attr_dw_frame_interval,
+	NULL,
+};
+
+static const struct config_item_type uvcg_frame_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
+	.ct_attrs	= uvcg_frame_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_item *uvcg_frame_make(struct config_group *group,
+					   const char *name)
+{
+	struct uvcg_frame *h;
+	struct uvcg_format *fmt;
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return ERR_PTR(-ENOMEM);
+
+	h->frame.b_descriptor_type		= USB_DT_CS_INTERFACE;
+	h->frame.b_frame_index			= 1;
+	h->frame.w_width			= 640;
+	h->frame.w_height			= 360;
+	h->frame.dw_min_bit_rate		= 18432000;
+	h->frame.dw_max_bit_rate		= 55296000;
+	h->frame.dw_max_video_frame_buffer_size	= 460800;
+	h->frame.dw_default_frame_interval	= 666666;
+
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	fmt = to_uvcg_format(&group->cg_item);
+	if (fmt->type == UVCG_UNCOMPRESSED) {
+		h->frame.b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED;
+		h->fmt_type = UVCG_UNCOMPRESSED;
+	} else if (fmt->type == UVCG_MJPEG) {
+		h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG;
+		h->fmt_type = UVCG_MJPEG;
+	} else {
+		mutex_unlock(&opts->lock);
+		kfree(h);
+		return ERR_PTR(-EINVAL);
+	}
+	++fmt->num_frames;
+	mutex_unlock(&opts->lock);
+
+	config_item_init_type_name(&h->item, name, &uvcg_frame_type);
+
+	return &h->item;
+}
+
+static void uvcg_frame_drop(struct config_group *group, struct config_item *item)
+{
+	struct uvcg_format *fmt;
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	fmt = to_uvcg_format(&group->cg_item);
+	--fmt->num_frames;
+	mutex_unlock(&opts->lock);
+
+	config_item_put(item);
+}
+
+static void uvcg_format_set_indices(struct config_group *fmt)
+{
+	struct config_item *ci;
+	unsigned int i = 1;
+
+	list_for_each_entry(ci, &fmt->cg_children, ci_entry) {
+		struct uvcg_frame *frm;
+
+		if (ci->ci_type != &uvcg_frame_type)
+			continue;
+
+		frm = to_uvcg_frame(ci);
+		frm->frame.b_frame_index = i++;
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * streaming/uncompressed/<NAME>
+ */
+
+struct uvcg_uncompressed {
+	struct uvcg_format		fmt;
+	struct uvc_format_uncompressed	desc;
+};
+
+static struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item)
+{
+	return container_of(
+		container_of(to_config_group(item), struct uvcg_format, group),
+		struct uvcg_uncompressed, fmt);
+}
+
+static struct configfs_group_operations uvcg_uncompressed_group_ops = {
+	.make_item		= uvcg_frame_make,
+	.drop_item		= uvcg_frame_drop,
+};
+
+static ssize_t uvcg_uncompressed_guid_format_show(struct config_item *item,
+							char *page)
+{
+	struct uvcg_uncompressed *ch = to_uvcg_uncompressed(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat));
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return sizeof(ch->desc.guidFormat);
+}
+
+static ssize_t uvcg_uncompressed_guid_format_store(struct config_item *item,
+						   const char *page, size_t len)
+{
+	struct uvcg_uncompressed *ch = to_uvcg_uncompressed(item);
+	struct f_uvc_opts *opts;
+	struct config_item *opts_item;
+	struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
+	int ret;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	if (ch->fmt.linked || opts->refcnt) {
+		ret = -EBUSY;
+		goto end;
+	}
+
+	memcpy(ch->desc.guidFormat, page,
+	       min(sizeof(ch->desc.guidFormat), len));
+	ret = sizeof(ch->desc.guidFormat);
+
+end:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+UVC_ATTR(uvcg_uncompressed_, guid_format, guidFormat);
+
+#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, bits)			\
+static ssize_t uvcg_uncompressed_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct uvcg_uncompressed *u = to_uvcg_uncompressed(item);	\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_uncompressed_, cname, aname);
+
+#define UVCG_UNCOMPRESSED_ATTR(cname, aname, bits)			\
+static ssize_t uvcg_uncompressed_##cname##_show(			\
+	struct config_item *item, char *page)				\
+{									\
+	struct uvcg_uncompressed *u = to_uvcg_uncompressed(item);	\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+static ssize_t								\
+uvcg_uncompressed_##cname##_store(struct config_item *item,		\
+				    const char *page, size_t len)	\
+{									\
+	struct uvcg_uncompressed *u = to_uvcg_uncompressed(item);	\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int ret;							\
+	u8 num;								\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	if (u->fmt.linked || opts->refcnt) {				\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou8(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	u->desc.aname = num;						\
+	ret = len;							\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	mutex_unlock(su_mutex);						\
+	return ret;							\
+}									\
+									\
+UVC_ATTR(uvcg_uncompressed_, cname, aname);
+
+UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, 8);
+UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, 8);
+UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
+UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
+
+#undef UVCG_UNCOMPRESSED_ATTR
+#undef UVCG_UNCOMPRESSED_ATTR_RO
+
+static inline ssize_t
+uvcg_uncompressed_bma_controls_show(struct config_item *item, char *page)
+{
+	struct uvcg_uncompressed *unc = to_uvcg_uncompressed(item);
+	return uvcg_format_bma_controls_show(&unc->fmt, page);
+}
+
+static inline ssize_t
+uvcg_uncompressed_bma_controls_store(struct config_item *item,
+				     const char *page, size_t len)
+{
+	struct uvcg_uncompressed *unc = to_uvcg_uncompressed(item);
+	return uvcg_format_bma_controls_store(&unc->fmt, page, len);
+}
+
+UVC_ATTR(uvcg_uncompressed_, bma_controls, bmaControls);
+
+static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
+	&uvcg_uncompressed_attr_b_format_index,
+	&uvcg_uncompressed_attr_guid_format,
+	&uvcg_uncompressed_attr_b_bits_per_pixel,
+	&uvcg_uncompressed_attr_b_default_frame_index,
+	&uvcg_uncompressed_attr_b_aspect_ratio_x,
+	&uvcg_uncompressed_attr_b_aspect_ratio_y,
+	&uvcg_uncompressed_attr_bm_interface_flags,
+	&uvcg_uncompressed_attr_bma_controls,
+	NULL,
+};
+
+static const struct config_item_type uvcg_uncompressed_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
+	.ct_group_ops	= &uvcg_uncompressed_group_ops,
+	.ct_attrs	= uvcg_uncompressed_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_group *uvcg_uncompressed_make(struct config_group *group,
+						   const char *name)
+{
+	static char guid[] = {
+		'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00,
+		 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+	};
+	struct uvcg_uncompressed *h;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return ERR_PTR(-ENOMEM);
+
+	h->desc.bLength			= UVC_DT_FORMAT_UNCOMPRESSED_SIZE;
+	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
+	h->desc.bDescriptorSubType	= UVC_VS_FORMAT_UNCOMPRESSED;
+	memcpy(h->desc.guidFormat, guid, sizeof(guid));
+	h->desc.bBitsPerPixel		= 16;
+	h->desc.bDefaultFrameIndex	= 1;
+	h->desc.bAspectRatioX		= 0;
+	h->desc.bAspectRatioY		= 0;
+	h->desc.bmInterfaceFlags	= 0;
+	h->desc.bCopyProtect		= 0;
+
+	h->fmt.type = UVCG_UNCOMPRESSED;
+	config_group_init_type_name(&h->fmt.group, name,
+				    &uvcg_uncompressed_type);
+
+	return &h->fmt.group;
+}
+
+static struct configfs_group_operations uvcg_uncompressed_grp_ops = {
+	.make_group		= uvcg_uncompressed_make,
+};
+
+static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_uncompressed_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "uncompressed",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/mjpeg/<NAME>
+ */
+
+struct uvcg_mjpeg {
+	struct uvcg_format		fmt;
+	struct uvc_format_mjpeg		desc;
+};
+
+static struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
+{
+	return container_of(
+		container_of(to_config_group(item), struct uvcg_format, group),
+		struct uvcg_mjpeg, fmt);
+}
+
+static struct configfs_group_operations uvcg_mjpeg_group_ops = {
+	.make_item		= uvcg_frame_make,
+	.drop_item		= uvcg_frame_drop,
+};
+
+#define UVCG_MJPEG_ATTR_RO(cname, aname, bits)				\
+static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
+{									\
+	struct uvcg_mjpeg *u = to_uvcg_mjpeg(item);			\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_mjpeg_, cname, aname)
+
+#define UVCG_MJPEG_ATTR(cname, aname, bits)				\
+static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\
+{									\
+	struct uvcg_mjpeg *u = to_uvcg_mjpeg(item);			\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+static ssize_t								\
+uvcg_mjpeg_##cname##_store(struct config_item *item,			\
+			   const char *page, size_t len)		\
+{									\
+	struct uvcg_mjpeg *u = to_uvcg_mjpeg(item);			\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
+	int ret;							\
+	u8 num;								\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	if (u->fmt.linked || opts->refcnt) {				\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtou8(page, 0, &num);					\
+	if (ret)							\
+		goto end;						\
+									\
+	u->desc.aname = num;						\
+	ret = len;							\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	mutex_unlock(su_mutex);						\
+	return ret;							\
+}									\
+									\
+UVC_ATTR(uvcg_mjpeg_, cname, aname)
+
+UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, 8);
+UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
+UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, 8);
+UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
+UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
+UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
+
+#undef UVCG_MJPEG_ATTR
+#undef UVCG_MJPEG_ATTR_RO
+
+static inline ssize_t
+uvcg_mjpeg_bma_controls_show(struct config_item *item, char *page)
+{
+	struct uvcg_mjpeg *u = to_uvcg_mjpeg(item);
+	return uvcg_format_bma_controls_show(&u->fmt, page);
+}
+
+static inline ssize_t
+uvcg_mjpeg_bma_controls_store(struct config_item *item,
+				     const char *page, size_t len)
+{
+	struct uvcg_mjpeg *u = to_uvcg_mjpeg(item);
+	return uvcg_format_bma_controls_store(&u->fmt, page, len);
+}
+
+UVC_ATTR(uvcg_mjpeg_, bma_controls, bmaControls);
+
+static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
+	&uvcg_mjpeg_attr_b_format_index,
+	&uvcg_mjpeg_attr_b_default_frame_index,
+	&uvcg_mjpeg_attr_bm_flags,
+	&uvcg_mjpeg_attr_b_aspect_ratio_x,
+	&uvcg_mjpeg_attr_b_aspect_ratio_y,
+	&uvcg_mjpeg_attr_bm_interface_flags,
+	&uvcg_mjpeg_attr_bma_controls,
+	NULL,
+};
+
+static const struct config_item_type uvcg_mjpeg_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
+	.ct_group_ops	= &uvcg_mjpeg_group_ops,
+	.ct_attrs	= uvcg_mjpeg_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_group *uvcg_mjpeg_make(struct config_group *group,
+						   const char *name)
+{
+	struct uvcg_mjpeg *h;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return ERR_PTR(-ENOMEM);
+
+	h->desc.bLength			= UVC_DT_FORMAT_MJPEG_SIZE;
+	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
+	h->desc.bDescriptorSubType	= UVC_VS_FORMAT_MJPEG;
+	h->desc.bDefaultFrameIndex	= 1;
+	h->desc.bAspectRatioX		= 0;
+	h->desc.bAspectRatioY		= 0;
+	h->desc.bmInterfaceFlags	= 0;
+	h->desc.bCopyProtect		= 0;
+
+	h->fmt.type = UVCG_MJPEG;
+	config_group_init_type_name(&h->fmt.group, name,
+				    &uvcg_mjpeg_type);
+
+	return &h->fmt.group;
+}
+
+static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
+	.make_group		= uvcg_mjpeg_make,
+};
+
+static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_mjpeg_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "mjpeg",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/color_matching/default
+ */
+
+#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, bits)		\
+static ssize_t uvcg_default_color_matching_##cname##_show(		\
+	struct config_item *item, char *page)				\
+{									\
+	struct config_group *group = to_config_group(item);		\
+	struct f_uvc_opts *opts;					\
+	struct config_item *opts_item;					\
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
+	struct uvc_color_matching_descriptor *cd;			\
+	int result;							\
+									\
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
+									\
+	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;	\
+	opts = to_f_uvc_opts(opts_item);				\
+	cd = &opts->uvc_color_matching;					\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname));	\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+	return result;							\
+}									\
+									\
+UVC_ATTR_RO(uvcg_default_color_matching_, cname, aname)
+
+UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8);
+UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics,
+				 bTransferCharacteristics, 8);
+UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8);
+
+#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR
+
+static struct configfs_attribute *uvcg_default_color_matching_attrs[] = {
+	&uvcg_default_color_matching_attr_b_color_primaries,
+	&uvcg_default_color_matching_attr_b_transfer_characteristics,
+	&uvcg_default_color_matching_attr_b_matrix_coefficients,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_default_color_matching_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_color_matching_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/color_matching
+ */
+
+static const struct uvcg_config_group_type uvcg_color_matching_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "color_matching",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_color_matching_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/class/{fs|hs|ss}
+ */
+
+struct uvcg_streaming_class_group {
+	struct config_group group;
+	const char *name;
+};
+
+static inline struct uvc_descriptor_header
+***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o)
+{
+	struct uvcg_streaming_class_group *group =
+		container_of(i, struct uvcg_streaming_class_group,
+			     group.cg_item);
+
+	if (!strcmp(group->name, "fs"))
+		return &o->uvc_fs_streaming_cls;
+
+	if (!strcmp(group->name, "hs"))
+		return &o->uvc_hs_streaming_cls;
+
+	if (!strcmp(group->name, "ss"))
+		return &o->uvc_ss_streaming_cls;
+
+	return NULL;
+}
+
+enum uvcg_strm_type {
+	UVCG_HEADER = 0,
+	UVCG_FORMAT,
+	UVCG_FRAME
+};
+
+/*
+ * Iterate over a hierarchy of streaming descriptors' config items.
+ * The items are created by the user with configfs.
+ *
+ * It "processes" the header pointed to by @priv1, then for each format
+ * that follows the header "processes" the format itself and then for
+ * each frame inside a format "processes" the frame.
+ *
+ * As a "processing" function the @fun is used.
+ *
+ * __uvcg_iter_strm_cls() is used in two context: first, to calculate
+ * the amount of memory needed for an array of streaming descriptors
+ * and second, to actually fill the array.
+ *
+ * @h: streaming header pointer
+ * @priv2: an "inout" parameter (the caller might want to see the changes to it)
+ * @priv3: an "inout" parameter (the caller might want to see the changes to it)
+ * @fun: callback function for processing each level of the hierarchy
+ */
+static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h,
+	void *priv2, void *priv3,
+	int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type))
+{
+	struct uvcg_format_ptr *f;
+	struct config_group *grp;
+	struct config_item *item;
+	struct uvcg_frame *frm;
+	int ret, i, j;
+
+	if (!fun)
+		return -EINVAL;
+
+	i = j = 0;
+	ret = fun(h, priv2, priv3, 0, UVCG_HEADER);
+	if (ret)
+		return ret;
+	list_for_each_entry(f, &h->formats, entry) {
+		ret = fun(f->fmt, priv2, priv3, i++, UVCG_FORMAT);
+		if (ret)
+			return ret;
+		grp = &f->fmt->group;
+		list_for_each_entry(item, &grp->cg_children, ci_entry) {
+			frm = to_uvcg_frame(item);
+			ret = fun(frm, priv2, priv3, j++, UVCG_FRAME);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * Count how many bytes are needed for an array of streaming descriptors.
+ *
+ * @priv1: pointer to a header, format or frame
+ * @priv2: inout parameter, accumulated size of the array
+ * @priv3: inout parameter, accumulated number of the array elements
+ * @n: unused, this function's prototype must match @fun in __uvcg_iter_strm_cls
+ */
+static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
+			   enum uvcg_strm_type type)
+{
+	size_t *size = priv2;
+	size_t *count = priv3;
+
+	switch (type) {
+	case UVCG_HEADER: {
+		struct uvcg_streaming_header *h = priv1;
+
+		*size += sizeof(h->desc);
+		/* bmaControls */
+		*size += h->num_fmt * UVCG_STREAMING_CONTROL_SIZE;
+	}
+	break;
+	case UVCG_FORMAT: {
+		struct uvcg_format *fmt = priv1;
+
+		if (fmt->type == UVCG_UNCOMPRESSED) {
+			struct uvcg_uncompressed *u =
+				container_of(fmt, struct uvcg_uncompressed,
+					     fmt);
+
+			*size += sizeof(u->desc);
+		} else if (fmt->type == UVCG_MJPEG) {
+			struct uvcg_mjpeg *m =
+				container_of(fmt, struct uvcg_mjpeg, fmt);
+
+			*size += sizeof(m->desc);
+		} else {
+			return -EINVAL;
+		}
+	}
+	break;
+	case UVCG_FRAME: {
+		struct uvcg_frame *frm = priv1;
+		int sz = sizeof(frm->dw_frame_interval);
+
+		*size += sizeof(frm->frame);
+		*size += frm->frame.b_frame_interval_type * sz;
+	}
+	break;
+	}
+
+	++*count;
+
+	return 0;
+}
+
+/*
+ * Fill an array of streaming descriptors.
+ *
+ * @priv1: pointer to a header, format or frame
+ * @priv2: inout parameter, pointer into a block of memory
+ * @priv3: inout parameter, pointer to a 2-dimensional array
+ */
+static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
+			    enum uvcg_strm_type type)
+{
+	void **dest = priv2;
+	struct uvc_descriptor_header ***array = priv3;
+	size_t sz;
+
+	**array = *dest;
+	++*array;
+
+	switch (type) {
+	case UVCG_HEADER: {
+		struct uvc_input_header_descriptor *ihdr = *dest;
+		struct uvcg_streaming_header *h = priv1;
+		struct uvcg_format_ptr *f;
+
+		memcpy(*dest, &h->desc, sizeof(h->desc));
+		*dest += sizeof(h->desc);
+		sz = UVCG_STREAMING_CONTROL_SIZE;
+		list_for_each_entry(f, &h->formats, entry) {
+			memcpy(*dest, f->fmt->bmaControls, sz);
+			*dest += sz;
+		}
+		ihdr->bLength = sizeof(h->desc) + h->num_fmt * sz;
+		ihdr->bNumFormats = h->num_fmt;
+	}
+	break;
+	case UVCG_FORMAT: {
+		struct uvcg_format *fmt = priv1;
+
+		if (fmt->type == UVCG_UNCOMPRESSED) {
+			struct uvcg_uncompressed *u =
+				container_of(fmt, struct uvcg_uncompressed,
+					     fmt);
+
+			u->desc.bFormatIndex = n + 1;
+			u->desc.bNumFrameDescriptors = fmt->num_frames;
+			memcpy(*dest, &u->desc, sizeof(u->desc));
+			*dest += sizeof(u->desc);
+		} else if (fmt->type == UVCG_MJPEG) {
+			struct uvcg_mjpeg *m =
+				container_of(fmt, struct uvcg_mjpeg, fmt);
+
+			m->desc.bFormatIndex = n + 1;
+			m->desc.bNumFrameDescriptors = fmt->num_frames;
+			memcpy(*dest, &m->desc, sizeof(m->desc));
+			*dest += sizeof(m->desc);
+		} else {
+			return -EINVAL;
+		}
+	}
+	break;
+	case UVCG_FRAME: {
+		struct uvcg_frame *frm = priv1;
+		struct uvc_descriptor_header *h = *dest;
+
+		sz = sizeof(frm->frame);
+		memcpy(*dest, &frm->frame, sz);
+		*dest += sz;
+		sz = frm->frame.b_frame_interval_type *
+			sizeof(*frm->dw_frame_interval);
+		memcpy(*dest, frm->dw_frame_interval, sz);
+		*dest += sz;
+		if (frm->fmt_type == UVCG_UNCOMPRESSED)
+			h->bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(
+				frm->frame.b_frame_interval_type);
+		else if (frm->fmt_type == UVCG_MJPEG)
+			h->bLength = UVC_DT_FRAME_MJPEG_SIZE(
+				frm->frame.b_frame_interval_type);
+	}
+	break;
+	}
+
+	return 0;
+}
+
+static int uvcg_streaming_class_allow_link(struct config_item *src,
+					   struct config_item *target)
+{
+	struct config_item *streaming, *header;
+	struct f_uvc_opts *opts;
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct uvc_descriptor_header ***class_array, **cl_arr;
+	struct uvcg_streaming_header *target_hdr;
+	void *data, *data_save;
+	size_t size = 0, count = 0;
+	int ret = -EINVAL;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	streaming = src->ci_parent->ci_parent;
+	header = config_group_find_item(to_config_group(streaming), "header");
+	if (!header || target->ci_parent != header)
+		goto out;
+
+	opts = to_f_uvc_opts(streaming->ci_parent);
+
+	mutex_lock(&opts->lock);
+
+	class_array = __uvcg_get_stream_class_arr(src, opts);
+	if (!class_array || *class_array || opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	target_hdr = to_uvcg_streaming_header(target);
+	ret = __uvcg_iter_strm_cls(target_hdr, &size, &count, __uvcg_cnt_strm);
+	if (ret)
+		goto unlock;
+
+	count += 2; /* color_matching, NULL */
+	*class_array = kcalloc(count, sizeof(void *), GFP_KERNEL);
+	if (!*class_array) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	data = data_save = kzalloc(size, GFP_KERNEL);
+	if (!data) {
+		kfree(*class_array);
+		*class_array = NULL;
+		ret = -ENOMEM;
+		goto unlock;
+	}
+	cl_arr = *class_array;
+	ret = __uvcg_iter_strm_cls(target_hdr, &data, &cl_arr,
+				   __uvcg_fill_strm);
+	if (ret) {
+		kfree(*class_array);
+		*class_array = NULL;
+		/*
+		 * __uvcg_fill_strm() called from __uvcg_iter_stream_cls()
+		 * might have advanced the "data", so use a backup copy
+		 */
+		kfree(data_save);
+		goto unlock;
+	}
+	*cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching;
+
+	++target_hdr->linked;
+	ret = 0;
+
+unlock:
+	mutex_unlock(&opts->lock);
+out:
+	config_item_put(header);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+static void uvcg_streaming_class_drop_link(struct config_item *src,
+					  struct config_item *target)
+{
+	struct config_item *streaming, *header;
+	struct f_uvc_opts *opts;
+	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+	struct uvc_descriptor_header ***class_array;
+	struct uvcg_streaming_header *target_hdr;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	streaming = src->ci_parent->ci_parent;
+	header = config_group_find_item(to_config_group(streaming), "header");
+	if (!header || target->ci_parent != header)
+		goto out;
+
+	opts = to_f_uvc_opts(streaming->ci_parent);
+
+	mutex_lock(&opts->lock);
+
+	class_array = __uvcg_get_stream_class_arr(src, opts);
+	if (!class_array || !*class_array)
+		goto unlock;
+
+	if (opts->refcnt)
+		goto unlock;
+
+	target_hdr = to_uvcg_streaming_header(target);
+	--target_hdr->linked;
+	kfree(**class_array);
+	kfree(*class_array);
+	*class_array = NULL;
+
+unlock:
+	mutex_unlock(&opts->lock);
+out:
+	config_item_put(header);
+	mutex_unlock(su_mutex);
+}
+
+static struct configfs_item_operations uvcg_streaming_class_item_ops = {
+	.release	= uvcg_config_item_release,
+	.allow_link	= uvcg_streaming_class_allow_link,
+	.drop_link	= uvcg_streaming_class_drop_link,
+};
+
+static const struct config_item_type uvcg_streaming_class_type = {
+	.ct_item_ops	= &uvcg_streaming_class_item_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming/class
+ */
+
+static int uvcg_streaming_class_create_children(struct config_group *parent)
+{
+	static const char * const names[] = { "fs", "hs", "ss" };
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(names); ++i) {
+		struct uvcg_streaming_class_group *group;
+
+		group = kzalloc(sizeof(*group), GFP_KERNEL);
+		if (!group)
+			return -ENOMEM;
+
+		group->name = names[i];
+
+		config_group_init_type_name(&group->group, group->name,
+					    &uvcg_streaming_class_type);
+		configfs_add_default_group(&group->group, parent);
+	}
+
+	return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "class",
+	.create_children = uvcg_streaming_class_create_children,
+};
+
+/* -----------------------------------------------------------------------------
+ * streaming
+ */
+
+static ssize_t uvcg_default_streaming_b_interface_number_show(
+	struct config_item *item, char *page)
+{
+	struct config_group *group = to_config_group(item);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	int result = 0;
+
+	mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+	opts_item = item->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	result += sprintf(page, "%u\n", opts->streaming_interface);
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return result;
+}
+
+UVC_ATTR_RO(uvcg_default_streaming_, b_interface_number, bInterfaceNumber);
+
+static struct configfs_attribute *uvcg_default_streaming_attrs[] = {
+	&uvcg_default_streaming_attr_b_interface_number,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_streaming_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "streaming",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_streaming_header_grp_type,
+		&uvcg_uncompressed_grp_type,
+		&uvcg_mjpeg_grp_type,
+		&uvcg_color_matching_grp_type,
+		&uvcg_streaming_class_grp_type,
+		NULL,
+	},
+};
+
+/* -----------------------------------------------------------------------------
+ * UVC function
+ */
+
+static void uvc_func_item_release(struct config_item *item)
+{
+	struct f_uvc_opts *opts = to_f_uvc_opts(item);
+
+	uvcg_config_remove_children(to_config_group(item));
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations uvc_func_item_ops = {
+	.release	= uvc_func_item_release,
+};
+
+#define UVCG_OPTS_ATTR(cname, aname, limit)				\
+static ssize_t f_uvc_opts_##cname##_show(				\
+	struct config_item *item, char *page)				\
+{									\
+	struct f_uvc_opts *opts = to_f_uvc_opts(item);			\
+	int result;							\
+									\
+	mutex_lock(&opts->lock);					\
+	result = sprintf(page, "%u\n", opts->cname);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t								\
+f_uvc_opts_##cname##_store(struct config_item *item,			\
+			   const char *page, size_t len)		\
+{									\
+	struct f_uvc_opts *opts = to_f_uvc_opts(item);			\
+	unsigned int num;						\
+	int ret;							\
+									\
+	mutex_lock(&opts->lock);					\
+	if (opts->refcnt) {						\
+		ret = -EBUSY;						\
+		goto end;						\
+	}								\
+									\
+	ret = kstrtouint(page, 0, &num);				\
+	if (ret)							\
+		goto end;						\
+									\
+	if (num > limit) {						\
+		ret = -EINVAL;						\
+		goto end;						\
+	}								\
+	opts->cname = num;						\
+	ret = len;							\
+end:									\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+UVC_ATTR(f_uvc_opts_, cname, cname)
+
+UVCG_OPTS_ATTR(streaming_interval, streaming_interval, 16);
+UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, 3072);
+UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, 15);
+
+#undef UVCG_OPTS_ATTR
+
+static struct configfs_attribute *uvc_attrs[] = {
+	&f_uvc_opts_attr_streaming_interval,
+	&f_uvc_opts_attr_streaming_maxpacket,
+	&f_uvc_opts_attr_streaming_maxburst,
+	NULL,
+};
+
+static const struct uvcg_config_group_type uvc_func_type = {
+	.type = {
+		.ct_item_ops	= &uvc_func_item_ops,
+		.ct_attrs	= uvc_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_control_grp_type,
+		&uvcg_streaming_grp_type,
+		NULL,
+	},
+};
+
+int uvcg_attach_configfs(struct f_uvc_opts *opts)
+{
+	int ret;
+
+	config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name,
+				    &uvc_func_type.type);
+
+	ret = uvcg_config_create_children(&opts->func_inst.group,
+					  &uvc_func_type);
+	if (ret < 0)
+		config_group_put(&opts->func_inst.group);
+
+	return ret;
+}
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_configfs.h b/marvell/linux/drivers/usb/gadget/function/uvc_configfs.h
new file mode 100644
index 0000000..341391d
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_configfs.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * uvc_configfs.h
+ *
+ * Configfs support for the uvc function.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+#ifndef UVC_CONFIGFS_H
+#define UVC_CONFIGFS_H
+
+struct f_uvc_opts;
+
+int uvcg_attach_configfs(struct f_uvc_opts *opts);
+
+#endif /* UVC_CONFIGFS_H */
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_queue.c b/marvell/linux/drivers/usb/gadget/function/uvc_queue.c
new file mode 100644
index 0000000..cab1e30
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_queue.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	uvc_queue.c  --  USB Video Class driver - Buffers management
+ *
+ *	Copyright (C) 2005-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-common.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvc.h"
+
+/* ------------------------------------------------------------------------
+ * Video buffers queue management.
+ *
+ * Video queues is initialized by uvcg_queue_init(). The function performs
+ * basic initialization of the uvc_video_queue struct and never fails.
+ *
+ * Video buffers are managed by videobuf2. The driver uses a mutex to protect
+ * the videobuf2 queue operations by serializing calls to videobuf2 and a
+ * spinlock to protect the IRQ queue that holds the buffers to be processed by
+ * the driver.
+ */
+
+/* -----------------------------------------------------------------------------
+ * videobuf2 queue operations
+ */
+
+static int uvc_queue_setup(struct vb2_queue *vq,
+			   unsigned int *nbuffers, unsigned int *nplanes,
+			   unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+	struct uvc_video *video = container_of(queue, struct uvc_video, queue);
+
+	if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
+		*nbuffers = UVC_MAX_VIDEO_BUFFERS;
+
+	*nplanes = 1;
+
+	sizes[0] = video->imagesize;
+
+	return 0;
+}
+
+static int uvc_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf);
+
+	if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+	    vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
+		uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
+		return -EINVAL;
+	}
+
+	if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED))
+		return -ENODEV;
+
+	buf->state = UVC_BUF_STATE_QUEUED;
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+	if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		buf->bytesused = 0;
+	else
+		buf->bytesused = vb2_get_plane_payload(vb, 0);
+
+	return 0;
+}
+
+static void uvc_buffer_queue(struct vb2_buffer *vb)
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf);
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+
+	if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) {
+		list_add_tail(&buf->queue, &queue->irqqueue);
+	} else {
+		/* If the device is disconnected return the buffer to userspace
+		 * directly. The next QBUF call will fail with -ENODEV.
+		 */
+		buf->state = UVC_BUF_STATE_ERROR;
+		vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+	}
+
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+static const struct vb2_ops uvc_queue_qops = {
+	.queue_setup = uvc_queue_setup,
+	.buf_prepare = uvc_buffer_prepare,
+	.buf_queue = uvc_buffer_queue,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+		    struct mutex *lock)
+{
+	int ret;
+
+	queue->queue.type = type;
+	queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	queue->queue.drv_priv = queue;
+	queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
+	queue->queue.ops = &uvc_queue_qops;
+	queue->queue.lock = lock;
+	queue->queue.mem_ops = &vb2_vmalloc_memops;
+	queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
+				     | V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+	ret = vb2_queue_init(&queue->queue);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&queue->irqlock);
+	INIT_LIST_HEAD(&queue->irqqueue);
+	queue->flags = 0;
+
+	return 0;
+}
+
+/*
+ * Free the video buffers.
+ */
+void uvcg_free_buffers(struct uvc_video_queue *queue)
+{
+	vb2_queue_release(&queue->queue);
+}
+
+/*
+ * Allocate the video buffers.
+ */
+int uvcg_alloc_buffers(struct uvc_video_queue *queue,
+			      struct v4l2_requestbuffers *rb)
+{
+	int ret;
+
+	ret = vb2_reqbufs(&queue->queue, rb);
+
+	return ret ? ret : rb->count;
+}
+
+int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+	return vb2_querybuf(&queue->queue, buf);
+}
+
+int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+	unsigned long flags;
+	int ret;
+
+	ret = vb2_qbuf(&queue->queue, NULL, buf);
+	if (ret < 0)
+		return ret;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	ret = (queue->flags & UVC_QUEUE_PAUSED) != 0;
+	queue->flags &= ~UVC_QUEUE_PAUSED;
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+	return ret;
+}
+
+/*
+ * Dequeue a video buffer. If nonblocking is false, block until a buffer is
+ * available.
+ */
+int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
+			int nonblocking)
+{
+	return vb2_dqbuf(&queue->queue, buf, nonblocking);
+}
+
+/*
+ * Poll the video queue.
+ *
+ * This function implements video queue polling and is intended to be used by
+ * the device poll handler.
+ */
+__poll_t uvcg_queue_poll(struct uvc_video_queue *queue, struct file *file,
+			     poll_table *wait)
+{
+	return vb2_poll(&queue->queue, file, wait);
+}
+
+int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
+{
+	return vb2_mmap(&queue->queue, vma);
+}
+
+#ifndef CONFIG_MMU
+/*
+ * Get unmapped area.
+ *
+ * NO-MMU arch need this function to make mmap() work correctly.
+ */
+unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
+					   unsigned long pgoff)
+{
+	return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
+}
+#endif
+
+/*
+ * Cancel the video buffers queue.
+ *
+ * Cancelling the queue marks all buffers on the irq queue as erroneous,
+ * wakes them up and removes them from the queue.
+ *
+ * If the disconnect parameter is set, further calls to uvc_queue_buffer will
+ * fail with -ENODEV.
+ *
+ * This function acquires the irq spinlock and can be called from interrupt
+ * context.
+ */
+void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect)
+{
+	struct uvc_buffer *buf;
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	while (!list_empty(&queue->irqqueue)) {
+		buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+				       queue);
+		list_del(&buf->queue);
+		buf->state = UVC_BUF_STATE_ERROR;
+		vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	queue->buf_used = 0;
+
+	/* This must be protected by the irqlock spinlock to avoid race
+	 * conditions between uvc_queue_buffer and the disconnection event that
+	 * could result in an interruptible wait in uvc_dequeue_buffer. Do not
+	 * blindly replace this logic by checking for the UVC_DEV_DISCONNECTED
+	 * state outside the queue code.
+	 */
+	if (disconnect)
+		queue->flags |= UVC_QUEUE_DISCONNECTED;
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+/*
+ * Enable or disable the video buffers queue.
+ *
+ * The queue must be enabled before starting video acquisition and must be
+ * disabled after stopping it. This ensures that the video buffers queue
+ * state can be properly initialized before buffers are accessed from the
+ * interrupt handler.
+ *
+ * Enabling the video queue initializes parameters (such as sequence number,
+ * sync pattern, ...). If the queue is already enabled, return -EBUSY.
+ *
+ * Disabling the video queue cancels the queue and removes all buffers from
+ * the main queue.
+ *
+ * This function can't be called from interrupt context. Use
+ * uvcg_queue_cancel() instead.
+ */
+int uvcg_queue_enable(struct uvc_video_queue *queue, int enable)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	if (enable) {
+		ret = vb2_streamon(&queue->queue, queue->queue.type);
+		if (ret < 0)
+			return ret;
+
+		queue->sequence = 0;
+		queue->buf_used = 0;
+	} else {
+		ret = vb2_streamoff(&queue->queue, queue->queue.type);
+		if (ret < 0)
+			return ret;
+
+		spin_lock_irqsave(&queue->irqlock, flags);
+		INIT_LIST_HEAD(&queue->irqqueue);
+
+		/*
+		 * FIXME: We need to clear the DISCONNECTED flag to ensure that
+		 * applications will be able to queue buffers for the next
+		 * streaming run. However, clearing it here doesn't guarantee
+		 * that the device will be reconnected in the meantime.
+		 */
+		queue->flags &= ~UVC_QUEUE_DISCONNECTED;
+		spin_unlock_irqrestore(&queue->irqlock, flags);
+	}
+
+	return ret;
+}
+
+/* called with &queue_irqlock held.. */
+struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue,
+					  struct uvc_buffer *buf)
+{
+	struct uvc_buffer *nextbuf;
+
+	if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) &&
+	     buf->length != buf->bytesused) {
+		buf->state = UVC_BUF_STATE_QUEUED;
+		vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0);
+		return buf;
+	}
+
+	list_del(&buf->queue);
+	if (!list_empty(&queue->irqqueue))
+		nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+					   queue);
+	else
+		nextbuf = NULL;
+
+	buf->buf.field = V4L2_FIELD_NONE;
+	buf->buf.sequence = queue->sequence++;
+	buf->buf.vb2_buf.timestamp = ktime_get_ns();
+
+	vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused);
+	vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE);
+
+	return nextbuf;
+}
+
+struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue)
+{
+	struct uvc_buffer *buf = NULL;
+
+	if (!list_empty(&queue->irqqueue))
+		buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+				       queue);
+	else
+		queue->flags |= UVC_QUEUE_PAUSED;
+
+	return buf;
+}
+
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_queue.h b/marvell/linux/drivers/usb/gadget/function/uvc_queue.h
new file mode 100644
index 0000000..2f0fff7
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_queue.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _UVC_QUEUE_H_
+#define _UVC_QUEUE_H_
+
+#include <linux/list.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+
+#include <media/videobuf2-v4l2.h>
+
+struct file;
+struct mutex;
+
+/* Maximum frame size in bytes, for sanity checking. */
+#define UVC_MAX_FRAME_SIZE	(16*1024*1024)
+/* Maximum number of video buffers. */
+#define UVC_MAX_VIDEO_BUFFERS	32
+
+/* ------------------------------------------------------------------------
+ * Structures.
+ */
+
+enum uvc_buffer_state {
+	UVC_BUF_STATE_IDLE	= 0,
+	UVC_BUF_STATE_QUEUED	= 1,
+	UVC_BUF_STATE_ACTIVE	= 2,
+	UVC_BUF_STATE_DONE	= 3,
+	UVC_BUF_STATE_ERROR	= 4,
+};
+
+struct uvc_buffer {
+	struct vb2_v4l2_buffer buf;
+	struct list_head queue;
+
+	enum uvc_buffer_state state;
+	void *mem;
+	unsigned int length;
+	unsigned int bytesused;
+};
+
+#define UVC_QUEUE_DISCONNECTED		(1 << 0)
+#define UVC_QUEUE_DROP_INCOMPLETE	(1 << 1)
+#define UVC_QUEUE_PAUSED		(1 << 2)
+
+struct uvc_video_queue {
+	struct vb2_queue queue;
+
+	unsigned int flags;
+	__u32 sequence;
+
+	unsigned int buf_used;
+
+	spinlock_t irqlock;	/* Protects flags and irqqueue */
+	struct list_head irqqueue;
+};
+
+static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
+{
+	return vb2_is_streaming(&queue->queue);
+}
+
+int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+		    struct mutex *lock);
+
+void uvcg_free_buffers(struct uvc_video_queue *queue);
+
+int uvcg_alloc_buffers(struct uvc_video_queue *queue,
+		       struct v4l2_requestbuffers *rb);
+
+int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
+
+int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf);
+
+int uvcg_dequeue_buffer(struct uvc_video_queue *queue,
+			struct v4l2_buffer *buf, int nonblocking);
+
+__poll_t uvcg_queue_poll(struct uvc_video_queue *queue,
+			     struct file *file, poll_table *wait);
+
+int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma);
+
+#ifndef CONFIG_MMU
+unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue,
+					   unsigned long pgoff);
+#endif /* CONFIG_MMU */
+
+void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect);
+
+int uvcg_queue_enable(struct uvc_video_queue *queue, int enable);
+
+struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue,
+					  struct uvc_buffer *buf);
+
+struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue);
+
+#endif /* _UVC_QUEUE_H_ */
+
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.c b/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.c
new file mode 100644
index 0000000..495f0ec
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	uvc_v4l2.c  --  USB Video Class Gadget driver
+ *
+ *	Copyright (C) 2009-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/usb/g_uvc.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+
+#include "f_uvc.h"
+#include "uvc.h"
+#include "uvc_queue.h"
+#include "uvc_video.h"
+#include "uvc_v4l2.h"
+
+/* --------------------------------------------------------------------------
+ * Requests handling
+ */
+
+static int
+uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
+{
+	struct usb_composite_dev *cdev = uvc->func.config->cdev;
+	struct usb_request *req = uvc->control_req;
+
+	if (data->length < 0)
+		return usb_ep_set_halt(cdev->gadget->ep0);
+
+	req->length = min_t(unsigned int, uvc->event_length, data->length);
+	req->zero = data->length < uvc->event_length;
+
+	memcpy(req->buf, data->data, req->length);
+
+	return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
+}
+
+/* --------------------------------------------------------------------------
+ * V4L2 ioctls
+ */
+
+struct uvc_format {
+	u8 bpp;
+	u32 fcc;
+};
+
+static struct uvc_format uvc_formats[] = {
+	{ 16, V4L2_PIX_FMT_YUYV  },
+	{ 0,  V4L2_PIX_FMT_MJPEG },
+};
+
+static int
+uvc_v4l2_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct usb_composite_dev *cdev = uvc->func.config->cdev;
+
+	strlcpy(cap->driver, "g_uvc", sizeof(cap->driver));
+	strlcpy(cap->card, cdev->gadget->name, sizeof(cap->card));
+	strlcpy(cap->bus_info, dev_name(&cdev->gadget->dev),
+		sizeof(cap->bus_info));
+	return 0;
+}
+
+static int
+uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+
+	fmt->fmt.pix.pixelformat = video->fcc;
+	fmt->fmt.pix.width = video->width;
+	fmt->fmt.pix.height = video->height;
+	fmt->fmt.pix.field = V4L2_FIELD_NONE;
+	fmt->fmt.pix.bytesperline = video->bpp * video->width / 8;
+	fmt->fmt.pix.sizeimage = video->imagesize;
+	fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	fmt->fmt.pix.priv = 0;
+
+	return 0;
+}
+
+static int
+uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+	struct uvc_format *format;
+	unsigned int imagesize;
+	unsigned int bpl;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) {
+		format = &uvc_formats[i];
+		if (format->fcc == fmt->fmt.pix.pixelformat)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(uvc_formats)) {
+		uvcg_info(&uvc->func, "Unsupported format 0x%08x.\n",
+			  fmt->fmt.pix.pixelformat);
+		return -EINVAL;
+	}
+
+	bpl = format->bpp * fmt->fmt.pix.width / 8;
+	imagesize = bpl ? bpl * fmt->fmt.pix.height : fmt->fmt.pix.sizeimage;
+
+	video->fcc = format->fcc;
+	video->bpp = format->bpp;
+	video->width = fmt->fmt.pix.width;
+	video->height = fmt->fmt.pix.height;
+	video->imagesize = imagesize;
+
+	fmt->fmt.pix.field = V4L2_FIELD_NONE;
+	fmt->fmt.pix.bytesperline = bpl;
+	fmt->fmt.pix.sizeimage = imagesize;
+	fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	fmt->fmt.pix.priv = 0;
+
+	return 0;
+}
+
+static int
+uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+
+	if (b->type != video->queue.queue.type)
+		return -EINVAL;
+
+	return uvcg_alloc_buffers(&video->queue, b);
+}
+
+static int
+uvc_v4l2_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+
+	return uvcg_query_buffer(&video->queue, b);
+}
+
+static int
+uvc_v4l2_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+	int ret;
+
+	ret = uvcg_queue_buffer(&video->queue, b);
+	if (ret < 0)
+		return ret;
+
+	return uvcg_video_pump(video);
+}
+
+static int
+uvc_v4l2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+
+	return uvcg_dequeue_buffer(&video->queue, b, file->f_flags & O_NONBLOCK);
+}
+
+static int
+uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+	int ret;
+
+	if (type != video->queue.queue.type)
+		return -EINVAL;
+
+	/* Enable UVC video. */
+	ret = uvcg_video_enable(video, 1);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Complete the alternate setting selection setup phase now that
+	 * userspace is ready to provide video frames.
+	 */
+	uvc_function_setup_continue(uvc);
+	uvc->state = UVC_STATE_STREAMING;
+
+	return 0;
+}
+
+static int
+uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_video *video = &uvc->video;
+
+	if (type != video->queue.queue.type)
+		return -EINVAL;
+
+	return uvcg_video_enable(video, 0);
+}
+
+static int
+uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
+			 const struct v4l2_event_subscription *sub)
+{
+	if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
+		return -EINVAL;
+
+	return v4l2_event_subscribe(fh, sub, 2, NULL);
+}
+
+static int
+uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh,
+			   const struct v4l2_event_subscription *sub)
+{
+	return v4l2_event_unsubscribe(fh, sub);
+}
+
+static long
+uvc_v4l2_ioctl_default(struct file *file, void *fh, bool valid_prio,
+		       unsigned int cmd, void *arg)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+
+	switch (cmd) {
+	case UVCIOC_SEND_RESPONSE:
+		return uvc_send_response(uvc, arg);
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
+	.vidioc_querycap = uvc_v4l2_querycap,
+	.vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
+	.vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
+	.vidioc_reqbufs = uvc_v4l2_reqbufs,
+	.vidioc_querybuf = uvc_v4l2_querybuf,
+	.vidioc_qbuf = uvc_v4l2_qbuf,
+	.vidioc_dqbuf = uvc_v4l2_dqbuf,
+	.vidioc_streamon = uvc_v4l2_streamon,
+	.vidioc_streamoff = uvc_v4l2_streamoff,
+	.vidioc_subscribe_event = uvc_v4l2_subscribe_event,
+	.vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event,
+	.vidioc_default = uvc_v4l2_ioctl_default,
+};
+
+/* --------------------------------------------------------------------------
+ * V4L2
+ */
+
+static int
+uvc_v4l2_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_file_handle *handle;
+
+	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+	if (handle == NULL)
+		return -ENOMEM;
+
+	v4l2_fh_init(&handle->vfh, vdev);
+	v4l2_fh_add(&handle->vfh);
+
+	handle->device = &uvc->video;
+	file->private_data = &handle->vfh;
+
+	uvc_function_connect(uvc);
+	return 0;
+}
+
+static int
+uvc_v4l2_release(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+	struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
+	struct uvc_video *video = handle->device;
+
+	uvc_function_disconnect(uvc);
+
+	mutex_lock(&video->mutex);
+	uvcg_video_enable(video, 0);
+	uvcg_free_buffers(&video->queue);
+	mutex_unlock(&video->mutex);
+
+	file->private_data = NULL;
+	v4l2_fh_del(&handle->vfh);
+	v4l2_fh_exit(&handle->vfh);
+	kfree(handle);
+
+	return 0;
+}
+
+static int
+uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+
+	return uvcg_queue_mmap(&uvc->video.queue, vma);
+}
+
+static __poll_t
+uvc_v4l2_poll(struct file *file, poll_table *wait)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+
+	return uvcg_queue_poll(&uvc->video.queue, file, wait);
+}
+
+#ifndef CONFIG_MMU
+static unsigned long uvcg_v4l2_get_unmapped_area(struct file *file,
+		unsigned long addr, unsigned long len, unsigned long pgoff,
+		unsigned long flags)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_device *uvc = video_get_drvdata(vdev);
+
+	return uvcg_queue_get_unmapped_area(&uvc->video.queue, pgoff);
+}
+#endif
+
+const struct v4l2_file_operations uvc_v4l2_fops = {
+	.owner		= THIS_MODULE,
+	.open		= uvc_v4l2_open,
+	.release	= uvc_v4l2_release,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= uvc_v4l2_mmap,
+	.poll		= uvc_v4l2_poll,
+#ifndef CONFIG_MMU
+	.get_unmapped_area = uvcg_v4l2_get_unmapped_area,
+#endif
+};
+
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.h b/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.h
new file mode 100644
index 0000000..452d710
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_v4l2.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *	uvc_v4l2.h  --  USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009-2010
+ *		Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *		Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef __UVC_V4L2_H__
+#define __UVC_V4L2_H__
+
+extern const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops;
+extern const struct v4l2_file_operations uvc_v4l2_fops;
+
+#endif /* __UVC_V4L2_H__ */
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_video.c b/marvell/linux/drivers/usb/gadget/function/uvc_video.c
new file mode 100644
index 0000000..f9fad63
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_video.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *	uvc_video.c  --  USB Video Class Gadget driver
+ *
+ *	Copyright (C) 2009-2010
+ *	    Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/video.h>
+
+#include <media/v4l2-dev.h>
+
+#include "uvc.h"
+#include "uvc_queue.h"
+#include "uvc_video.h"
+
+/* --------------------------------------------------------------------------
+ * Video codecs
+ */
+
+static int
+uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
+		u8 *data, int len)
+{
+	data[0] = 2;
+	data[1] = UVC_STREAM_EOH | video->fid;
+
+	if (buf->bytesused - video->queue.buf_used <= len - 2)
+		data[1] |= UVC_STREAM_EOF;
+
+	return 2;
+}
+
+static int
+uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
+		u8 *data, int len)
+{
+	struct uvc_video_queue *queue = &video->queue;
+	unsigned int nbytes;
+	void *mem;
+
+	/* Copy video data to the USB buffer. */
+	mem = buf->mem + queue->buf_used;
+	nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
+
+	memcpy(data, mem, nbytes);
+	queue->buf_used += nbytes;
+
+	return nbytes;
+}
+
+static void
+uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
+		struct uvc_buffer *buf)
+{
+	void *mem = req->buf;
+	int len = video->req_size;
+	int ret;
+
+	/* Add a header at the beginning of the payload. */
+	if (video->payload_size == 0) {
+		ret = uvc_video_encode_header(video, buf, mem, len);
+		video->payload_size += ret;
+		mem += ret;
+		len -= ret;
+	}
+
+	/* Process video data. */
+	len = min((int)(video->max_payload_size - video->payload_size), len);
+	ret = uvc_video_encode_data(video, buf, mem, len);
+
+	video->payload_size += ret;
+	len -= ret;
+
+	req->length = video->req_size - len;
+	req->zero = video->payload_size == video->max_payload_size;
+
+	if (buf->bytesused == video->queue.buf_used) {
+		video->queue.buf_used = 0;
+		buf->state = UVC_BUF_STATE_DONE;
+		uvcg_queue_next_buffer(&video->queue, buf);
+		video->fid ^= UVC_STREAM_FID;
+
+		video->payload_size = 0;
+	}
+
+	if (video->payload_size == video->max_payload_size ||
+	    buf->bytesused == video->queue.buf_used)
+		video->payload_size = 0;
+}
+
+static void
+uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
+		struct uvc_buffer *buf)
+{
+	void *mem = req->buf;
+	int len = video->req_size;
+	int ret;
+
+	/* Add the header. */
+	ret = uvc_video_encode_header(video, buf, mem, len);
+	mem += ret;
+	len -= ret;
+
+	/* Process video data. */
+	ret = uvc_video_encode_data(video, buf, mem, len);
+	len -= ret;
+
+	req->length = video->req_size - len;
+
+	if (buf->bytesused == video->queue.buf_used) {
+		video->queue.buf_used = 0;
+		buf->state = UVC_BUF_STATE_DONE;
+		uvcg_queue_next_buffer(&video->queue, buf);
+		video->fid ^= UVC_STREAM_FID;
+	}
+}
+
+/* --------------------------------------------------------------------------
+ * Request handling
+ */
+
+static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
+{
+	int ret;
+
+	ret = usb_ep_queue(video->ep, req, GFP_ATOMIC);
+	if (ret < 0) {
+		uvcg_err(&video->uvc->func, "Failed to queue request (%d).\n",
+			 ret);
+
+		/* Isochronous endpoints can't be halted. */
+		if (usb_endpoint_xfer_bulk(video->ep->desc))
+			usb_ep_set_halt(video->ep);
+	}
+
+	return ret;
+}
+
+/*
+ * I somehow feel that synchronisation won't be easy to achieve here. We have
+ * three events that control USB requests submission:
+ *
+ * - USB request completion: the completion handler will resubmit the request
+ *   if a video buffer is available.
+ *
+ * - USB interface setting selection: in response to a SET_INTERFACE request,
+ *   the handler will start streaming if a video buffer is available and if
+ *   video is not currently streaming.
+ *
+ * - V4L2 buffer queueing: the driver will start streaming if video is not
+ *   currently streaming.
+ *
+ * Race conditions between those 3 events might lead to deadlocks or other
+ * nasty side effects.
+ *
+ * The "video currently streaming" condition can't be detected by the irqqueue
+ * being empty, as a request can still be in flight. A separate "queue paused"
+ * flag is thus needed.
+ *
+ * The paused flag will be set when we try to retrieve the irqqueue head if the
+ * queue is empty, and cleared when we queue a buffer.
+ *
+ * The USB request completion handler will get the buffer at the irqqueue head
+ * under protection of the queue spinlock. If the queue is empty, the streaming
+ * paused flag will be set. Right after releasing the spinlock a userspace
+ * application can queue a buffer. The flag will then cleared, and the ioctl
+ * handler will restart the video stream.
+ */
+static void
+uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct uvc_video *video = req->context;
+	struct uvc_video_queue *queue = &video->queue;
+	struct uvc_buffer *buf;
+	unsigned long flags;
+	int ret;
+
+	switch (req->status) {
+	case 0:
+		break;
+
+	case -ESHUTDOWN:	/* disconnect from host. */
+		uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
+		uvcg_queue_cancel(queue, 1);
+		goto requeue;
+
+	default:
+		uvcg_warn(&video->uvc->func,
+			  "VS request completed with status %d.\n",
+			  req->status);
+		uvcg_queue_cancel(queue, 0);
+		goto requeue;
+	}
+
+	spin_lock_irqsave(&video->queue.irqlock, flags);
+	buf = uvcg_queue_head(&video->queue);
+	if (buf == NULL) {
+		spin_unlock_irqrestore(&video->queue.irqlock, flags);
+		goto requeue;
+	}
+
+	video->encode(req, video, buf);
+
+	ret = uvcg_video_ep_queue(video, req);
+	spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+	if (ret < 0) {
+		uvcg_queue_cancel(queue, 0);
+		goto requeue;
+	}
+
+	return;
+
+requeue:
+	spin_lock_irqsave(&video->req_lock, flags);
+	list_add_tail(&req->list, &video->req_free);
+	spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
+static int
+uvc_video_free_requests(struct uvc_video *video)
+{
+	unsigned int i;
+
+	for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+		if (video->req[i]) {
+			usb_ep_free_request(video->ep, video->req[i]);
+			video->req[i] = NULL;
+		}
+
+		if (video->req_buffer[i]) {
+			kfree(video->req_buffer[i]);
+			video->req_buffer[i] = NULL;
+		}
+	}
+
+	INIT_LIST_HEAD(&video->req_free);
+	video->req_size = 0;
+	return 0;
+}
+
+static int
+uvc_video_alloc_requests(struct uvc_video *video)
+{
+	unsigned int req_size;
+	unsigned int i;
+	int ret = -ENOMEM;
+
+	BUG_ON(video->req_size);
+
+	req_size = video->ep->maxpacket
+		 * max_t(unsigned int, video->ep->maxburst, 1)
+		 * (video->ep->mult);
+
+	for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+		video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL);
+		if (video->req_buffer[i] == NULL)
+			goto error;
+
+		video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+		if (video->req[i] == NULL)
+			goto error;
+
+		video->req[i]->buf = video->req_buffer[i];
+		video->req[i]->length = 0;
+		video->req[i]->complete = uvc_video_complete;
+		video->req[i]->context = video;
+
+		list_add_tail(&video->req[i]->list, &video->req_free);
+	}
+
+	video->req_size = req_size;
+
+	return 0;
+
+error:
+	uvc_video_free_requests(video);
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Video streaming
+ */
+
+/*
+ * uvcg_video_pump - Pump video data into the USB requests
+ *
+ * This function fills the available USB requests (listed in req_free) with
+ * video data from the queued buffers.
+ */
+int uvcg_video_pump(struct uvc_video *video)
+{
+	struct uvc_video_queue *queue = &video->queue;
+	struct usb_request *req;
+	struct uvc_buffer *buf;
+	unsigned long flags;
+	int ret;
+
+	/* FIXME TODO Race between uvcg_video_pump and requests completion
+	 * handler ???
+	 */
+
+	while (1) {
+		/* Retrieve the first available USB request, protected by the
+		 * request lock.
+		 */
+		spin_lock_irqsave(&video->req_lock, flags);
+		if (list_empty(&video->req_free)) {
+			spin_unlock_irqrestore(&video->req_lock, flags);
+			return 0;
+		}
+		req = list_first_entry(&video->req_free, struct usb_request,
+					list);
+		list_del(&req->list);
+		spin_unlock_irqrestore(&video->req_lock, flags);
+
+		/* Retrieve the first available video buffer and fill the
+		 * request, protected by the video queue irqlock.
+		 */
+		spin_lock_irqsave(&queue->irqlock, flags);
+		buf = uvcg_queue_head(queue);
+		if (buf == NULL) {
+			spin_unlock_irqrestore(&queue->irqlock, flags);
+			break;
+		}
+
+		video->encode(req, video, buf);
+
+		/* Queue the USB request */
+		ret = uvcg_video_ep_queue(video, req);
+		spin_unlock_irqrestore(&queue->irqlock, flags);
+
+		if (ret < 0) {
+			uvcg_queue_cancel(queue, 0);
+			break;
+		}
+	}
+
+	spin_lock_irqsave(&video->req_lock, flags);
+	list_add_tail(&req->list, &video->req_free);
+	spin_unlock_irqrestore(&video->req_lock, flags);
+	return 0;
+}
+
+/*
+ * Enable or disable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video, int enable)
+{
+	unsigned int i;
+	int ret;
+
+	if (video->ep == NULL) {
+		uvcg_info(&video->uvc->func,
+			  "Video enable failed, device is uninitialized.\n");
+		return -ENODEV;
+	}
+
+	if (!enable) {
+		for (i = 0; i < UVC_NUM_REQUESTS; ++i)
+			if (video->req[i])
+				usb_ep_dequeue(video->ep, video->req[i]);
+
+		uvc_video_free_requests(video);
+		uvcg_queue_enable(&video->queue, 0);
+		return 0;
+	}
+
+	if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
+		return ret;
+
+	if ((ret = uvc_video_alloc_requests(video)) < 0)
+		return ret;
+
+	if (video->max_payload_size) {
+		video->encode = uvc_video_encode_bulk;
+		video->payload_size = 0;
+	} else
+		video->encode = uvc_video_encode_isoc;
+
+	return uvcg_video_pump(video);
+}
+
+/*
+ * Initialize the UVC video stream.
+ */
+int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
+{
+	INIT_LIST_HEAD(&video->req_free);
+	spin_lock_init(&video->req_lock);
+
+	video->uvc = uvc;
+	video->fcc = V4L2_PIX_FMT_YUYV;
+	video->bpp = 16;
+	video->width = 320;
+	video->height = 240;
+	video->imagesize = 320 * 240 * 2;
+
+	/* Initialize the video buffers queue. */
+	uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT,
+			&video->mutex);
+	return 0;
+}
+
diff --git a/marvell/linux/drivers/usb/gadget/function/uvc_video.h b/marvell/linux/drivers/usb/gadget/function/uvc_video.h
new file mode 100644
index 0000000..dff1210
--- /dev/null
+++ b/marvell/linux/drivers/usb/gadget/function/uvc_video.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *	uvc_video.h  --  USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009-2010
+ *		Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *		Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+#ifndef __UVC_VIDEO_H__
+#define __UVC_VIDEO_H__
+
+struct uvc_video;
+
+int uvcg_video_pump(struct uvc_video *video);
+
+int uvcg_video_enable(struct uvc_video *video, int enable);
+
+int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);
+
+#endif /* __UVC_VIDEO_H__ */