/* cyangadget.c - Linux USB Gadget driver file for the Cypress West Bridge
## ===========================
## Copyright (C) 2010  Cypress Semiconductor
##
## 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.
##
## 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.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Fifth Floor
## Boston, MA  02110-1301, USA.
## ===========================
*/

/*
 * Cypress West Bridge high/full speed usb device controller code
 * Based on the Netchip 2280 device controller by David Brownell
 * in the linux 2.6.10 kernel
 *
 * linux/drivers/usb/gadget/net2280.c
 */

/*
 * Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com)
 * Copyright (C) 2003 David Brownell
 *
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330
 * Boston, MA  02111-1307  USA
 */

#include "cyasgadget.h"

#define	CY_AS_DRIVER_DESC		"cypress west bridge usb gadget"
#define	CY_AS_DRIVER_VERSION		"REV B"
#define	DMA_ADDR_INVALID			(~(dma_addr_t)0)

static const char cy_as_driver_name[] = "cy_astoria_gadget";
static const char cy_as_driver_desc[] = CY_AS_DRIVER_DESC;

static const char cy_as_ep0name[] = "EP0";
static const char *cy_as_ep_names[] = {
	cy_as_ep0name, "EP1",
	"EP2", "EP3", "EP4", "EP5", "EP6", "EP7", "EP8",
	"EP9", "EP10", "EP11", "EP12", "EP13", "EP14", "EP15"
};

/* forward declarations */
static void
cyas_ep_reset(
	struct cyasgadget_ep *an_ep);

static int
cyasgadget_fifo_status(
	struct usb_ep *_ep);

static void
cyasgadget_stallcallback(
	cy_as_device_handle h,
	cy_as_return_status_t status,
	uint32_t tag,
	cy_as_funct_c_b_type cbtype,
	void *cbdata);

/* variables */
static cyasgadget	*cy_as_gadget_controller;

static int append_mtp;
module_param(append_mtp, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(append_mtp,
	"west bridge to append descriptors for mtp 0=no 1=yes");

static int msc_enum_bus_0;
module_param(msc_enum_bus_0, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msc_enum_bus_0,
	"west bridge to enumerate bus 0 as msc 0=no 1=yes");

static int msc_enum_bus_1;
module_param(msc_enum_bus_1, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(msc_enum_bus_1,
	"west bridge to enumerate bus 1 as msc 0=no 1=yes");

/* all Callbacks are placed in this subsection*/
static void cy_as_gadget_usb_event_callback(
					cy_as_device_handle h,
					cy_as_usb_event ev,
					void *evdata
					)
{
	cyasgadget  *cy_as_dev;
	#ifndef WESTBRIDGE_NDEBUG
	struct usb_ctrlrequest *ctrlreq;
	#endif

	/* cy_as_dev = container_of(h, cyasgadget, dev_handle); */
	cy_as_dev = cy_as_gadget_controller;
	switch (ev) {
	case cy_as_event_usb_suspend:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_suspend received\n");
		#endif
		cy_as_dev->driver->suspend(&cy_as_dev->gadget);
		break;

	case cy_as_event_usb_resume:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_resume received\n");
		#endif
		cy_as_dev->driver->resume(&cy_as_dev->gadget);
		break;

	case cy_as_event_usb_reset:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_reset received\n");
		#endif
		break;

	case cy_as_event_usb_speed_change:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_speed_change received\n");
		#endif
		break;

	case cy_as_event_usb_set_config:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_set_config received\n");
		#endif
		break;

	case cy_as_event_usb_setup_packet:
		#ifndef WESTBRIDGE_NDEBUG
		ctrlreq = (struct usb_ctrlrequest *)evdata;

		cy_as_hal_print_message("<1>_cy_as_event_usb_setup_packet "
							"received"
							"bRequestType=0x%x,"
							"bRequest=0x%x,"
							"wValue=x%x,"
							"wIndex=0x%x,"
							"wLength=0x%x,",
							ctrlreq->bRequestType,
							ctrlreq->bRequest,
							ctrlreq->wValue,
							ctrlreq->wIndex,
							ctrlreq->wLength
							);
		#endif
		cy_as_dev->outsetupreq = 0;
		if ((((uint8_t *)evdata)[0] & USB_DIR_IN) == USB_DIR_OUT)
			cy_as_dev->outsetupreq = 1;
		cy_as_dev->driver->setup(&cy_as_dev->gadget,
			(struct usb_ctrlrequest *)evdata);
		break;

	case cy_as_event_usb_status_packet:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_status_packet received\n");
		#endif
		break;

	case cy_as_event_usb_inquiry_before:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_inquiry_before received\n");
		#endif
		break;

	case cy_as_event_usb_inquiry_after:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_inquiry_after received\n");
		#endif
		break;

	case cy_as_event_usb_start_stop:
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"<1>_cy_as_event_usb_start_stop received\n");
		#endif
		break;

	default:
		break;
	}
}

static void cy_as_gadget_mtp_event_callback(
					cy_as_device_handle handle,
					cy_as_mtp_event evtype,
					void *evdata
					)
{

	cyasgadget *dev = cy_as_gadget_controller;
	(void) handle;

	switch (evtype) {
	case cy_as_mtp_send_object_complete:
		{
			cy_as_mtp_send_object_complete_data *send_obj_data =
				(cy_as_mtp_send_object_complete_data *) evdata;

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"<6>MTP EVENT: send_object_complete\n");
			cy_as_hal_print_message(
				"<6>_bytes sent = %d\n_send status = %d",
					send_obj_data->byte_count,
					send_obj_data->status);
			#endif

			dev->tmtp_send_complete_data.byte_count =
				send_obj_data->byte_count;
			dev->tmtp_send_complete_data.status =
				send_obj_data->status;
			dev->tmtp_send_complete_data.transaction_id =
				send_obj_data->transaction_id;
			dev->tmtp_send_complete = cy_true;
			break;
		}
	case cy_as_mtp_get_object_complete:
		{
			cy_as_mtp_get_object_complete_data *get_obj_data =
				(cy_as_mtp_get_object_complete_data *) evdata;

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"<6>MTP EVENT: get_object_complete\n");
			cy_as_hal_print_message(
				"<6>_bytes got = %d\n_get status = %d",
				get_obj_data->byte_count, get_obj_data->status);
			#endif

			dev->tmtp_get_complete_data.byte_count =
				get_obj_data->byte_count;
			dev->tmtp_get_complete_data.status =
				get_obj_data->status;
			dev->tmtp_get_complete = cy_true;
			break;
		}
	case cy_as_mtp_block_table_needed:
		{
			dev->tmtp_need_new_blk_tbl = cy_true;
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"<6>MTP EVENT: cy_as_mtp_block_table_needed\n");
			#endif
			break;
		}
	default:
		break;
	}
}

static void
cyasgadget_setupreadcallback(
		cy_as_device_handle h,
		cy_as_end_point_number_t ep,
		uint32_t count,
		void *buf,
		cy_as_return_status_t status)
{
	cyasgadget_ep  *an_ep;
	cyasgadget_req *an_req;
	cyasgadget	 *cy_as_dev;
	unsigned	   stopped;
	unsigned long	flags;
	(void)buf;

	cy_as_dev = cy_as_gadget_controller;
	if (cy_as_dev->driver == NULL)
		return;

	an_ep =  &cy_as_dev->an_gadget_ep[ep];
	spin_lock_irqsave(&cy_as_dev->lock, flags);
	stopped = an_ep->stopped;

#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message(
		"%s: ep=%d, count=%d, "
		"status=%d\n", __func__,  ep, count, status);
#endif

	an_req = list_entry(an_ep->queue.next,
		cyasgadget_req, queue);
	list_del_init(&an_req->queue);

	if (status == CY_AS_ERROR_SUCCESS)
		an_req->req.status = 0;
	else
		an_req->req.status = -status;
	an_req->req.actual = count;
	an_ep->stopped = 1;

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);

	an_ep->stopped = stopped;

}
/*called when the write of a setup packet has been completed*/
static void cyasgadget_setupwritecallback(
					cy_as_device_handle h,
					cy_as_end_point_number_t ep,
					uint32_t count,
					void *buf,
					cy_as_return_status_t status
					)
{
	cyasgadget_ep  *an_ep;
	cyasgadget_req *an_req;
	cyasgadget	 *cy_as_dev;
	unsigned	   stopped;
	unsigned long	flags;

	(void)buf;

	#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>%s called status=0x%x\n",
			__func__, status);
	#endif

	cy_as_dev = cy_as_gadget_controller;

	if (cy_as_dev->driver == NULL)
		return;

	an_ep =  &cy_as_dev->an_gadget_ep[ep];

	spin_lock_irqsave(&cy_as_dev->lock, flags);

	stopped = an_ep->stopped;

#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("setup_write_callback: ep=%d, "
		"count=%d, status=%d\n", ep, count, status);
#endif

	an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
	list_del_init(&an_req->queue);

	an_req->req.actual = count;
	an_req->req.status = 0;
	an_ep->stopped = 1;

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);

	an_ep->stopped = stopped;

}

/* called when a read operation has completed.*/
static void cyasgadget_readcallback(
					cy_as_device_handle h,
					cy_as_end_point_number_t ep,
					uint32_t count,
					void *buf,
					cy_as_return_status_t status
					)
{
	cyasgadget_ep  *an_ep;
	cyasgadget_req *an_req;
	cyasgadget	 *cy_as_dev;
	unsigned	   stopped;
	cy_as_return_status_t  ret;
	unsigned long	flags;

	(void)h;
	(void)buf;

	cy_as_dev = cy_as_gadget_controller;

	if (cy_as_dev->driver == NULL)
		return;

	an_ep =  &cy_as_dev->an_gadget_ep[ep];
	stopped = an_ep->stopped;

	#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: ep=%d, count=%d, status=%d\n",
			__func__, ep, count, status);
	#endif

	if (status == CY_AS_ERROR_CANCELED)
		return;

	spin_lock_irqsave(&cy_as_dev->lock, flags);

	an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
	list_del_init(&an_req->queue);

	if (status == CY_AS_ERROR_SUCCESS)
		an_req->req.status = 0;
	else
		an_req->req.status = -status;

	an_req->complete = 1;
	an_req->req.actual = count;
	an_ep->stopped = 1;

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);
	an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);

	an_ep->stopped = stopped;

	/* We need to call ReadAsync on this end-point
	 * again, so as to not miss any data packets. */
	if (!an_ep->stopped) {
		spin_lock_irqsave(&cy_as_dev->lock, flags);
		an_req = 0;
		if (!list_empty(&an_ep->queue))
			an_req = list_entry(an_ep->queue.next,
				cyasgadget_req, queue);

		spin_unlock_irqrestore(&cy_as_dev->lock, flags);

		if ((an_req) && (an_req->req.status == -EINPROGRESS)) {
			ret = cy_as_usb_read_data_async(cy_as_dev->dev_handle,
				an_ep->num, cy_false, an_req->req.length,
				an_req->req.buf, cyasgadget_readcallback);

			if (ret != CY_AS_ERROR_SUCCESS)
				cy_as_hal_print_message("<1>_cy_as_gadget: "
					"cy_as_usb_read_data_async failed "
					"with error code %d\n", ret);
			else
				an_req->req.status = -EALREADY;
		}
	}
}

/* function is called when a usb write operation has completed*/
static void cyasgadget_writecallback(
					cy_as_device_handle h,
					cy_as_end_point_number_t ep,
					uint32_t count,
					void *buf,
					cy_as_return_status_t status
					)
{
	cyasgadget_ep  *an_ep;
	cyasgadget_req *an_req;
	cyasgadget	 *cy_as_dev;
	unsigned	   stopped = 0;
	cy_as_return_status_t  ret;
	unsigned long	flags;

	(void)h;
	(void)buf;

	cy_as_dev = cy_as_gadget_controller;
	if (cy_as_dev->driver == NULL)
		return;

	an_ep =  &cy_as_dev->an_gadget_ep[ep];

	if (status == CY_AS_ERROR_CANCELED)
		return;

	#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: ep=%d, count=%d, status=%d\n",
			__func__, ep, count, status);
	#endif

	spin_lock_irqsave(&cy_as_dev->lock, flags);

	an_req = list_entry(an_ep->queue.next, cyasgadget_req, queue);
	list_del_init(&an_req->queue);
	an_req->req.actual = count;

	/* Verify the status value before setting req.status to zero */
	if (status == CY_AS_ERROR_SUCCESS)
		an_req->req.status = 0;
	else
		an_req->req.status = -status;

	an_ep->stopped = 1;

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
	an_ep->stopped = stopped;

	/* We need to call WriteAsync on this end-point again, so as to not
	   miss any data packets. */
	if (!an_ep->stopped) {
		spin_lock_irqsave(&cy_as_dev->lock, flags);
		an_req = 0;
		if (!list_empty(&an_ep->queue))
			an_req = list_entry(an_ep->queue.next,
				cyasgadget_req, queue);
		spin_unlock_irqrestore(&cy_as_dev->lock, flags);

		if ((an_req) && (an_req->req.status == -EINPROGRESS)) {
			ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
				an_ep->num, an_req->req.length, an_req->req.buf,
				cy_false, cyasgadget_writecallback);

			if (ret != CY_AS_ERROR_SUCCESS)
				cy_as_hal_print_message("<1>_cy_as_gadget: "
					"cy_as_usb_write_data_async "
					"failed with error code %d\n", ret);
			else
				an_req->req.status = -EALREADY;
		}
	}
}

static void cyasgadget_stallcallback(
						cy_as_device_handle h,
						cy_as_return_status_t status,
						uint32_t tag,
						cy_as_funct_c_b_type cbtype,
						void *cbdata
						)
{
	#ifndef WESTBRIDGE_NDEBUG
	if (status != CY_AS_ERROR_SUCCESS)
		cy_as_hal_print_message("<1>_set/_clear stall "
			"failed with status %d\n", status);
	#endif
}


/*******************************************************************/
/* All usb_ep_ops (cyasgadget_ep_ops) are placed in this subsection*/
/*******************************************************************/
static int cyasgadget_enable(
			struct usb_ep *_ep,
			const struct usb_endpoint_descriptor *desc
					)
{
	cyasgadget		*an_dev;
	cyasgadget_ep	*an_ep;
	u32			max, tmp;
	unsigned long	flags;

	an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
	if (!_ep || !desc || an_ep->desc || _ep->name == cy_as_ep0name
		|| desc->bDescriptorType != USB_DT_ENDPOINT)
		return -EINVAL;

	an_dev = an_ep->dev;
	if (!an_dev->driver || an_dev->gadget.speed == USB_SPEED_UNKNOWN)
		return -ESHUTDOWN;

	max = le16_to_cpu(desc->wMaxPacketSize) & 0x1fff;

	spin_lock_irqsave(&an_dev->lock, flags);
	_ep->maxpacket = max & 0x7ff;
	an_ep->desc = desc;

	/* ep_reset() has already been called */
	an_ep->stopped = 0;
	an_ep->out_overflow = 0;

	if (an_ep->cyepconfig.enabled != cy_true) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>_cy_as_gadget: "
			"cy_as_usb_end_point_config EP %s mismatch "
			"on enabled\n", an_ep->usb_ep_inst.name);
		#endif
		return -EINVAL;
	}

	tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK);
	an_ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC) ? 1 : 0;

	spin_unlock_irqrestore(&an_dev->lock, flags);

	switch (tmp) {
	case USB_ENDPOINT_XFER_ISOC:
		if (an_ep->cyepconfig.type != cy_as_usb_iso) {
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_end_point_config EP %s mismatch "
				"on type %d %d\n", an_ep->usb_ep_inst.name,
				an_ep->cyepconfig.type, cy_as_usb_iso);
			#endif
			return -EINVAL;
		}
		break;
	case USB_ENDPOINT_XFER_INT:
		if (an_ep->cyepconfig.type != cy_as_usb_int) {
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_end_point_config EP %s mismatch "
				"on type %d %d\n", an_ep->usb_ep_inst.name,
				an_ep->cyepconfig.type, cy_as_usb_int);
			#endif
			return -EINVAL;
		}
		break;
	default:
		if (an_ep->cyepconfig.type != cy_as_usb_bulk) {
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_end_point_config EP %s mismatch "
				"on type %d %d\n", an_ep->usb_ep_inst.name,
				an_ep->cyepconfig.type, cy_as_usb_bulk);
			#endif
			return -EINVAL;
		}
		break;
	}

	tmp = desc->bEndpointAddress;
	an_ep->is_in = (tmp & USB_DIR_IN) != 0;

	if ((an_ep->cyepconfig.dir == cy_as_usb_in) &&
	(!an_ep->is_in)) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>_cy_as_gadget: "
			"cy_as_usb_end_point_config EP %s mismatch "
			"on dir %d %d\n", an_ep->usb_ep_inst.name,
			an_ep->cyepconfig.dir, cy_as_usb_in);
		#endif
		return -EINVAL;
	} else if ((an_ep->cyepconfig.dir == cy_as_usb_out) &&
	(an_ep->is_in)) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>_cy_as_gadget: "
			"cy_as_usb_end_point_config EP %s mismatch "
			"on dir %d %d\n", an_ep->usb_ep_inst.name,
			an_ep->cyepconfig.dir, cy_as_usb_out);
		#endif
		return -EINVAL;
	}

	cy_as_usb_clear_stall(an_dev->dev_handle, an_ep->num,
		cyasgadget_stallcallback, 0);

	cy_as_hal_print_message("%s enabled %s (ep%d-%d) max %04x\n",
		__func__, _ep->name, an_ep->num, tmp, max);

	return 0;
}

static int cyasgadget_disable(
					struct usb_ep *_ep
					)
{
	cyasgadget_ep	*an_ep;
	unsigned long	flags;

	an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
	if (!_ep || !an_ep->desc || _ep->name == cy_as_ep0name)
		return -EINVAL;

	spin_lock_irqsave(&an_ep->dev->lock, flags);
	cyas_ep_reset(an_ep);

	spin_unlock_irqrestore(&an_ep->dev->lock, flags);
	return 0;
}

static struct usb_request *cyasgadget_alloc_request(
			struct usb_ep *_ep, gfp_t gfp_flags
			)
{
	cyasgadget_ep	*an_ep;
	cyasgadget_req	*an_req;

	if (!_ep)
		return NULL;

	an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);

	an_req = kzalloc(sizeof(cyasgadget_req), gfp_flags);
	if (!an_req)
		return NULL;

	an_req->req.dma = DMA_ADDR_INVALID;
	INIT_LIST_HEAD(&an_req->queue);

	return &an_req->req;
}

static void cyasgadget_free_request(
					struct usb_ep *_ep,
					struct usb_request *_req
					)
{
	cyasgadget_req *an_req;

	if (!_ep || !_req)
		return;

	an_req = container_of(_req, cyasgadget_req, req);

	kfree(an_req);
}

/* Load a packet into the fifo we use for usb IN transfers.
 * works for all endpoints. */
static int cyasgadget_queue(
				struct usb_ep *_ep,
				struct usb_request *_req,
				gfp_t gfp_flags
				)
{
	cyasgadget_req	*as_req;
	cyasgadget_ep	*as_ep;
	cyasgadget		*cy_as_dev;
	unsigned long	flags;
	cy_as_return_status_t  ret = 0;

	as_req = container_of(_req, cyasgadget_req, req);
	if (!_req || !_req->complete || !_req->buf
		|| !list_empty(&as_req->queue))
		return -EINVAL;

	as_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);

	if (!_ep || (!as_ep->desc && (as_ep->num != 0)))
		return -EINVAL;

	cy_as_dev = as_ep->dev;
	if (!cy_as_dev->driver ||
		cy_as_dev->gadget.speed == USB_SPEED_UNKNOWN)
		return -ESHUTDOWN;

	spin_lock_irqsave(&cy_as_dev->lock, flags);

	_req->status = -EINPROGRESS;
	_req->actual = 0;

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	/* Call Async functions */
	if (as_ep->is_in) {
		#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_write_data_async being called "
				"on ep %d\n", as_ep->num);
		#endif

		ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
			as_ep->num, _req->length, _req->buf,
			cy_false, cyasgadget_writecallback);
		if (ret != CY_AS_ERROR_SUCCESS)
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_write_data_async failed with "
				"error code %d\n", ret);
		else
			_req->status = -EALREADY;
	} else if (as_ep->num == 0) {
		/*
		ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
			as_ep->num, _req->length, _req->buf, cy_false,
			cyasgadget_setupwritecallback);

		if (ret != CY_AS_ERROR_SUCCESS)
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_write_data_async failed with error "
				"code %d\n", ret);
		*/
		if ((cy_as_dev->outsetupreq) && (_req->length)) {
			#ifndef WESTBRIDGE_NDEBUG
				cy_as_hal_print_message("<1>_cy_as_gadget: "
					"cy_as_usb_read_data_async "
					"being called on ep %d\n",
					as_ep->num);
			#endif

			ret = cy_as_usb_read_data_async (
				cy_as_dev->dev_handle, as_ep->num,
				cy_true, _req->length, _req->buf,
				cyasgadget_setupreadcallback);

			if (ret != CY_AS_ERROR_SUCCESS)
				cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_read_data_async failed with "
				"error code %d\n", ret);

		} else {
			#ifndef WESTBRIDGE_NDEBUG
				cy_as_hal_print_message("<1>_cy_as_gadget: "
					"cy_as_usb_write_data_async "
					"being called on ep %d\n",
					as_ep->num);
			#endif

			ret = cy_as_usb_write_data_async(cy_as_dev->dev_handle,
			as_ep->num, _req->length, _req->buf, cy_false,
			cyasgadget_setupwritecallback);

			if (ret != CY_AS_ERROR_SUCCESS)
				cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_write_data_async failed with "
				"error code %d\n", ret);
		}

	} else if (list_empty(&as_ep->queue)) {
		#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_read_data_async being called since "
				"ep queue empty%d\n", ret);
		#endif

		ret = cy_as_usb_read_data_async(cy_as_dev->dev_handle,
			as_ep->num, cy_false, _req->length, _req->buf,
			cyasgadget_readcallback);
		if (ret != CY_AS_ERROR_SUCCESS)
			cy_as_hal_print_message("<1>_cy_as_gadget: "
				"cy_as_usb_read_data_async failed with error "
				"code %d\n", ret);
		else
			_req->status = -EALREADY;
	}

	spin_lock_irqsave(&cy_as_dev->lock, flags);

	if (as_req)
		list_add_tail(&as_req->queue, &as_ep->queue);

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	return 0;
}

/* dequeue request */
static int cyasgadget_dequeue(
				struct usb_ep *_ep,
				struct usb_request *_req
				)
{
	cyasgadget_ep	*an_ep;
	cyasgadget		*dev;
	an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
	dev = an_ep->dev;

	#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	cy_as_usb_cancel_async(dev->dev_handle, an_ep->num);

	return 0;
}

static int cyasgadget_set_halt(
				struct usb_ep *_ep,
				int value
				)
{
	cyasgadget_ep	*an_ep;
	int			retval = 0;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	an_ep = container_of(_ep, cyasgadget_ep, usb_ep_inst);
	if (!_ep || (!an_ep->desc && an_ep->num != 0))
		return -EINVAL;

	if (!an_ep->dev->driver || an_ep->dev->gadget.speed ==
		USB_SPEED_UNKNOWN)
		return -ESHUTDOWN;

	if (an_ep->desc /* not ep0 */ &&
	(an_ep->desc->bmAttributes & 0x03) == USB_ENDPOINT_XFER_ISOC)
		return -EINVAL;

	if (!list_empty(&an_ep->queue))
		retval = -EAGAIN;
	else if (an_ep->is_in && value &&
		cyasgadget_fifo_status(_ep) != 0)
			retval = -EAGAIN;
	else {
		if (value) {
			cy_as_usb_set_stall(an_ep->dev->dev_handle,
				an_ep->num, cyasgadget_stallcallback, 0);
		} else {
			cy_as_usb_clear_stall(an_ep->dev->dev_handle,
				an_ep->num, cyasgadget_stallcallback, 0);
		}
	}

	return retval;
}

static int cyasgadget_fifo_status(
				struct usb_ep *_ep
				)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	return 0;
}

static void cyasgadget_fifo_flush(
				struct usb_ep *_ep
				)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif
}

static struct usb_ep_ops cyasgadget_ep_ops = {
	.enable		= cyasgadget_enable,
	.disable	= cyasgadget_disable,
	.alloc_request	= cyasgadget_alloc_request,
	.free_request	= cyasgadget_free_request,
	.queue		= cyasgadget_queue,
	.dequeue	= cyasgadget_dequeue,
	.set_halt	= cyasgadget_set_halt,
	.fifo_status	= cyasgadget_fifo_status,
	.fifo_flush	= cyasgadget_fifo_flush,
};

/*************************************************************/
/*This subsection contains all usb_gadget_ops cyasgadget_ops */
/*************************************************************/
static int cyasgadget_get_frame(
				struct usb_gadget *_gadget
				)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif
	return 0;
}

static int cyasgadget_wakeup(
					struct usb_gadget *_gadget
					)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif
	return 0;
}

static int cyasgadget_set_selfpowered(
					struct usb_gadget *_gadget,
					int value
					)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif
	return 0;
}

static int cyasgadget_pullup(
					struct usb_gadget *_gadget,
					int is_on
					)
{
	struct cyasgadget  *cy_as_dev;
	unsigned long   flags;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	if (!_gadget)
		return -ENODEV;

	cy_as_dev = container_of(_gadget, cyasgadget, gadget);

	spin_lock_irqsave(&cy_as_dev->lock, flags);
	cy_as_dev->softconnect = (is_on != 0);
	if (is_on)
		cy_as_usb_connect(cy_as_dev->dev_handle, 0, 0);
	else
		cy_as_usb_disconnect(cy_as_dev->dev_handle, 0, 0);

	spin_unlock_irqrestore(&cy_as_dev->lock, flags);

	return 0;
}

static int cyasgadget_ioctl(
					struct usb_gadget *_gadget,
					unsigned code,
					unsigned long param
					)
{
	int err = 0;
	int retval = 0;
	int ret_stat = 0;
	cyasgadget *dev = cy_as_gadget_controller;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called, code=%d, param=%ld\n",
		__func__, code, param);
	#endif
	/*
	 * extract the type and number bitfields, and don't decode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(code) != CYASGADGET_IOC_MAGIC) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s, bad magic number = 0x%x\n",
			__func__, _IOC_TYPE(code));
		#endif
		return -ENOTTY;
	}

	if (_IOC_NR(code) > CYASGADGET_IOC_MAXNR) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s, bad ioctl code = 0x%x\n",
			__func__, _IOC_NR(code));
		#endif
		return -ENOTTY;
	}

	/*
	 * the direction is a bitmask, and VERIFY_WRITE catches R/W
	 * transfers. `Type' is user-oriented, while
	 * access_ok is kernel-oriented, so the concept of "read" and
	 * "write" is reversed
	 */
	if (_IOC_DIR(code) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE,
			(void __user *)param, _IOC_SIZE(code));
	else if (_IOC_DIR(code) & _IOC_WRITE)
		err =  !access_ok(VERIFY_READ,
			(void __user *)param, _IOC_SIZE(code));

	if (err) {
		cy_as_hal_print_message("%s, bad ioctl dir = 0x%x\n",
			__func__, _IOC_DIR(code));
		return -EFAULT;
	}

	switch (code) {
	case CYASGADGET_GETMTPSTATUS:
		{
		cy_as_gadget_ioctl_tmtp_status *usr_d =
			(cy_as_gadget_ioctl_tmtp_status *)param;

		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: got CYASGADGET_GETMTPSTATUS\n",
			__func__);
		#endif

		retval = __put_user(dev->tmtp_send_complete,
			(uint32_t __user *)(&(usr_d->tmtp_send_complete)));
		retval = __put_user(dev->tmtp_get_complete,
			(uint32_t __user *)(&(usr_d->tmtp_get_complete)));
		retval = __put_user(dev->tmtp_need_new_blk_tbl,
			(uint32_t __user *)(&(usr_d->tmtp_need_new_blk_tbl)));

		if (copy_to_user((&(usr_d->tmtp_send_complete_data)),
			(&(dev->tmtp_send_complete_data)),
			sizeof(cy_as_gadget_ioctl_send_object)))
			return -EFAULT;

		if (copy_to_user((&(usr_d->tmtp_get_complete_data)),
			(&(dev->tmtp_get_complete_data)),
			sizeof(cy_as_gadget_ioctl_get_object)))
			return -EFAULT;
		break;
		}
	case CYASGADGET_CLEARTMTPSTATUS:
		{
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s got CYASGADGET_CLEARTMTPSTATUS\n",
			__func__);
		#endif

		dev->tmtp_send_complete = 0;
		dev->tmtp_get_complete = 0;
		dev->tmtp_need_new_blk_tbl = 0;

		break;
		}
	case CYASGADGET_INITSOJ:
		{
		cy_as_gadget_ioctl_i_s_o_j_d k_d;
		cy_as_gadget_ioctl_i_s_o_j_d *usr_d =
			(cy_as_gadget_ioctl_i_s_o_j_d *)param;
		cy_as_mtp_block_table blk_table;
		struct scatterlist sg;
		char *alloc_filename;
		struct file *file_to_allocate;

		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s got CYASGADGET_INITSOJ\n",
			__func__);
		#endif

		memset(&blk_table, 0, sizeof(blk_table));

		/* Get user argument structure  */
		if (copy_from_user(&k_d, usr_d,
			sizeof(cy_as_gadget_ioctl_i_s_o_j_d)))
			return -EFAULT;

		/* better use fixed size buff*/
		alloc_filename = kmalloc(k_d.name_length + 1, GFP_KERNEL);
		if (alloc_filename == NULL)
			return -ENOMEM;

		/* get the filename */
		if (copy_from_user(alloc_filename, k_d.file_name,
			k_d.name_length + 1)) {
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("%s: CYASGADGET_INITSOJ, "
				"copy file name from user space failed\n",
				__func__);
			#endif
			kfree(alloc_filename);
			return -EFAULT;
		}

		file_to_allocate = filp_open(alloc_filename, O_RDWR, 0);

		if (!IS_ERR(file_to_allocate)) {

			struct address_space *mapping =
				file_to_allocate->f_mapping;
			const struct address_space_operations *a_ops =
				mapping->a_ops;
			struct inode *inode = mapping->host;
			struct inode *alloc_inode =
				file_to_allocate->f_path.dentry->d_inode;
			uint32_t num_clusters = 0;
			struct buffer_head bh;
			struct kstat stat;
			int nr_pages = 0;
			int ret_stat = 0;

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("%s: fhandle is OK, "
				"calling vfs_getattr\n", __func__);
			#endif

			ret_stat = vfs_getattr(file_to_allocate->f_path.mnt,
				file_to_allocate->f_path.dentry, &stat);

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("%s: returned from "
				"vfs_getattr() stat->blksize=0x%lx\n",
				__func__, stat.blksize);
			#endif

			/* TODO:  get this from disk properties
			 * (from blockdevice)*/
			#define SECTOR_SIZE 512
			if (stat.blksize != 0) {
				num_clusters = (k_d.num_bytes) / SECTOR_SIZE;

				if (((k_d.num_bytes) % SECTOR_SIZE) != 0)
						num_clusters++;
			} else {
				goto initsoj_safe_exit;
			}

			bh.b_state = 0;
			bh.b_blocknr = 0;
			/* block size is arbitrary , we'll use sector size*/
			bh.b_size = SECTOR_SIZE;



			/* clear dirty pages in page cache
			 * (if were any allocated) */
			nr_pages = (k_d.num_bytes) / (PAGE_CACHE_SIZE);

			if (((k_d.num_bytes) % (PAGE_CACHE_SIZE)) != 0)
				nr_pages++;

			#ifndef WESTBRIDGE_NDEBUG
			/*check out how many pages where actually allocated */
			if (mapping->nrpages != nr_pages)
				cy_as_hal_print_message("%s mpage_cleardirty "
					"mapping->nrpages %d != num_pages %d\n",
					__func__, (int) mapping->nrpages,
					nr_pages);

				cy_as_hal_print_message("%s: calling "
					"mpage_cleardirty() "
					"for %d pages\n", __func__, nr_pages);
			#endif

			ret_stat = mpage_cleardirty(mapping, nr_pages);

			/*fill up the the block table from the addr mapping  */
			if (a_ops->bmap) {
				int8_t blk_table_idx = -1;
				uint32_t file_block_idx = 0;
				uint32_t last_blk_addr_map = 0,
					curr_blk_addr_map = 0;

				#ifndef WESTBRIDGE_NDEBUG
				if (alloc_inode->i_bytes == 0)
						cy_as_hal_print_message(
						"%s: alloc_inode->ibytes =0\n",
						__func__);
				#endif

				/* iterate through the list of
				 * blocks (not clusters)*/
				for (file_block_idx = 0;
					file_block_idx < num_clusters
					/*inode->i_bytes*/; file_block_idx++) {

					/* returns starting sector number */
					curr_blk_addr_map =
						a_ops->bmap(mapping,
							file_block_idx);

					/*no valid mapping*/
					if (curr_blk_addr_map == 0) {
						#ifndef WESTBRIDGE_NDEBUG
						cy_as_hal_print_message(
							"%s:hit invalid "
							"mapping\n", __func__);
						#endif
						break;
					} else if (curr_blk_addr_map !=
						(last_blk_addr_map + 1) ||
						(blk_table.num_blocks
						[blk_table_idx] == 65535)) {

						/* next table entry */
						blk_table_idx++;
						/* starting sector of a
						 * scattered cluster*/
						blk_table.start_blocks
							[blk_table_idx] =
							curr_blk_addr_map;
						/* ++ num of blocks in cur
						 * table entry*/
						blk_table.
						num_blocks[blk_table_idx]++;

						#ifndef WESTBRIDGE_NDEBUG
						if (file_block_idx != 0)
							cy_as_hal_print_message(
							 "<*> next table "
							 "entry:%d required\n",
							 blk_table_idx);
						#endif
					} else {
						/*add contiguous block*/
						blk_table.num_blocks
						[blk_table_idx]++;
					} /*if (curr_blk_addr_map == 0)*/

					last_blk_addr_map = curr_blk_addr_map;
				} /* end for (file_block_idx = 0; file_block_idx
				< inode->i_bytes;) */

				#ifndef WESTBRIDGE_NDEBUG
				/*print result for verification*/
				{
					int i;
					cy_as_hal_print_message(
						"%s: print block table "
						"mapping:\n",
						__func__);
					for (i = 0; i <= blk_table_idx; i++) {
						cy_as_hal_print_message(
						"<1> %d 0x%x 0x%x\n", i,
						blk_table.start_blocks[i],
						blk_table.num_blocks[i]);
					}
				}
				#endif

				/* copy the block table to user
				 * space (for debug purposes) */
				retval = __put_user(
					blk_table.start_blocks[blk_table_idx],
					(uint32_t __user *)
						(&(usr_d->blk_addr_p)));

				retval = __put_user(
					blk_table.num_blocks[blk_table_idx],
					(uint32_t __user *)
						(&(usr_d->blk_count_p)));

				blk_table_idx++;
				retval = __put_user(blk_table_idx,
					(uint32_t __user *)
						(&(usr_d->item_count)));

			} /*end if (a_ops->bmap)*/

			filp_close(file_to_allocate, NULL);

			dev->tmtp_send_complete = 0;
			dev->tmtp_need_new_blk_tbl = 0;

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"%s: calling cy_as_mtp_init_send_object()\n",
				__func__);
			#endif
			sg_init_one(&sg, &blk_table, sizeof(blk_table));
			ret_stat = cy_as_mtp_init_send_object(dev->dev_handle,
				(cy_as_mtp_block_table *)&sg,
				k_d.num_bytes, 0, 0);
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("%s: returned from "
				"cy_as_mtp_init_send_object()\n", __func__);
			#endif

		}
		#ifndef WESTBRIDGE_NDEBUG
		else {
			cy_as_hal_print_message(
				"%s: failed to allocate the file %s\n",
				__func__, alloc_filename);
		} /* end if (file_to_allocate)*/
		#endif
		kfree(alloc_filename);
initsoj_safe_exit:
			ret_stat = 0;
			retval = __put_user(ret_stat,
				(uint32_t __user *)(&(usr_d->ret_val)));

			break;
		}
	case CYASGADGET_INITGOJ:
		{
		cy_as_gadget_ioctl_i_g_o_j_d k_d;
		cy_as_gadget_ioctl_i_g_o_j_d *usr_d =
			(cy_as_gadget_ioctl_i_g_o_j_d *)param;
		cy_as_mtp_block_table blk_table;
		struct scatterlist sg;
		char *map_filename;
		struct file *file_to_map;

		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message(
			"%s: got CYASGADGET_INITGOJ\n",
				__func__);
		#endif

		memset(&blk_table, 0, sizeof(blk_table));

		/* Get user argument sturcutre */
		if (copy_from_user(&k_d, usr_d,
			sizeof(cy_as_gadget_ioctl_i_g_o_j_d)))
				return -EFAULT;

		map_filename = kmalloc(k_d.name_length + 1, GFP_KERNEL);
		if (map_filename == NULL)
			return -ENOMEM;
		if (copy_from_user(map_filename, k_d.file_name,
			k_d.name_length + 1)) {
			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message("%s: copy file name from "
				"user space failed\n", __func__);
			#endif
			kfree(map_filename);
			return -EFAULT;
		}

		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<*>%s: opening %s for kernel "
			"mode access map\n", __func__, map_filename);
		#endif
		file_to_map = filp_open(map_filename, O_RDWR, 0);
		if (file_to_map) {
			struct address_space *mapping = file_to_map->f_mapping;
			const struct address_space_operations
				*a_ops = mapping->a_ops;
			struct inode *inode = mapping->host;

			int8_t blk_table_idx = -1;
			uint32_t file_block_idx = 0;
			uint32_t last_blk_addr_map = 0, curr_blk_addr_map = 0;

			/*verify operation exists*/
			if (a_ops->bmap) {
				#ifndef WESTBRIDGE_NDEBUG
				cy_as_hal_print_message(
					"<*>%s: bmap found, i_bytes=0x%x, "
					"i_size=0x%x, i_blocks=0x%x\n",
					__func__, inode->i_bytes,
					(unsigned int) inode->i_size,
					(unsigned int) inode->i_blocks);
				#endif

				k_d.num_bytes = inode->i_size;

				#ifndef WESTBRIDGE_NDEBUG
				cy_as_hal_print_message(
					"<*>%s: k_d.num_bytes=0x%x\n",
					__func__, k_d.num_bytes);
				#endif

				for (file_block_idx = 0;
					file_block_idx < inode->i_size;
					file_block_idx++) {
					curr_blk_addr_map =
						a_ops->bmap(mapping,
							file_block_idx);

					if (curr_blk_addr_map == 0) {
						/*no valid mapping*/
						#ifndef WESTBRIDGE_NDEBUG
						cy_as_hal_print_message(
							"%s: no valid "
							"mapping\n", __func__);
						#endif
						break;
					} else if (curr_blk_addr_map !=
					(last_blk_addr_map + 1)) {
						/*non-contiguous break*/
						blk_table_idx++;
						blk_table.start_blocks
							[blk_table_idx] =
							curr_blk_addr_map;
						blk_table.num_blocks
							[blk_table_idx]++;
						#ifndef WESTBRIDGE_NDEBUG
						cy_as_hal_print_message(
							"%s: found non-"
							"contiguous break",
							__func__);
						#endif
					} else {
						/*add contiguous block*/
						blk_table.num_blocks
							[blk_table_idx]++;
					}
					last_blk_addr_map = curr_blk_addr_map;
				}

				/*print result for verification*/
				#ifndef WESTBRIDGE_NDEBUG
				{
					int i = 0;

					for (i = 0; i <= blk_table_idx; i++) {
						cy_as_hal_print_message(
						"%s %d 0x%x 0x%x\n",
						__func__, i,
						blk_table.start_blocks[i],
						blk_table.num_blocks[i]);
					}
				}
				#endif
			} else {
				#ifndef WESTBRIDGE_NDEBUG
				cy_as_hal_print_message(
					"%s: could not find "
					"a_ops->bmap\n", __func__);
				#endif
				return -EFAULT;
			}

			filp_close(file_to_map, NULL);

			dev->tmtp_get_complete = 0;
			dev->tmtp_need_new_blk_tbl = 0;

			ret_stat = __put_user(
				blk_table.start_blocks[blk_table_idx],
				(uint32_t __user *)(&(usr_d->blk_addr_p)));

			ret_stat = __put_user(
				blk_table.num_blocks[blk_table_idx],
				(uint32_t __user *)(&(usr_d->blk_count_p)));

			sg_init_one(&sg, &blk_table, sizeof(blk_table));

			#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"%s: calling cy_as_mtp_init_get_object() "
				"start=0x%x, num =0x%x, tid=0x%x, "
				"num_bytes=0x%x\n",
				__func__,
				blk_table.start_blocks[0],
				blk_table.num_blocks[0],
				k_d.tid,
				k_d.num_bytes);
			#endif

			ret_stat = cy_as_mtp_init_get_object(
				dev->dev_handle,
				(cy_as_mtp_block_table *)&sg,
				k_d.num_bytes, k_d.tid, 0, 0);
			if (ret_stat != CY_AS_ERROR_SUCCESS) {
					#ifndef WESTBRIDGE_NDEBUG
					cy_as_hal_print_message(
						"%s: cy_as_mtp_init_get_object "
						"failed ret_stat=0x%x\n",
						__func__, ret_stat);
					#endif
			}
		}
		#ifndef WESTBRIDGE_NDEBUG
		else {
				cy_as_hal_print_message(
					"%s: failed to open file %s\n",
					__func__, map_filename);
		}
		#endif
		kfree(map_filename);

		ret_stat = 0;
		retval = __put_user(ret_stat, (uint32_t __user *)
			(&(usr_d->ret_val)));
		break;
		}
	case CYASGADGET_CANCELSOJ:
		{
		cy_as_gadget_ioctl_cancel *usr_d =
			(cy_as_gadget_ioctl_cancel *)param;

		#ifndef WESTBRIDGE_NDEBUG
			cy_as_hal_print_message(
				"%s: got CYASGADGET_CANCELSOJ\n",
				__func__);
		#endif

		ret_stat = cy_as_mtp_cancel_send_object(dev->dev_handle, 0, 0);

		retval = __put_user(ret_stat, (uint32_t __user *)
			(&(usr_d->ret_val)));
		break;
		}
	case CYASGADGET_CANCELGOJ:
		{
		cy_as_gadget_ioctl_cancel *usr_d =
			(cy_as_gadget_ioctl_cancel *)param;

		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: got CYASGADGET_CANCELGOJ\n",
			__func__);
		#endif

		ret_stat = cy_as_mtp_cancel_get_object(dev->dev_handle, 0, 0);

		retval = __put_user(ret_stat,
			(uint32_t __user *)(&(usr_d->ret_val)));
		break;
		}
	default:
		{
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: unknown ioctl received: %d\n",
			__func__, code);

		cy_as_hal_print_message("%s: known codes:\n"
			"CYASGADGET_GETMTPSTATUS=%d\n"
			"CYASGADGET_CLEARTMTPSTATUS=%d\n"
			"CYASGADGET_INITSOJ=%d\n"
			"CYASGADGET_INITGOJ=%d\n"
			"CYASGADGET_CANCELSOJ=%d\n"
			"CYASGADGET_CANCELGOJ=%d\n",
			__func__,
			CYASGADGET_GETMTPSTATUS,
			CYASGADGET_CLEARTMTPSTATUS,
			CYASGADGET_INITSOJ,
			CYASGADGET_INITGOJ,
			CYASGADGET_CANCELSOJ,
			CYASGADGET_CANCELGOJ);
		#endif
		break;
		}
	}

	return 0;
}

static const struct usb_gadget_ops cyasgadget_ops = {
	.get_frame		 = cyasgadget_get_frame,
	.wakeup		 = cyasgadget_wakeup,
	.set_selfpowered = cyasgadget_set_selfpowered,
	.pullup		 = cyasgadget_pullup,
	.ioctl	   = cyasgadget_ioctl,
};


/* keeping it simple:
 * - one bus driver, initted first;
 * - one function driver, initted second
 *
 * most of the work to support multiple controllers would
 * be to associate this gadget driver with all of them, or
 * perhaps to bind specific drivers to specific devices.
 */

static void cyas_ep_reset(
				cyasgadget_ep *an_ep
				)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	an_ep->desc = NULL;
	INIT_LIST_HEAD(&an_ep->queue);

	an_ep->stopped = 0;
	an_ep->is_in   = 0;
	an_ep->is_iso  = 0;
	an_ep->usb_ep_inst.maxpacket = ~0;
	an_ep->usb_ep_inst.ops = &cyasgadget_ep_ops;
}

static void cyas_usb_reset(
				cyasgadget *cy_as_dev
				)
{
	cy_as_return_status_t ret;
	cy_as_usb_enum_control config;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_device *dev_p = (cy_as_device *)cy_as_dev->dev_handle;

	cy_as_hal_print_message("<1>%s called mtp_firmware=0x%x\n",
		__func__, dev_p->is_mtp_firmware);
	#endif

	ret = cy_as_misc_release_resource(cy_as_dev->dev_handle,
		cy_as_bus_u_s_b);
	if (ret != CY_AS_ERROR_SUCCESS && ret !=
		CY_AS_ERROR_RESOURCE_NOT_OWNED) {
		cy_as_hal_print_message("<1>_cy_as_gadget: cannot "
			"release usb resource: failed with error code %d\n",
			ret);
		return;
	}

	cy_as_dev->gadget.speed = USB_SPEED_HIGH;

	ret = cy_as_usb_start(cy_as_dev->dev_handle, 0, 0);
	if (ret != CY_AS_ERROR_SUCCESS) {
		cy_as_hal_print_message("<1>_cy_as_gadget: "
			"cy_as_usb_start failed with error code %d\n",
			ret);
		return;
	}
	/* P port will do enumeration, not West Bridge */
	config.antioch_enumeration = cy_false;
	/*  1  2  : 1-BUS_NUM , 2:Storage_device number, SD - is bus 1*/

	/* TODO: add module param to enumerate mass storage */
	config.mass_storage_interface = 0;

	if (append_mtp) {
		ret = cy_as_mtp_start(cy_as_dev->dev_handle,
			cy_as_gadget_mtp_event_callback, 0, 0);
		if (ret == CY_AS_ERROR_SUCCESS)  {
			cy_as_hal_print_message("MTP start passed, enumerating "
				"MTP interface\n");
			config.mtp_interface = append_mtp;
			/*Do not enumerate NAND storage*/
			config.devices_to_enumerate[0][0] = cy_false;

			/*enumerate SD storage as MTP*/
			config.devices_to_enumerate[1][0] = cy_true;
		}
	} else {
		cy_as_hal_print_message("MTP start not attempted, not "
			"enumerating MTP interface\n");
		config.mtp_interface = 0;
		/* enumerate mass storage based on module parameters */
		config.devices_to_enumerate[0][0] = msc_enum_bus_0;
		config.devices_to_enumerate[1][0] = msc_enum_bus_1;
	}

	ret = cy_as_usb_set_enum_config(cy_as_dev->dev_handle,
		&config, 0, 0);
	if (ret != CY_AS_ERROR_SUCCESS) {
		cy_as_hal_print_message("<1>_cy_as_gadget: "
			"cy_as_usb_set_enum_config failed with error "
			"code %d\n", ret);
		return;
	}

	cy_as_usb_set_physical_configuration(cy_as_dev->dev_handle, 1);

}

static void cyas_usb_reinit(
				cyasgadget *cy_as_dev
				)
{
	int index = 0;
	cyasgadget_ep *an_ep_p;
	cy_as_return_status_t ret;
	cy_as_device *dev_p = (cy_as_device *)cy_as_dev->dev_handle;

	INIT_LIST_HEAD(&cy_as_dev->gadget.ep_list);

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called, is_mtp_firmware = "
		"0x%x\n", __func__, dev_p->is_mtp_firmware);
	#endif

	/* Init the end points */
	for (index = 1; index <= 15; index++) {
		an_ep_p = &cy_as_dev->an_gadget_ep[index];
		cyas_ep_reset(an_ep_p);
		an_ep_p->usb_ep_inst.name = cy_as_ep_names[index];
		an_ep_p->dev = cy_as_dev;
		an_ep_p->num = index;
		memset(&an_ep_p->cyepconfig, 0, sizeof(an_ep_p->cyepconfig));

		/* EP0, EPs 2,4,6,8 need not be added */
		if ((index <= 8) && (index % 2 == 0) &&
			(!dev_p->is_mtp_firmware)) {
			/* EP0 is 64 and EPs 2,4,6,8 not allowed */
			cy_as_dev->an_gadget_ep[index].fifo_size = 0;
		} else {
			if (index == 1)
				an_ep_p->fifo_size = 64;
			else
				an_ep_p->fifo_size = 512;
			list_add_tail(&an_ep_p->usb_ep_inst.ep_list,
				&cy_as_dev->gadget.ep_list);
		}
	}
	/* need to setendpointconfig before usb connect, this is not
	 * quite compatible with gadget methodology (ep_enable called
	 * by gadget after connect), therefore need to set config in
	 * initialization and verify compatibility in ep_enable,
	 * kick up error otherwise*/
	an_ep_p = &cy_as_dev->an_gadget_ep[3];
	an_ep_p->cyepconfig.enabled = cy_true;
	an_ep_p->cyepconfig.dir = cy_as_usb_out;
	an_ep_p->cyepconfig.type = cy_as_usb_bulk;
	an_ep_p->cyepconfig.size = 0;
	an_ep_p->cyepconfig.physical = 1;
	ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
		3, &an_ep_p->cyepconfig);
	if (ret != CY_AS_ERROR_SUCCESS) {
		cy_as_hal_print_message("cy_as_usb_set_end_point_config "
			"failed with error code %d\n", ret);
	}

	cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 3, 0, 0);

	an_ep_p = &cy_as_dev->an_gadget_ep[5];
	an_ep_p->cyepconfig.enabled = cy_true;
	an_ep_p->cyepconfig.dir = cy_as_usb_in;
	an_ep_p->cyepconfig.type = cy_as_usb_bulk;
	an_ep_p->cyepconfig.size = 0;
	an_ep_p->cyepconfig.physical = 2;
	ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
		5, &an_ep_p->cyepconfig);
	if (ret != CY_AS_ERROR_SUCCESS) {
		cy_as_hal_print_message("cy_as_usb_set_end_point_config "
			"failed with error code %d\n", ret);
	}

	cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 5, 0, 0);

	an_ep_p = &cy_as_dev->an_gadget_ep[9];
	an_ep_p->cyepconfig.enabled = cy_true;
	an_ep_p->cyepconfig.dir = cy_as_usb_in;
	an_ep_p->cyepconfig.type = cy_as_usb_bulk;
	an_ep_p->cyepconfig.size = 0;
	an_ep_p->cyepconfig.physical = 4;
	ret = cy_as_usb_set_end_point_config(an_ep_p->dev->dev_handle,
		9, &an_ep_p->cyepconfig);
	if (ret != CY_AS_ERROR_SUCCESS) {
		cy_as_hal_print_message("cy_as_usb_set_end_point_config "
			"failed with error code %d\n", ret);
	}

	cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 9, 0, 0);

	if (dev_p->mtp_count != 0) {
		/* these need to be set for compatibility with
		 * the gadget_enable logic */
		an_ep_p = &cy_as_dev->an_gadget_ep[2];
		an_ep_p->cyepconfig.enabled = cy_true;
		an_ep_p->cyepconfig.dir = cy_as_usb_out;
		an_ep_p->cyepconfig.type = cy_as_usb_bulk;
		an_ep_p->cyepconfig.size = 0;
		an_ep_p->cyepconfig.physical = 0;
		cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 2, 0, 0);

		an_ep_p = &cy_as_dev->an_gadget_ep[6];
		an_ep_p->cyepconfig.enabled = cy_true;
		an_ep_p->cyepconfig.dir = cy_as_usb_in;
		an_ep_p->cyepconfig.type = cy_as_usb_bulk;
		an_ep_p->cyepconfig.size = 0;
		an_ep_p->cyepconfig.physical = 0;
		cy_as_usb_set_stall(an_ep_p->dev->dev_handle, 6, 0, 0);
	}

	cyas_ep_reset(&cy_as_dev->an_gadget_ep[0]);
	cy_as_dev->an_gadget_ep[0].usb_ep_inst.name = cy_as_ep_names[0];
	cy_as_dev->an_gadget_ep[0].dev = cy_as_dev;
	cy_as_dev->an_gadget_ep[0].num = 0;
	cy_as_dev->an_gadget_ep[0].fifo_size = 64;

	cy_as_dev->an_gadget_ep[0].usb_ep_inst.maxpacket = 64;
	cy_as_dev->gadget.ep0 = &cy_as_dev->an_gadget_ep[0].usb_ep_inst;
	cy_as_dev->an_gadget_ep[0].stopped = 0;
	INIT_LIST_HEAD(&cy_as_dev->gadget.ep0->ep_list);
}

static void cyas_ep0_start(
				cyasgadget *dev
				)
{
	cy_as_return_status_t ret;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	ret = cy_as_usb_register_callback(dev->dev_handle,
		cy_as_gadget_usb_event_callback);
	if (ret != CY_AS_ERROR_SUCCESS) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: cy_as_usb_register_callback "
			"failed with error code %d\n", __func__, ret);
		#endif
		return;
	}

	ret = cy_as_usb_commit_config(dev->dev_handle, 0, 0);
	if (ret != CY_AS_ERROR_SUCCESS) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: cy_as_usb_commit_config "
			"failed with error code %d\n", __func__, ret);
		#endif
		return;
	}

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("%s: cy_as_usb_commit_config "
		"message sent\n", __func__);
	#endif

	ret = cy_as_usb_connect(dev->dev_handle, 0, 0);
	if (ret != CY_AS_ERROR_SUCCESS) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s: cy_as_usb_connect failed "
			"with error code %d\n", __func__, ret);
		#endif
		return;
	}

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("%s: cy_as_usb_connect message "
		"sent\n", __func__);
	#endif
}

/*
 * When a driver is successfully registered, it will receive
 * control requests including set_configuration(), which enables
 * non-control requests.  then usb traffic follows until a
 * disconnect is reported.  then a host may connect again, or
 * the driver might get unbound.
 */
int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
		int (*bind)(struct usb_gadget *))
{
	cyasgadget *dev = cy_as_gadget_controller;
	int		retval;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called driver=0x%x\n",
		__func__, (unsigned int) driver);
	#endif

	/* insist on high speed support from the driver, since
	* "must not be used in normal operation"
	*/
	if (!driver
		|| !bind
		|| !driver->unbind
		|| !driver->setup)
		return -EINVAL;

	if (!dev)
		return -ENODEV;

	if (dev->driver)
		return -EBUSY;

	/* hook up the driver ... */
	dev->softconnect = 1;
	driver->driver.bus = NULL;
	dev->driver = driver;
	dev->gadget.dev.driver = &driver->driver;

	/* Do the needful */
	cyas_usb_reset(dev); /* External usb */
	cyas_usb_reinit(dev); /* Internal */

	retval = bind(&dev->gadget);
	if (retval) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("%s bind to driver %s --> %d\n",
			__func__, driver->driver.name, retval);
		#endif

		dev->driver = NULL;
		dev->gadget.dev.driver = NULL;
		return retval;
	}

	/* ... then enable host detection and ep0; and we're ready
	* for set_configuration as well as eventual disconnect.
	*/
	cyas_ep0_start(dev);

	return 0;
}
EXPORT_SYMBOL(usb_gadget_probe_driver);

static void cyasgadget_nuke(
							cyasgadget_ep *an_ep
							)
{
	cyasgadget	*dev = cy_as_gadget_controller;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	cy_as_usb_cancel_async(dev->dev_handle, an_ep->num);
	an_ep->stopped = 1;

	while (!list_empty(&an_ep->queue)) {
		cyasgadget_req *an_req = list_entry
			(an_ep->queue.next, cyasgadget_req, queue);
		list_del_init(&an_req->queue);
		an_req->req.status = -ESHUTDOWN;
		an_req->req.complete(&an_ep->usb_ep_inst, &an_req->req);
	}
}

static void cyasgadget_stop_activity(
				cyasgadget *dev,
				struct usb_gadget_driver *driver
				)
{
	int index;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	/* don't disconnect if it's not connected */
	if (dev->gadget.speed == USB_SPEED_UNKNOWN)
		driver = NULL;

	if (spin_is_locked(&dev->lock))
		spin_unlock(&dev->lock);

	/* Stop hardware; prevent new request submissions;
	 * and kill any outstanding requests.
	 */
	cy_as_usb_disconnect(dev->dev_handle, 0, 0);

	for (index = 3; index <= 7; index += 2) {
		cyasgadget_ep *an_ep_p = &dev->an_gadget_ep[index];
		cyasgadget_nuke(an_ep_p);
	}

	for (index = 9; index <= 15; index++) {
		cyasgadget_ep *an_ep_p = &dev->an_gadget_ep[index];
		cyasgadget_nuke(an_ep_p);
	}

	/* report disconnect; the driver is already quiesced */
	if (driver)
		driver->disconnect(&dev->gadget);

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("cy_as_usb_disconnect returned success");
	#endif

	/* Stop Usb */
	cy_as_usb_stop(dev->dev_handle, 0, 0);

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("cy_as_usb_stop returned success");
	#endif
}

int usb_gadget_unregister_driver(
				struct usb_gadget_driver *driver
				)
{
	cyasgadget	*dev = cy_as_gadget_controller;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	if (!dev)
		return -ENODEV;

	if (!driver || driver != dev->driver)
		return -EINVAL;

	cyasgadget_stop_activity(dev, driver);

	driver->unbind(&dev->gadget);
	dev->gadget.dev.driver = NULL;
	dev->driver = NULL;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("unregistered driver '%s'\n",
		driver->driver.name);
	#endif

	return 0;
}
EXPORT_SYMBOL(usb_gadget_unregister_driver);

static void cyas_gadget_release(
				struct device *_dev
				)
{
	cyasgadget *dev = dev_get_drvdata(_dev);

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>%s called\n", __func__);
	#endif

	kfree(dev);
}

/* DeInitialize gadget driver  */
static void cyasgadget_deinit(
			cyasgadget *cy_as_dev
			)
{
	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>_cy_as_gadget deinitialize called\n");
	#endif

	if (!cy_as_dev) {
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1>_cy_as_gadget_deinit: "
			"invalid cyasgadget device\n");
		#endif
		return;
	}

	if (cy_as_dev->driver) {
		/* should have been done already by driver model core */
		#ifndef WESTBRIDGE_NDEBUG
		cy_as_hal_print_message("<1> cy_as_gadget: '%s' "
			"is still registered\n",
			cy_as_dev->driver->driver.name);
		#endif
		usb_gadget_unregister_driver(cy_as_dev->driver);
	}

	kfree(cy_as_dev);
	cy_as_gadget_controller = NULL;
}

/* Initialize gadget driver  */
static int cyasgadget_initialize(void)
{
	cyasgadget *cy_as_dev = 0;
	int		 retval = 0;

	#ifndef WESTBRIDGE_NDEBUG
	cy_as_hal_print_message("<1>_cy_as_gadget [V1.1] initialize called\n");
	#endif

	if (cy_as_gadget_controller != 0) {
		cy_as_hal_print_message("<1> cy_as_gadget: the device has "
			"already been initilaized. ignoring\n");
		return -EBUSY;
	}

	cy_as_dev = kzalloc(sizeof(cyasgadget), GFP_ATOMIC);
	if (cy_as_dev == NULL) {
		cy_as_hal_print_message("<1> cy_as_gadget: memory "
			"allocation failed\n");
		return -ENOMEM;
	}

	spin_lock_init(&cy_as_dev->lock);
	cy_as_dev->gadget.ops = &cyasgadget_ops;
	cy_as_dev->gadget.is_dualspeed = 1;

	/* the "gadget" abstracts/virtualizes the controller */
	/*strcpy(cy_as_dev->gadget.dev.bus_id, "cyasgadget");*/
	cy_as_dev->gadget.dev.release = cyas_gadget_release;
	cy_as_dev->gadget.name = cy_as_driver_name;

	/* Get the device handle */
	cy_as_dev->dev_handle = cyasdevice_getdevhandle();
	if (0 == cy_as_dev->dev_handle) {
		#ifndef NDEBUG
		cy_as_hal_print_message("<1> cy_as_gadget: "
			"no west bridge device\n");
		#endif
		retval = -EFAULT;
		goto done;
	}

	/* We are done now */
	cy_as_gadget_controller = cy_as_dev;
	return 0;

/*
 * in case of an error
 */
done:
	if (cy_as_dev)
		cyasgadget_deinit(cy_as_dev);

	return retval;
}

static int __init cyas_init(void)
{
	int init_res = 0;

	init_res = cyasgadget_initialize();

	if (init_res != 0) {
		printk(KERN_WARNING "<1> gadget ctl instance "
			"init error:%d\n", init_res);
		if (init_res > 0) {
			/* force -E/0 linux convention */
			init_res = init_res * -1;
		}
	}

	return init_res;
}
module_init(cyas_init);

static void __exit cyas_cleanup(void)
{
	if (cy_as_gadget_controller != NULL)
		cyasgadget_deinit(cy_as_gadget_controller);
}
module_exit(cyas_cleanup);


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(CY_AS_DRIVER_DESC);
MODULE_AUTHOR("cypress semiconductor");

/*[]*/
