/* ==========================================================================
 * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
 * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
 * otherwise expressly agreed to in writing between Synopsys and you.
 *
 * The Software IS NOT an item of Licensed Software or Licensed Product under
 * any End User Software License Agreement or Agreement for Licensed Product
 * with Synopsys or any supplement thereto. You are permitted to use and
 * redistribute this Software in source and binary forms, with or without
 * modification, provided that redistributions of source code must retain this
 * notice. You may not view, use, disclose, copy or distribute this file or
 * any information contained herein except pursuant to this license grant from
 * Synopsys. If you do not agree with this notice, including the disclaimer
 * below, then you are not authorized to use the Software.
 *
 * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS 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.
 * ========================================================================== */

/** @file
 *
 * This file contains the most of the CFI(Core Feature Interface)
 * implementation for the OTG.
 */

#ifdef DWC_UTE_CFI

#include "dwc_otg_pcd.h"
#include "dwc_otg_cfi.h"

/** This definition should actually migrate to the Portability Library */
#define DWC_CONSTANT_CPU_TO_LE16(x) (x)

extern dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t *pcd, u16 wIndex);

static int cfi_core_features_buf(uint8_t *buf, uint16_t buflen);
static int cfi_get_feature_value(uint8_t *buf, uint16_t buflen,
				 struct dwc_otg_pcd *pcd,
				 struct cfi_usb_ctrlrequest *ctrl_req);
static int cfi_set_feature_value(struct dwc_otg_pcd *pcd);
static int cfi_ep_get_sg_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
			     struct cfi_usb_ctrlrequest *req);
static int cfi_ep_get_concat_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
				 struct cfi_usb_ctrlrequest *req);
static int cfi_ep_get_align_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
				struct cfi_usb_ctrlrequest *req);
static int cfi_preproc_reset(struct dwc_otg_pcd *pcd,
			     struct cfi_usb_ctrlrequest *req);
static void cfi_free_ep_bs_dyn_data(cfi_ep_t *cfiep);

static uint16_t get_dfifo_size(dwc_otg_core_if_t *core_if);
static int32_t get_rxfifo_size(dwc_otg_core_if_t *core_if, uint16_t wValue);
static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue);

static uint8_t resize_fifos(dwc_otg_core_if_t *core_if);

/** This is the header of the all features descriptor */
static cfi_all_features_header_t all_props_desc_header = {
	.wVersion = DWC_CONSTANT_CPU_TO_LE16(0x100),
	.wCoreID = DWC_CONSTANT_CPU_TO_LE16(CFI_CORE_ID_OTG),
	.wNumFeatures = DWC_CONSTANT_CPU_TO_LE16(9),
};

/** This is an array of statically allocated feature descriptors */
static cfi_feature_desc_header_t prop_descs[] = {

	/* FT_ID_DMA_MODE */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_MODE),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(1),
	 },

	/* FT_ID_DMA_BUFFER_SETUP */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFFER_SETUP),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
	 },

	/* FT_ID_DMA_BUFF_ALIGN */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFF_ALIGN),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
	 },

	/* FT_ID_DMA_CONCAT_SETUP */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CONCAT_SETUP),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 /*.wDataLength  = DWC_CONSTANT_CPU_TO_LE16(6),*/
	 },

	/* FT_ID_DMA_CIRCULAR */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CIRCULAR),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
	 },

	/* FT_ID_THRESHOLD_SETUP */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_THRESHOLD_SETUP),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
	 },

	/* FT_ID_DFIFO_DEPTH */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DFIFO_DEPTH),
	 .bmAttributes = CFI_FEATURE_ATTR_RO,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
	 },

	/* FT_ID_TX_FIFO_DEPTH */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_TX_FIFO_DEPTH),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
	 },

	/* FT_ID_RX_FIFO_DEPTH */
	{
	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_RX_FIFO_DEPTH),
	 .bmAttributes = CFI_FEATURE_ATTR_RW,
	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
	 }
};

/** The table of feature names */
cfi_string_t prop_name_table[] = {
	{FT_ID_DMA_MODE, "dma_mode"},
	{FT_ID_DMA_BUFFER_SETUP, "buffer_setup"},
	{FT_ID_DMA_BUFF_ALIGN, "buffer_align"},
	{FT_ID_DMA_CONCAT_SETUP, "concat_setup"},
	{FT_ID_DMA_CIRCULAR, "buffer_circular"},
	{FT_ID_THRESHOLD_SETUP, "threshold_setup"},
	{FT_ID_DFIFO_DEPTH, "dfifo_depth"},
	{FT_ID_TX_FIFO_DEPTH, "txfifo_depth"},
	{FT_ID_RX_FIFO_DEPTH, "rxfifo_depth"},
	{}
};

/************************************************************************/

/**
 * Returns the name of the feature by its ID
 * or NULL if no feature ID matches.
 *
 */
const uint8_t *get_prop_name(uint16_t prop_id, int *len)
{
	cfi_string_t *pstr;
	*len = 0;

	for (pstr = prop_name_table; pstr && pstr->s; pstr++) {
		if (pstr->id == prop_id) {
			*len = DWC_STRLEN(pstr->s);
			return pstr->s;
		}
	}
	return NULL;
}

/**
 * This function handles all CFI specific control requests.
 *
 * Return a negative value to stall the DCE.
 */
int cfi_setup(struct dwc_otg_pcd *pcd, struct cfi_usb_ctrlrequest *ctrl)
{
	int retval = 0;
	dwc_otg_pcd_ep_t *ep = NULL;
	cfiobject_t *cfi = pcd->cfi;
	struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd);
	uint16_t wLen = DWC_LE16_TO_CPU(&ctrl->wLength);
	uint16_t wValue = DWC_LE16_TO_CPU(&ctrl->wValue);
	uint16_t wIndex = DWC_LE16_TO_CPU(&ctrl->wIndex);
	uint32_t regaddr = 0;
	uint32_t regval = 0;

	/* Save this Control Request in the CFI object.
	 * The data field will be assigned in the data stage completion CB function.
	 */
	cfi->ctrl_req = *ctrl;
	cfi->ctrl_req.data = NULL;

	cfi->need_gadget_att = 0;
	cfi->need_status_in_complete = 0;

	switch (ctrl->bRequest) {
	case VEN_CORE_GET_FEATURES:
		retval = cfi_core_features_buf(cfi->buf_in.buf, CFI_IN_BUF_LEN);
		if (retval >= 0) {
			ep = &pcd->ep0;

			retval = min((uint16_t) retval, wLen);
			/* Transfer this buffer to the host through the EP0-IN EP */
			ep->dwc_ep.dma_addr = cfi->buf_in.addr;
			ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
			ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
			ep->dwc_ep.xfer_len = retval;
			ep->dwc_ep.xfer_count = 0;
			ep->dwc_ep.sent_zlp = 0;
			ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;

			pcd->ep0_pending = 1;
			dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
		}
		retval = 0;
		break;

	case VEN_CORE_GET_FEATURE:
		CFI_INFO("VEN_CORE_GET_FEATURE\n");
		retval = cfi_get_feature_value(cfi->buf_in.buf, CFI_IN_BUF_LEN,
					       pcd, ctrl);
		if (retval >= 0) {
			ep = &pcd->ep0;

			retval = min((uint16_t) retval, wLen);
			/* Transfer this buffer to the host through the EP0-IN EP */
			ep->dwc_ep.dma_addr = cfi->buf_in.addr;
			ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
			ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
			ep->dwc_ep.xfer_len = retval;
			ep->dwc_ep.xfer_count = 0;
			ep->dwc_ep.sent_zlp = 0;
			ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;

			pcd->ep0_pending = 1;
			dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
		}
		CFI_INFO("VEN_CORE_GET_FEATURE=%d\n", retval);
		dump_msg(cfi->buf_in.buf, retval);
		break;

	case VEN_CORE_SET_FEATURE:
		CFI_INFO("VEN_CORE_SET_FEATURE\n");
		/* Set up an XFER to get the data stage of the control request,
		 * which is the new value of the feature to be modified.
		 */
		ep = &pcd->ep0;
		ep->dwc_ep.is_in = 0;
		ep->dwc_ep.dma_addr = cfi->buf_out.addr;
		ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf;
		ep->dwc_ep.xfer_buff = cfi->buf_out.buf;
		ep->dwc_ep.xfer_len = wLen;
		ep->dwc_ep.xfer_count = 0;
		ep->dwc_ep.sent_zlp = 0;
		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;

		pcd->ep0_pending = 1;
		/* Read the control write's data stage */
		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
		retval = 0;
		break;

	case VEN_CORE_RESET_FEATURES:
		CFI_INFO("VEN_CORE_RESET_FEATURES\n");
		cfi->need_gadget_att = 1;
		cfi->need_status_in_complete = 1;
		retval = cfi_preproc_reset(pcd, ctrl);
		CFI_INFO("VEN_CORE_RESET_FEATURES = (%d)\n", retval);
		break;

	case VEN_CORE_ACTIVATE_FEATURES:
		CFI_INFO("VEN_CORE_ACTIVATE_FEATURES\n");
		break;

	case VEN_CORE_READ_REGISTER:
		CFI_INFO("VEN_CORE_READ_REGISTER\n");
		/* wValue optionally contains the HI WORD of the register offset and
		 * wIndex contains the LOW WORD of the register offset
		 */
		if (wValue == 0) {
			/* @TODO - MAS - fix the access to the base field */
			regaddr = 0;
			regaddr |= wIndex;
		} else {
			regaddr = (wValue << 16) | wIndex;
		}

		/* Read a 32-bit value of the memory at the regaddr */
		regval = DWC_READ_REG32((uint32_t *) regaddr);

		ep = &pcd->ep0;
		dwc_memcpy(cfi->buf_in.buf, &regval, sizeof(uint32_t));
		ep->dwc_ep.is_in = 1;
		ep->dwc_ep.dma_addr = cfi->buf_in.addr;
		ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
		ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
		ep->dwc_ep.xfer_len = wLen;
		ep->dwc_ep.xfer_count = 0;
		ep->dwc_ep.sent_zlp = 0;
		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;

		pcd->ep0_pending = 1;
		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
		cfi->need_gadget_att = 0;
		retval = 0;
		break;

	case VEN_CORE_WRITE_REGISTER:
		CFI_INFO("VEN_CORE_WRITE_REGISTER\n");
		/* Set up an XFER to get the data stage of the control request,
		 * which is the new value of the register to be modified.
		 */
		ep = &pcd->ep0;
		ep->dwc_ep.is_in = 0;
		ep->dwc_ep.dma_addr = cfi->buf_out.addr;
		ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf;
		ep->dwc_ep.xfer_buff = cfi->buf_out.buf;
		ep->dwc_ep.xfer_len = wLen;
		ep->dwc_ep.xfer_count = 0;
		ep->dwc_ep.sent_zlp = 0;
		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;

		pcd->ep0_pending = 1;
		/* Read the control write's data stage */
		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
		retval = 0;
		break;

	default:
		retval = -DWC_E_NOT_SUPPORTED;
		break;
	}

	return retval;
}

/**
 * This function prepares the core features descriptors and copies its
 * raw representation into the buffer <buf>.
 *
 * The buffer structure is as follows:
 *	all_features_header (8 bytes)
 *	features_#1 (8 bytes + feature name string length)
 *	features_#2 (8 bytes + feature name string length)
 *	.....
 *	features_#n - where n=the total count of feature descriptors
 */
static int cfi_core_features_buf(uint8_t *buf, uint16_t buflen)
{
	cfi_feature_desc_header_t *prop_hdr = prop_descs;
	cfi_feature_desc_header_t *prop;
	cfi_all_features_header_t *all_props_hdr = &all_props_desc_header;
	cfi_all_features_header_t *tmp;
	uint8_t *tmpbuf = buf;
	const uint8_t *pname = NULL;
	int i, j, namelen = 0, totlen;

	/* Prepare and copy the core features into the buffer */
	CFI_INFO("%s:\n", __func__);

	tmp = (cfi_all_features_header_t *) tmpbuf;
	*tmp = *all_props_hdr;
	tmpbuf += CFI_ALL_FEATURES_HDR_LEN;

	j = sizeof(prop_descs) / sizeof(cfi_all_features_header_t);
	for (i = 0; i < j; i++, prop_hdr++) {
		pname = get_prop_name(prop_hdr->wFeatureID, &namelen);
		prop = (cfi_feature_desc_header_t *) tmpbuf;
		*prop = *prop_hdr;

		prop->bNameLen = namelen;
		prop->wLength =
		    DWC_CONSTANT_CPU_TO_LE16(CFI_FEATURE_DESC_HDR_LEN +
					     namelen);

		tmpbuf += CFI_FEATURE_DESC_HDR_LEN;
		dwc_memcpy(tmpbuf, pname, namelen);
		tmpbuf += namelen;
	}

	totlen = tmpbuf - buf;

	if (totlen > 0) {
		tmp = (cfi_all_features_header_t *) buf;
		tmp->wTotalLen = DWC_CONSTANT_CPU_TO_LE16(totlen);
	}

	return totlen;
}

/**
 * This function releases all the dynamic memory in the CFI object.
 */
static void cfi_release(cfiobject_t *cfiobj)
{
	cfi_ep_t *cfiep;
	dwc_list_link_t *tmp;

	CFI_INFO("%s\n", __func__);

	if (cfiobj->buf_in.buf) {
		DWC_DMA_FREE(CFI_IN_BUF_LEN, cfiobj->buf_in.buf,
			     cfiobj->buf_in.addr);
		cfiobj->buf_in.buf = NULL;
	}

	if (cfiobj->buf_out.buf) {
		DWC_DMA_FREE(CFI_OUT_BUF_LEN, cfiobj->buf_out.buf,
			     cfiobj->buf_out.addr);
		cfiobj->buf_out.buf = NULL;
	}

	/* Free the Buffer Setup values for each EP */
	DWC_LIST_FOREACH(tmp, &cfiobj->active_eps) {
		cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
		cfi_free_ep_bs_dyn_data(cfiep);
	}
}

/**
 * This function frees the dynamically allocated EP buffer setup data.
 */
static void cfi_free_ep_bs_dyn_data(cfi_ep_t *cfiep)
{
	if (cfiep->bm_sg) {
		DWC_FREE(cfiep->bm_sg);
		cfiep->bm_sg = NULL;
	}

	if (cfiep->bm_align) {
		DWC_FREE(cfiep->bm_align);
		cfiep->bm_align = NULL;
	}

	if (cfiep->bm_concat) {
		if (NULL != cfiep->bm_concat->wTxBytes) {
			DWC_FREE(cfiep->bm_concat->wTxBytes);
			cfiep->bm_concat->wTxBytes = NULL;
		}
		DWC_FREE(cfiep->bm_concat);
		cfiep->bm_concat = NULL;
	}
}

/**
 * This function initializes the default values of the features
 * for a specific endpoint and should be called only once when
 * the EP is enabled first time.
 */
static int cfi_ep_init_defaults(struct dwc_otg_pcd *pcd, cfi_ep_t *cfiep)
{
	int retval = 0;

	cfiep->bm_sg = DWC_ALLOC(sizeof(ddma_sg_buffer_setup_t));
	if (NULL == cfiep->bm_sg) {
		CFI_INFO("Failed to allocate memory for SG feature value\n");
		return -DWC_E_NO_MEMORY;
	}
	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));

	/* For the Concatenation feature's default value we do not allocate
	 * memory for the wTxBytes field - it will be done in the set_feature_value
	 * request handler.
	 */
	cfiep->bm_concat = DWC_ALLOC(sizeof(ddma_concat_buffer_setup_t));
	if (NULL == cfiep->bm_concat) {
		CFI_INFO
		    ("Failed to allocate memory for CONCATENATION feature value\n");
		DWC_FREE(cfiep->bm_sg);
		return -DWC_E_NO_MEMORY;
	}
	dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t));

	cfiep->bm_align = DWC_ALLOC(sizeof(ddma_align_buffer_setup_t));
	if (NULL == cfiep->bm_align) {
		CFI_INFO
		    ("Failed to allocate memory for Alignment feature value\n");
		DWC_FREE(cfiep->bm_sg);
		DWC_FREE(cfiep->bm_concat);
		return -DWC_E_NO_MEMORY;
	}
	dwc_memset(cfiep->bm_align, 0, sizeof(ddma_align_buffer_setup_t));

	return retval;
}

/**
 * The callback function that notifies the CFI on the activation of
 * an endpoint in the PCD. The following steps are done in this function:
 *
 *	Create a dynamically allocated cfi_ep_t object (a CFI wrapper to the PCD's
 *		active endpoint)
 *	Create MAX_DMA_DESCS_PER_EP count DMA Descriptors for the EP
 *	Set the Buffer Mode to standard
 *	Initialize the default values for all EP modes (SG, Circular, Concat, Align)
 *	Add the cfi_ep_t object to the list of active endpoints in the CFI object
 */
static int cfi_ep_enable(struct cfiobject *cfi, struct dwc_otg_pcd *pcd,
			 struct dwc_otg_pcd_ep *ep)
{
	cfi_ep_t *cfiep;
	int retval = -DWC_E_NOT_SUPPORTED;

	CFI_INFO("%s: epname=%s; epnum=0x%02x\n", __func__,
		 "EP_" /*ep->ep.name */, ep->desc->bEndpointAddress);
	/* MAS - Check whether this endpoint already is in the list */
	cfiep = get_cfi_ep_by_pcd_ep(cfi, ep);

	if (NULL == cfiep) {
		/* Allocate a cfi_ep_t object */
		cfiep = DWC_ALLOC(sizeof(cfi_ep_t));
		if (NULL == cfiep) {
			CFI_INFO
			    ("Unable to allocate memory for <cfiep> in function %s\n",
			     __func__);
			return -DWC_E_NO_MEMORY;
		}
		dwc_memset(cfiep, 0, sizeof(cfi_ep_t));

		/* Save the dwc_otg_pcd_ep pointer in the cfiep object */
		cfiep->ep = ep;

		/* Allocate the DMA Descriptors chain of MAX_DMA_DESCS_PER_EP count */
		ep->dwc_ep.descs =
		    DWC_DMA_ALLOC(MAX_DMA_DESCS_PER_EP *
				  sizeof(dwc_otg_dma_desc_t),
				  &ep->dwc_ep.descs_dma_addr);

		if (NULL == ep->dwc_ep.descs) {
			DWC_FREE(cfiep);
			return -DWC_E_NO_MEMORY;
		}

		DWC_LIST_INIT(&cfiep->lh);

		/* Set the buffer mode to BM_STANDARD. It will be modified
		 * when building descriptors for a specific buffer mode */
		ep->dwc_ep.buff_mode = BM_STANDARD;

		/* Create and initialize the default values for this EP's Buffer modes */
		retval = cfi_ep_init_defaults(pcd, cfiep);
		if (retval < 0)
			return retval;

		/* Add the cfi_ep_t object to the CFI object's list of active endpoints */
		DWC_LIST_INSERT_TAIL(&cfi->active_eps, &cfiep->lh);
		retval = 0;
	} else {		/* The sought EP already is in the list */
		CFI_INFO("%s: The sought EP already is in the list\n",
			 __func__);
	}

	return retval;
}
/**
 * This function is called when the data stage of a 3-stage Control Write request
 * is complete.
 *
 */
static int cfi_ctrl_write_complete(struct cfiobject *cfi,
				   struct dwc_otg_pcd *pcd)
{
	uint32_t addr, reg_value;
	uint16_t wIndex, wValue;
	uint8_t bRequest;
	uint8_t *buf = cfi->buf_out.buf;
	struct cfi_usb_ctrlrequest *ctrl_req = &cfi->ctrl_req;
	int retval = -DWC_E_NOT_SUPPORTED;

	CFI_INFO("%s\n", __func__);

	bRequest = ctrl_req->bRequest;
	wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex);
	wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue);

	/*
	 * Save the pointer to the data stage in the ctrl_req's <data> field.
	 * The request should be already saved in the command stage by now.
	 */
	ctrl_req->data = cfi->buf_out.buf;
	cfi->need_status_in_complete = 0;
	cfi->need_gadget_att = 0;

	switch (bRequest) {
	case VEN_CORE_WRITE_REGISTER:
		/* The buffer contains raw data of the new value for the register */
		reg_value = *((uint32_t *) buf);
		if (wValue == 0) {
			addr = 0;
			addr += wIndex;
		} else {
			addr = (wValue << 16) | wIndex;
		}

		retval = 0;
		cfi->need_status_in_complete = 1;
		break;

	case VEN_CORE_SET_FEATURE:
		/* The buffer contains raw data of the new value of the feature */
		retval = cfi_set_feature_value(pcd);
		if (retval < 0)
			return retval;

		cfi->need_status_in_complete = 1;
		break;

	default:
		break;
	}

	return retval;
}

/**
 * This function builds the DMA descriptors for the SG buffer mode.
 */
static void cfi_build_sg_descs(struct cfiobject *cfi, cfi_ep_t *cfiep,
			       dwc_otg_pcd_request_t *req)
{
	struct dwc_otg_pcd_ep *ep = cfiep->ep;
	ddma_sg_buffer_setup_t *sgval = cfiep->bm_sg;
	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
	struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs;
	dma_addr_t buff_addr = req->dma;
	int i;
	uint32_t txsize, off;

	txsize = sgval->wSize;
	off = sgval->bOffset;

	for (i = 0; i < sgval->bCount; i++) {
		desc->status.b.bs = BS_HOST_BUSY;
		desc->buf = buff_addr;
		desc->status.b.l = 0;
		desc->status.b.ioc = 0;
		desc->status.b.sp = 0;
		desc->status.b.bytes = txsize;
		desc->status.b.bs = BS_HOST_READY;

		/* Set the next address of the buffer */
		buff_addr += txsize + off;
		desc_last = desc;
		desc++;
	}

	/* Set the last, ioc and sp bits on the Last DMA Descriptor */
	desc_last->status.b.l = 1;
	desc_last->status.b.ioc = 1;
	desc_last->status.b.sp = ep->dwc_ep.sent_zlp;
	/* Save the last DMA descriptor pointer */
	cfiep->dma_desc_last = desc_last;
	cfiep->desc_count = sgval->bCount;
}

/**
 * This function builds the DMA descriptors for the Concatenation buffer mode.
 */
static void cfi_build_concat_descs(struct cfiobject *cfi, cfi_ep_t *cfiep,
				   dwc_otg_pcd_request_t *req)
{
	struct dwc_otg_pcd_ep *ep = cfiep->ep;
	ddma_concat_buffer_setup_t *concatval = cfiep->bm_concat;
	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
	struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs;
	dma_addr_t buff_addr = req->dma;
	int i;
	uint16_t *txsize;

	txsize = concatval->wTxBytes;

	for (i = 0; i < concatval->hdr.bDescCount; i++) {
		desc->buf = buff_addr;
		desc->status.b.bs = BS_HOST_BUSY;
		desc->status.b.l = 0;
		desc->status.b.ioc = 0;
		desc->status.b.sp = 0;
		desc->status.b.bytes = *txsize;
		desc->status.b.bs = BS_HOST_READY;

		txsize++;
		/* Set the next address of the buffer */
		buff_addr += UGETW(ep->desc->wMaxPacketSize);
		desc_last = desc;
		desc++;
	}

	/* Set the last, ioc and sp bits on the Last DMA Descriptor */
	desc_last->status.b.l = 1;
	desc_last->status.b.ioc = 1;
	desc_last->status.b.sp = ep->dwc_ep.sent_zlp;
	cfiep->dma_desc_last = desc_last;
	cfiep->desc_count = concatval->hdr.bDescCount;
}

/**
 * This function builds the DMA descriptors for the Circular buffer mode
 */
static void cfi_build_circ_descs(struct cfiobject *cfi, cfi_ep_t *cfiep,
				 dwc_otg_pcd_request_t *req)
{
	/* @todo: MAS - add implementation when this feature needs to be tested */
}

/**
 * This function builds the DMA descriptors for the Alignment buffer mode
 */
static void cfi_build_align_descs(struct cfiobject *cfi, cfi_ep_t *cfiep,
				  dwc_otg_pcd_request_t *req)
{
	struct dwc_otg_pcd_ep *ep = cfiep->ep;
	ddma_align_buffer_setup_t *alignval = cfiep->bm_align;
	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
	dma_addr_t buff_addr = req->dma;

	desc->status.b.bs = BS_HOST_BUSY;
	desc->status.b.l = 1;
	desc->status.b.ioc = 1;
	desc->status.b.sp = ep->dwc_ep.sent_zlp;
	desc->status.b.bytes = req->length;
	/* Adjust the buffer alignment */
	desc->buf = (buff_addr + alignval->bAlign);
	desc->status.b.bs = BS_HOST_READY;
	cfiep->dma_desc_last = desc;
	cfiep->desc_count = 1;
}

/**
 * This function builds the DMA descriptors chain for different modes of the
 * buffer setup of an endpoint.
 */
static void cfi_build_descriptors(struct cfiobject *cfi,
				  struct dwc_otg_pcd *pcd,
				  struct dwc_otg_pcd_ep *ep,
				  dwc_otg_pcd_request_t *req)
{
	cfi_ep_t *cfiep;

	/* Get the cfiep by the dwc_otg_pcd_ep */
	cfiep = get_cfi_ep_by_pcd_ep(cfi, ep);
	if (NULL == cfiep) {
		CFI_INFO("%s: Unable to find a matching active endpoint\n",
			 __func__);
		return;
	}

	cfiep->xfer_len = req->length;

	/* Iterate through all the DMA descriptors */
	switch (cfiep->ep->dwc_ep.buff_mode) {
	case BM_SG:
		cfi_build_sg_descs(cfi, cfiep, req);
		break;

	case BM_CONCAT:
		cfi_build_concat_descs(cfi, cfiep, req);
		break;

	case BM_CIRCULAR:
		cfi_build_circ_descs(cfi, cfiep, req);
		break;

	case BM_ALIGN:
		cfi_build_align_descs(cfi, cfiep, req);
		break;

	default:
		break;
	}
}

/**
 * Allocate DMA buffer for different Buffer modes.
 */
static void *cfi_ep_alloc_buf(struct cfiobject *cfi, struct dwc_otg_pcd *pcd,
			      struct dwc_otg_pcd_ep *ep, dma_addr_t *dma,
			      unsigned size, gfp_t flags)
{
	return DWC_DMA_ALLOC(size, dma);
}

/**
 * This function initializes the CFI object.
 */
int init_cfi(cfiobject_t *cfiobj)
{
	CFI_INFO("%s\n", __func__);

	/* Allocate a buffer for IN XFERs */
	cfiobj->buf_in.buf =
	    DWC_DMA_ALLOC(CFI_IN_BUF_LEN, &cfiobj->buf_in.addr);
	if (NULL == cfiobj->buf_in.buf) {
		CFI_INFO("Unable to allocate buffer for INs\n");
		return -DWC_E_NO_MEMORY;
	}

	/* Allocate a buffer for OUT XFERs */
	cfiobj->buf_out.buf =
	    DWC_DMA_ALLOC(CFI_OUT_BUF_LEN, &cfiobj->buf_out.addr);
	if (NULL == cfiobj->buf_out.buf) {
		CFI_INFO("Unable to allocate buffer for OUT\n");
		return -DWC_E_NO_MEMORY;
	}

	/* Initialize the callback function pointers */
	cfiobj->ops.release = cfi_release;
	cfiobj->ops.ep_enable = cfi_ep_enable;
	cfiobj->ops.ctrl_write_complete = cfi_ctrl_write_complete;
	cfiobj->ops.build_descriptors = cfi_build_descriptors;
	cfiobj->ops.ep_alloc_buf = cfi_ep_alloc_buf;

	/* Initialize the list of active endpoints in the CFI object */
	DWC_LIST_INIT(&cfiobj->active_eps);

	return 0;
}

/**
 * This function reads the required feature's current value into the buffer
 *
 * @retval: Returns negative as error, or the data length of the feature
 */
static int cfi_get_feature_value(uint8_t *buf, uint16_t buflen,
				 struct dwc_otg_pcd *pcd,
				 struct cfi_usb_ctrlrequest *ctrl_req)
{
	int retval = -DWC_E_NOT_SUPPORTED;
	struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd);
	uint16_t dfifo, rxfifo, txfifo;

	switch (ctrl_req->wIndex) {
		/* Whether the DDMA is enabled or not */
	case FT_ID_DMA_MODE:
		*buf = (coreif->dma_enable && coreif->dma_desc_enable) ? 1 : 0;
		retval = 1;
		break;

	case FT_ID_DMA_BUFFER_SETUP:
		retval = cfi_ep_get_sg_val(buf, pcd, ctrl_req);
		break;

	case FT_ID_DMA_BUFF_ALIGN:
		retval = cfi_ep_get_align_val(buf, pcd, ctrl_req);
		break;

	case FT_ID_DMA_CONCAT_SETUP:
		retval = cfi_ep_get_concat_val(buf, pcd, ctrl_req);
		break;

	case FT_ID_DMA_CIRCULAR:
		CFI_INFO("GetFeature value (FT_ID_DMA_CIRCULAR)\n");
		break;

	case FT_ID_THRESHOLD_SETUP:
		CFI_INFO("GetFeature value (FT_ID_THRESHOLD_SETUP)\n");
		break;

	case FT_ID_DFIFO_DEPTH:
		dfifo = get_dfifo_size(coreif);
		*((uint16_t *) buf) = dfifo;
		retval = sizeof(uint16_t);
		break;

	case FT_ID_TX_FIFO_DEPTH:
		retval = get_txfifo_size(pcd, ctrl_req->wValue);
		if (retval >= 0) {
			txfifo = retval;
			*((uint16_t *) buf) = txfifo;
			retval = sizeof(uint16_t);
		}
		break;

	case FT_ID_RX_FIFO_DEPTH:
		retval = get_rxfifo_size(coreif, ctrl_req->wValue);
		if (retval >= 0) {
			rxfifo = retval;
			*((uint16_t *) buf) = rxfifo;
			retval = sizeof(uint16_t);
		}
		break;
	}

	return retval;
}

/**
 * This function resets the SG for the specified EP to its default value
 */
static int cfi_reset_sg_val(cfi_ep_t *cfiep)
{
	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));
	return 0;
}

/**
 * This function resets the Alignment for the specified EP to its default value
 */
static int cfi_reset_align_val(cfi_ep_t *cfiep)
{
	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));
	return 0;
}

/**
 * This function resets the Concatenation for the specified EP to its default value
 * This function will also set the value of the wTxBytes field to NULL after
 * freeing the memory previously allocated for this field.
 */
static int cfi_reset_concat_val(cfi_ep_t *cfiep)
{
	/* First we need to free the wTxBytes field */
	if (cfiep->bm_concat->wTxBytes) {
		DWC_FREE(cfiep->bm_concat->wTxBytes);
		cfiep->bm_concat->wTxBytes = NULL;
	}

	dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t));
	return 0;
}

/**
 * This function resets all the buffer setups of the specified endpoint
 */
static int cfi_ep_reset_all_setup_vals(cfi_ep_t *cfiep)
{
	cfi_reset_sg_val(cfiep);
	cfi_reset_align_val(cfiep);
	cfi_reset_concat_val(cfiep);
	return 0;
}

static int cfi_handle_reset_fifo_val(struct dwc_otg_pcd *pcd, uint8_t ep_addr,
				     uint8_t rx_rst, uint8_t tx_rst)
{
	int retval = -DWC_E_INVALID;
	uint16_t tx_siz[15];
	uint16_t rx_siz = 0;
	dwc_otg_pcd_ep_t *ep = NULL;
	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;

	if (rx_rst) {
		rx_siz = params->dev_rx_fifo_size;
		params->dev_rx_fifo_size = GET_CORE_IF(pcd)->init_rxfsiz;
	}

	if (tx_rst) {
		if (ep_addr == 0) {
			int i;

			for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
				tx_siz[i] =
				    core_if->core_params->dev_tx_fifo_size[i];
				core_if->core_params->dev_tx_fifo_size[i] =
				    core_if->init_txfsiz[i];
			}
		} else {

			ep = get_ep_by_addr(pcd, ep_addr);

			if (NULL == ep) {
				CFI_INFO
				    ("%s: Unable to get the endpoint addr=0x%02x\n",
				     __func__, ep_addr);
				return -DWC_E_INVALID;
			}

			tx_siz[0] =
			    params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num -
						     1];
			params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] =
			    GET_CORE_IF(pcd)->init_txfsiz[ep->
							  dwc_ep.tx_fifo_num -
							  1];
		}
	}

	if (resize_fifos(GET_CORE_IF(pcd))) {
		retval = 0;
	} else {
		CFI_INFO
		    ("%s: Error resetting the feature Reset All(FIFO size)\n",
		     __func__);
		if (rx_rst) {
			params->dev_rx_fifo_size = rx_siz;
		}

		if (tx_rst) {
			if (ep_addr == 0) {
				int i;
				for (i = 0; i < core_if->hwcfg4.b.num_in_eps;
				     i++) {
					core_if->
					    core_params->dev_tx_fifo_size[i] =
					    tx_siz[i];
				}
			} else {
				params->dev_tx_fifo_size[ep->
							 dwc_ep.tx_fifo_num -
							 1] = tx_siz[0];
			}
		}
		retval = -DWC_E_INVALID;
	}
	return retval;
}

static int cfi_handle_reset_all(struct dwc_otg_pcd *pcd, uint8_t addr)
{
	int retval = 0;
	cfi_ep_t *cfiep;
	cfiobject_t *cfi = pcd->cfi;
	dwc_list_link_t *tmp;

	retval = cfi_handle_reset_fifo_val(pcd, addr, 1, 1);
	if (retval < 0) {
		return retval;
	}

	/* If the EP address is known then reset the features for only that EP */
	if (addr) {
		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
		if (NULL == cfiep) {
			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
				 __func__, addr);
			return -DWC_E_INVALID;
		}
		retval = cfi_ep_reset_all_setup_vals(cfiep);
		cfiep->ep->dwc_ep.buff_mode = BM_STANDARD;
	} else {
		/* Traverse all the active EP's and reset the feature(s) value(s) */
		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
			retval = cfi_ep_reset_all_setup_vals(cfiep);
			cfiep->ep->dwc_ep.buff_mode = BM_STANDARD;
			if (retval < 0) {
				CFI_INFO
				    ("%s: Error resetting the feature Reset All\n",
				     __func__);
				return retval;
			}
		}
	}
	return retval;
}

static int cfi_handle_reset_dma_buff_setup(struct dwc_otg_pcd *pcd,
					   uint8_t addr)
{
	int retval = 0;
	cfi_ep_t *cfiep;
	cfiobject_t *cfi = pcd->cfi;
	dwc_list_link_t *tmp;

	/* If the EP address is known then reset the features for only that EP */
	if (addr) {
		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
		if (NULL == cfiep) {
			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
				 __func__, addr);
			return -DWC_E_INVALID;
		}
		retval = cfi_reset_sg_val(cfiep);
	} else {
		/* Traverse all the active EP's and reset the feature(s) value(s) */
		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
			retval = cfi_reset_sg_val(cfiep);
			if (retval < 0) {
				CFI_INFO
				    ("%s: Error resetting the feature Buffer Setup\n",
				     __func__);
				return retval;
			}
		}
	}
	return retval;
}

static int cfi_handle_reset_concat_val(struct dwc_otg_pcd *pcd, uint8_t addr)
{
	int retval = 0;
	cfi_ep_t *cfiep;
	cfiobject_t *cfi = pcd->cfi;
	dwc_list_link_t *tmp;

	/* If the EP address is known then reset the features for only that EP */
	if (addr) {
		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
		if (NULL == cfiep) {
			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
				 __func__, addr);
			return -DWC_E_INVALID;
		}
		retval = cfi_reset_concat_val(cfiep);
	} else {
		/* Traverse all the active EP's and reset the feature(s) value(s) */
		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
			retval = cfi_reset_concat_val(cfiep);
			if (retval < 0) {
				CFI_INFO
				    ("%s: Error resetting the feature Concatenation Value\n",
				     __func__);
				return retval;
			}
		}
	}
	return retval;
}

static int cfi_handle_reset_align_val(struct dwc_otg_pcd *pcd, uint8_t addr)
{
	int retval = 0;
	cfi_ep_t *cfiep;
	cfiobject_t *cfi = pcd->cfi;
	dwc_list_link_t *tmp;

	/* If the EP address is known then reset the features for only that EP */
	if (addr) {
		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
		if (NULL == cfiep) {
			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
				 __func__, addr);
			return -DWC_E_INVALID;
		}
		retval = cfi_reset_align_val(cfiep);
	} else {
		/* Traverse all the active EP's and reset the feature(s) value(s) */
		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
			retval = cfi_reset_align_val(cfiep);
			if (retval < 0) {
				CFI_INFO
				    ("%s: Error resetting the feature Alignment Value\n",
				     __func__);
				return retval;
			}
		}
	}
	return retval;

}

static int cfi_preproc_reset(struct dwc_otg_pcd *pcd,
			     struct cfi_usb_ctrlrequest *req)
{
	int retval = 0;

	switch (req->wIndex) {
	case 0:
		/* Reset all features */
		retval = cfi_handle_reset_all(pcd, req->wValue & 0xff);
		break;

	case FT_ID_DMA_BUFFER_SETUP:
		/* Reset the SG buffer setup */
		retval =
		    cfi_handle_reset_dma_buff_setup(pcd, req->wValue & 0xff);
		break;

	case FT_ID_DMA_CONCAT_SETUP:
		/* Reset the Concatenation buffer setup */
		retval = cfi_handle_reset_concat_val(pcd, req->wValue & 0xff);
		break;

	case FT_ID_DMA_BUFF_ALIGN:
		/* Reset the Alignment buffer setup */
		retval = cfi_handle_reset_align_val(pcd, req->wValue & 0xff);
		break;

	case FT_ID_TX_FIFO_DEPTH:
		retval =
		    cfi_handle_reset_fifo_val(pcd, req->wValue & 0xff, 0, 1);
		pcd->cfi->need_gadget_att = 0;
		break;

	case FT_ID_RX_FIFO_DEPTH:
		retval = cfi_handle_reset_fifo_val(pcd, 0, 1, 0);
		pcd->cfi->need_gadget_att = 0;
		break;
	default:
		break;
	}
	return retval;
}

/**
 * This function sets a new value for the SG buffer setup.
 */
static int cfi_ep_set_sg_val(uint8_t *buf, struct dwc_otg_pcd *pcd)
{
	uint8_t inaddr, outaddr;
	cfi_ep_t *epin, *epout;
	ddma_sg_buffer_setup_t *psgval;
	uint32_t desccount, size;

	CFI_INFO("%s\n", __func__);

	psgval = (ddma_sg_buffer_setup_t *) buf;
	desccount = (uint32_t) psgval->bCount;
	size = (uint32_t) psgval->wSize;

	/* Check the DMA descriptor count */
	if ((desccount > MAX_DMA_DESCS_PER_EP) || (desccount == 0)) {
		CFI_INFO
		    ("%s: The count of DMA Descriptors should be between 1 and %d\n",
		     __func__, MAX_DMA_DESCS_PER_EP);
		return -DWC_E_INVALID;
	}

	/* Check the DMA descriptor count */

	if (size == 0) {

		CFI_INFO("%s: The transfer size should be at least 1 byte\n",
			 __func__);

		return -DWC_E_INVALID;

	}

	inaddr = psgval->bInEndpointAddress;
	outaddr = psgval->bOutEndpointAddress;

	epin = get_cfi_ep_by_addr(pcd->cfi, inaddr);
	epout = get_cfi_ep_by_addr(pcd->cfi, outaddr);

	if (NULL == epin || NULL == epout) {
		CFI_INFO
		    ("%s: Unable to get the endpoints inaddr=0x%02x outaddr=0x%02x\n",
		     __func__, inaddr, outaddr);
		return -DWC_E_INVALID;
	}

	epin->ep->dwc_ep.buff_mode = BM_SG;
	dwc_memcpy(epin->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t));

	epout->ep->dwc_ep.buff_mode = BM_SG;
	dwc_memcpy(epout->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t));

	return 0;
}

/**
 * This function sets a new value for the buffer Alignment setup.
 */
static int cfi_ep_set_alignment_val(uint8_t *buf, struct dwc_otg_pcd *pcd)
{
	cfi_ep_t *ep;
	uint8_t addr;
	ddma_align_buffer_setup_t *palignval;

	palignval = (ddma_align_buffer_setup_t *) buf;
	addr = palignval->bEndpointAddress;

	ep = get_cfi_ep_by_addr(pcd->cfi, addr);

	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
			 __func__, addr);
		return -DWC_E_INVALID;
	}

	ep->ep->dwc_ep.buff_mode = BM_ALIGN;
	dwc_memcpy(ep->bm_align, palignval, sizeof(ddma_align_buffer_setup_t));

	return 0;
}

/**
 * This function sets a new value for the Concatenation buffer setup.
 */
static int cfi_ep_set_concat_val(uint8_t *buf, struct dwc_otg_pcd *pcd)
{
	uint8_t addr;
	cfi_ep_t *ep;
	struct _ddma_concat_buffer_setup_hdr *pConcatValHdr;
	uint16_t *pVals;
	uint32_t desccount;
	int i;
	uint16_t mps;

	pConcatValHdr = (struct _ddma_concat_buffer_setup_hdr *)buf;
	desccount = (uint32_t) pConcatValHdr->bDescCount;
	pVals = (uint16_t *) (buf + BS_CONCAT_VAL_HDR_LEN);

	/* Check the DMA descriptor count */
	if (desccount > MAX_DMA_DESCS_PER_EP) {
		CFI_INFO("%s: Maximum DMA Descriptor count should be %d\n",
			 __func__, MAX_DMA_DESCS_PER_EP);
		return -DWC_E_INVALID;
	}

	addr = pConcatValHdr->bEndpointAddress;
	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
			 __func__, addr);
		return -DWC_E_INVALID;
	}

	mps = UGETW(ep->ep->desc->wMaxPacketSize);

#if 0
	for (i = 0; i < desccount; i++) {
		CFI_INFO("%s: wTxSize[%d]=0x%04x\n", __func__, i, pVals[i]);
	}
	CFI_INFO("%s: epname=%s; mps=%d\n", __func__, ep->ep->ep.name, mps);
#endif

	/* Check the wTxSizes to be less than or equal to the mps */
	for (i = 0; i < desccount; i++) {
		if (pVals[i] > mps) {
			CFI_INFO
			    ("%s: ERROR - the wTxSize[%d] should be <= MPS (wTxSize=%d)\n",
			     __func__, i, pVals[i]);
			return -DWC_E_INVALID;
		}
	}

	ep->ep->dwc_ep.buff_mode = BM_CONCAT;
	dwc_memcpy(ep->bm_concat, pConcatValHdr, BS_CONCAT_VAL_HDR_LEN);

	/* Free the previously allocated storage for the wTxBytes */
	if (ep->bm_concat->wTxBytes)
		DWC_FREE(ep->bm_concat->wTxBytes);

	/* Allocate a new storage for the wTxBytes field */
	ep->bm_concat->wTxBytes =
	    DWC_ALLOC(sizeof(uint16_t) * pConcatValHdr->bDescCount);
	if (NULL == ep->bm_concat->wTxBytes) {
		CFI_INFO("%s: Unable to allocate memory\n", __func__);
		return -DWC_E_NO_MEMORY;
	}

	/* Copy the new values into the wTxBytes filed */
	dwc_memcpy(ep->bm_concat->wTxBytes, buf + BS_CONCAT_VAL_HDR_LEN,
		   sizeof(uint16_t) * pConcatValHdr->bDescCount);

	return 0;
}

/**
 * This function calculates the total of all FIFO sizes
 *
 * @param core_if Programming view of DWC_otg controller
 *
 * @return The total of data FIFO sizes.
 *
 */
static uint16_t get_dfifo_size(dwc_otg_core_if_t *core_if)
{
	dwc_otg_core_params_t *params = core_if->core_params;
	uint16_t dfifo_total = 0;
	int i;

	/* The shared RxFIFO size */
	dfifo_total =
	    params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size;

	/* Add up each TxFIFO size to the total */
	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++)
		dfifo_total += params->dev_tx_fifo_size[i];

	return dfifo_total;
}

/**
 * This function returns Rx FIFO size
 *
 * @param core_if Programming view of DWC_otg controller
 *
 * @return The total of data FIFO sizes.
 *
 */
static int32_t get_rxfifo_size(dwc_otg_core_if_t *core_if, uint16_t wValue)
{
	switch (wValue >> 8) {
	case 0:
		return (core_if->pwron_rxfsiz <
			32768) ? core_if->pwron_rxfsiz : 32768;
		break;
	case 1:
		return core_if->core_params->dev_rx_fifo_size;
		break;
	default:
		return -DWC_E_INVALID;
		break;
	}
}

/**
 * This function returns Tx FIFO size for IN EP
 *
 * @param core_if Programming view of DWC_otg controller
 *
 * @return The total of data FIFO sizes.
 *
 */
static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue)
{
	dwc_otg_pcd_ep_t *ep;

	ep = get_ep_by_addr(pcd, wValue & 0xff);

	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
			 __func__, wValue & 0xff);
		return -DWC_E_INVALID;
	}

	if (!ep->dwc_ep.is_in) {
		CFI_INFO
		    ("%s: No Tx FIFO assigned to the Out endpoint addr=0x%02x\n",
		     __func__, wValue & 0xff);
		return -DWC_E_INVALID;
	}

	switch (wValue >> 8) {
	case 0:
		return (GET_CORE_IF(pcd)->pwron_txfsiz
			[ep->dwc_ep.tx_fifo_num - 1] <
			768) ? GET_CORE_IF(pcd)->pwron_txfsiz[ep->
							      dwc_ep.tx_fifo_num
							      - 1] : 32768;
		break;
	case 1:
		return GET_CORE_IF(pcd)->core_params->
		    dev_tx_fifo_size[ep->dwc_ep.num - 1];
		break;
	default:
		return -DWC_E_INVALID;
		break;
	}
}

/**
 * This function checks if the submitted combination of
 * device mode FIFO sizes is possible or not.
 *
 * @param core_if Programming view of DWC_otg controller
 *
 * @return 1 if possible, 0 otherwise.
 *
 */
static uint8_t check_fifo_sizes(dwc_otg_core_if_t *core_if)
{
	uint16_t dfifo_actual = 0;
	dwc_otg_core_params_t *params = core_if->core_params;
	uint16_t start_addr = 0;
	int i;

	dfifo_actual =
	    params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size;

	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++)
		dfifo_actual += params->dev_tx_fifo_size[i];

	if (dfifo_actual > core_if->total_fifo_size)
		return 0;

	if (params->dev_rx_fifo_size > 32768 || params->dev_rx_fifo_size < 16)
		return 0;

	if (params->dev_nperio_tx_fifo_size > 32768
	    || params->dev_nperio_tx_fifo_size < 16)
		return 0;

	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
		if (params->dev_tx_fifo_size[i] > 768
		    || params->dev_tx_fifo_size[i] < 4)
			return 0;
	}

	if (params->dev_rx_fifo_size > core_if->pwron_rxfsiz)
		return 0;
	start_addr = params->dev_rx_fifo_size;

	if (params->dev_nperio_tx_fifo_size > core_if->pwron_gnptxfsiz)
		return 0;
	start_addr += params->dev_nperio_tx_fifo_size;

	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
		if (params->dev_tx_fifo_size[i] > core_if->pwron_txfsiz[i])
			return 0;
		start_addr += params->dev_tx_fifo_size[i];
	}

	return 1;
}

/**
 * This function resizes Device mode FIFOs
 *
 * @param core_if Programming view of DWC_otg controller
 *
 * @return 1 if successful, 0 otherwise
 *
 */
static uint8_t resize_fifos(dwc_otg_core_if_t *core_if)
{
	int i = 0;
	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
	dwc_otg_core_params_t *params = core_if->core_params;
	uint32_t rx_fifo_size;
	fifosize_data_t nptxfifosize;
	fifosize_data_t txfifosize[15];

	uint32_t rx_fsz_bak;
	uint32_t nptxfsz_bak;
	uint32_t txfsz_bak[15];

	uint16_t start_address;
	uint8_t retval = 1;

	if (!check_fifo_sizes(core_if))
		return 0;

	/* Configure data FIFO sizes */
	if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) {
		rx_fsz_bak = DWC_READ_REG32(&global_regs->grxfsiz);
		rx_fifo_size = params->dev_rx_fifo_size;
		DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size);

		/*
		 * Tx FIFOs These FIFOs are numbered from 1 to 15.
		 * Indexes of the FIFO size module parameters in the
		 * dev_tx_fifo_size array and the FIFO size registers in
		 * the dtxfsiz array run from 0 to 14.
		 */

		/* Non-periodic Tx FIFO */
		nptxfsz_bak = DWC_READ_REG32(&global_regs->gnptxfsiz);
		nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size;
		start_address = params->dev_rx_fifo_size;
		nptxfifosize.b.startaddr = start_address;

		DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32);

		start_address += nptxfifosize.b.depth;

		for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
			txfsz_bak[i] = DWC_READ_REG32(&global_regs->dtxfsiz[i]);

			txfifosize[i].b.depth = params->dev_tx_fifo_size[i];
			txfifosize[i].b.startaddr = start_address;
			DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
					txfifosize[i].d32);

			start_address += txfifosize[i].b.depth;
		}

		/** Check if register values are set correctly */
		if (rx_fifo_size != DWC_READ_REG32(&global_regs->grxfsiz))
			retval = 0;

		if (nptxfifosize.d32 != DWC_READ_REG32(&global_regs->gnptxfsiz))
			retval = 0;

		for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++)
			if (txfifosize[i].d32 !=
			    DWC_READ_REG32(&global_regs->dtxfsiz[i]))
				retval = 0;

		/** If register values are not set correctly, reset old values */
		if (retval == 0) {
			DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fsz_bak);

			/* Non-periodic Tx FIFO */
			DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfsz_bak);

			for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++)
				DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
						txfsz_bak[i]);
		}
	} else {
		return 0;
	}

	/* Flush the FIFOs */
	/* all Tx FIFOs */
	dwc_otg_flush_tx_fifo(core_if, 0x10);
	dwc_otg_flush_rx_fifo(core_if);

	return retval;
}

/**
 * This function sets a new value for the buffer Alignment setup.
 */
static int cfi_ep_set_tx_fifo_val(uint8_t *buf, dwc_otg_pcd_t *pcd)
{
	int retval;
	uint32_t fsiz;
	uint16_t size;
	uint16_t ep_addr;
	dwc_otg_pcd_ep_t *ep;
	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;
	tx_fifo_size_setup_t *ptxfifoval;

	ptxfifoval = (tx_fifo_size_setup_t *) buf;
	ep_addr = ptxfifoval->bEndpointAddress;
	size = ptxfifoval->wDepth;

	ep = get_ep_by_addr(pcd, ep_addr);

	CFI_INFO
	    ("%s: Set Tx FIFO size: endpoint addr=0x%02x, depth=%d, FIFO Num=%d\n",
	     __func__, ep_addr, size, ep->dwc_ep.tx_fifo_num);

	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
			 __func__, ep_addr);
		return -DWC_E_INVALID;
	}

	fsiz = params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1];
	params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = size;

	if (resize_fifos(GET_CORE_IF(pcd))) {
		retval = 0;
	} else {
		CFI_INFO
		    ("%s: Error setting the feature Tx FIFO Size for EP%d\n",
		     __func__, ep_addr);
		params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = fsiz;
		retval = -DWC_E_INVALID;
	}

	return retval;
}

/**
 * This function sets a new value for the buffer Alignment setup.
 */
static int cfi_set_rx_fifo_val(uint8_t *buf, dwc_otg_pcd_t *pcd)
{
	int retval;
	uint32_t fsiz;
	uint16_t size;
	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;
	rx_fifo_size_setup_t *prxfifoval;

	prxfifoval = (rx_fifo_size_setup_t *) buf;
	size = prxfifoval->wDepth;

	fsiz = params->dev_rx_fifo_size;
	params->dev_rx_fifo_size = size;

	if (resize_fifos(GET_CORE_IF(pcd))) {
		retval = 0;
	} else {
		CFI_INFO("%s: Error setting the feature Rx FIFO Size\n",
			 __func__);
		params->dev_rx_fifo_size = fsiz;
		retval = -DWC_E_INVALID;
	}

	return retval;
}

/**
 * This function reads the SG of an EP's buffer setup into the buffer buf
 */
static int cfi_ep_get_sg_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
			     struct cfi_usb_ctrlrequest *req)
{
	int retval = -DWC_E_INVALID;
	uint8_t addr;
	cfi_ep_t *ep;

	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
	addr = req->wValue & 0xFF;
	if (addr == 0)		/* The address should be non-zero */
		return retval;

	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
			 __func__, addr);
		return retval;
	}

	dwc_memcpy(buf, ep->bm_sg, BS_SG_VAL_DESC_LEN);
	retval = BS_SG_VAL_DESC_LEN;
	return retval;
}

/**
 * This function reads the Concatenation value of an EP's buffer mode into
 * the buffer buf
 */
static int cfi_ep_get_concat_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
				 struct cfi_usb_ctrlrequest *req)
{
	int retval = -DWC_E_INVALID;
	uint8_t addr;
	cfi_ep_t *ep;
	uint8_t desc_count;

	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
	addr = req->wValue & 0xFF;
	if (addr == 0)		/* The address should be non-zero */
		return retval;

	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
			 __func__, addr);
		return retval;
	}

	/* Copy the header to the buffer */
	dwc_memcpy(buf, ep->bm_concat, BS_CONCAT_VAL_HDR_LEN);
	/* Advance the buffer pointer by the header size */
	buf += BS_CONCAT_VAL_HDR_LEN;

	desc_count = ep->bm_concat->hdr.bDescCount;
	/* Copy all the wTxBytes to the buffer */
	dwc_memcpy(buf, ep->bm_concat->wTxBytes, sizeof(uid16_t) * desc_count);

	retval = BS_CONCAT_VAL_HDR_LEN + sizeof(uid16_t) * desc_count;
	return retval;
}

/**
 * This function reads the buffer Alignment value of an EP's buffer mode into
 * the buffer buf
 *
 * @return The total number of bytes copied to the buffer or negative error code.
 */
static int cfi_ep_get_align_val(uint8_t *buf, struct dwc_otg_pcd *pcd,
				struct cfi_usb_ctrlrequest *req)
{
	int retval = -DWC_E_INVALID;
	uint8_t addr;
	cfi_ep_t *ep;

	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
	addr = req->wValue & 0xFF;
	if (addr == 0)		/* The address should be non-zero */
		return retval;

	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
	if (NULL == ep) {
		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
			 __func__, addr);
		return retval;
	}

	dwc_memcpy(buf, ep->bm_align, BS_ALIGN_VAL_HDR_LEN);
	retval = BS_ALIGN_VAL_HDR_LEN;

	return retval;
}

/**
 * This function sets a new value for the specified feature
 *
 * @param	pcd	A pointer to the PCD object
 *
 * @return 0 if successful, negative error code otherwise to stall the DCE.
 */
static int cfi_set_feature_value(struct dwc_otg_pcd *pcd)
{
	int retval = -DWC_E_NOT_SUPPORTED;
	uint16_t wIndex, wValue;
	uint8_t bRequest;
	struct dwc_otg_core_if *coreif;
	cfiobject_t *cfi = pcd->cfi;
	struct cfi_usb_ctrlrequest *ctrl_req;
	uint8_t *buf;
	ctrl_req = &cfi->ctrl_req;

	buf = pcd->cfi->ctrl_req.data;

	coreif = GET_CORE_IF(pcd);
	bRequest = ctrl_req->bRequest;
	wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex);
	wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue);

	/* See which feature is to be modified */
	switch (wIndex) {
	case FT_ID_DMA_BUFFER_SETUP:
		/* Modify the feature */
		retval = cfi_ep_set_sg_val(buf, pcd);
		if (retval < 0)
			return retval;

		/* And send this request to the gadget */
		cfi->need_gadget_att = 1;
		break;

	case FT_ID_DMA_BUFF_ALIGN:
		retval = cfi_ep_set_alignment_val(buf, pcd);
		if (retval < 0)
			return retval;
		cfi->need_gadget_att = 1;
		break;

	case FT_ID_DMA_CONCAT_SETUP:
		/* Modify the feature */
		retval = cfi_ep_set_concat_val(buf, pcd);
		if (retval < 0)
			return retval;
		cfi->need_gadget_att = 1;
		break;

	case FT_ID_DMA_CIRCULAR:
		CFI_INFO("FT_ID_DMA_CIRCULAR\n");
		break;

	case FT_ID_THRESHOLD_SETUP:
		CFI_INFO("FT_ID_THRESHOLD_SETUP\n");
		break;

	case FT_ID_DFIFO_DEPTH:
		CFI_INFO("FT_ID_DFIFO_DEPTH\n");
		break;

	case FT_ID_TX_FIFO_DEPTH:
		CFI_INFO("FT_ID_TX_FIFO_DEPTH\n");
		retval = cfi_ep_set_tx_fifo_val(buf, pcd);
		if (retval < 0)
			return retval;
		cfi->need_gadget_att = 0;
		break;

	case FT_ID_RX_FIFO_DEPTH:
		CFI_INFO("FT_ID_RX_FIFO_DEPTH\n");
		retval = cfi_set_rx_fifo_val(buf, pcd);
		if (retval < 0)
			return retval;
		cfi->need_gadget_att = 0;
		break;
	}

	return retval;
}

#endif
