/*==========================================================================
 * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_ddma.c $
 * $Revision: #10 $
 * $Date: 2011/10/20 $
 * $Change: 1869464 $
 *
 * 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.
 * ========================================================================== */
#ifndef DWC_DEVICE_ONLY

/** @file
 * This file contains Descriptor DMA support implementation for host mode.
 */

#include "dwc_otg_hcd.h"
#include "dwc_otg_regs.h"

static inline uint8_t frame_list_idx(uint16_t frame)
{
	return frame & (MAX_FRLIST_EN_NUM - 1);
}

static inline uint16_t desclist_idx_inc(uint16_t idx, uint16_t inc, uint8_t speed)
{
	return (idx + inc) &
	    (((speed ==
	       DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC :
	      MAX_DMA_DESC_NUM_GENERIC) - 1);
}

static inline uint16_t desclist_idx_dec(uint16_t idx, uint16_t inc, uint8_t speed)
{
	return (idx - inc) &
	    (((speed ==
	       DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC :
	      MAX_DMA_DESC_NUM_GENERIC) - 1);
}

static inline uint16_t max_desc_num(dwc_otg_qh_t *qh)
{
	return ((qh->ep_type == UE_ISOCHRONOUS)
		 && (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH))
		? MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC;
}
static inline uint16_t frame_incr_val(dwc_otg_qh_t *qh)
{
	return (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH)
		? ((qh->interval + 8 - 1) / 8)
		: qh->interval;
}

static int desc_list_alloc(dwc_otg_qh_t *qh)
{
	int retval = 0;

	qh->desc_list = (dwc_otg_host_dma_desc_t *)
	    DWC_DMA_ALLOC(sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh),
			  &qh->desc_list_dma);

	if (!qh->desc_list) {
		retval = -DWC_E_NO_MEMORY;
		DWC_ERROR("%s: DMA descriptor list allocation failed\n", __func__);
		return retval;
	}

	dwc_memset(qh->desc_list, 0x00,
		   sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh));

	qh->n_bytes =
	    (uint32_t *) DWC_ALLOC(sizeof(uint32_t) * max_desc_num(qh));

	if (!qh->n_bytes) {
		retval = -DWC_E_NO_MEMORY;
		DWC_ERROR
		    ("%s: Failed to allocate array for descriptors' size actual values\n",
		     __func__);

	}
	return retval;

}

static void desc_list_free(dwc_otg_qh_t *qh)
{
	if (qh->desc_list) {
		DWC_DMA_FREE(max_desc_num(qh), qh->desc_list,
			     qh->desc_list_dma);
		qh->desc_list = NULL;
	}

	if (qh->n_bytes) {
		DWC_FREE(qh->n_bytes);
		qh->n_bytes = NULL;
	}
}

static int frame_list_alloc(dwc_otg_hcd_t *hcd)
{
	int retval = 0;
	if (hcd->frame_list)
		return 0;

	hcd->frame_list = DWC_DMA_ALLOC(4 * MAX_FRLIST_EN_NUM,
					&hcd->frame_list_dma);
	if (!hcd->frame_list) {
		retval = -DWC_E_NO_MEMORY;
		DWC_ERROR("%s: Frame List allocation failed\n", __func__);
		return retval;
	}

	dwc_memset(hcd->frame_list, 0x00, 4 * MAX_FRLIST_EN_NUM);

	return retval;
}

static void frame_list_free(dwc_otg_hcd_t *hcd)
{
	if (!hcd->frame_list)
		return;

	DWC_DMA_FREE(4 * MAX_FRLIST_EN_NUM, hcd->frame_list, hcd->frame_list_dma);
	hcd->frame_list = NULL;
}

static void per_sched_enable(dwc_otg_hcd_t *hcd, uint16_t fr_list_en)
{

	hcfg_data_t hcfg;

	hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg);

	if (hcfg.b.perschedena)
		/* already enabled */
		return;

	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hflbaddr,
			hcd->frame_list_dma);

	switch (fr_list_en) {
	case 64:
		hcfg.b.frlisten = 3;
		break;
	case 32:
		hcfg.b.frlisten = 2;
		break;
	case 16:
		hcfg.b.frlisten = 1;
		break;
	case 8:
		hcfg.b.frlisten = 0;
		break;
	default:
		break;
	}

	hcfg.b.perschedena = 1;

	DWC_DEBUGPL(DBG_HCD, "Enabling Periodic schedule\n");
	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32);

}

static void per_sched_disable(dwc_otg_hcd_t *hcd)
{
	hcfg_data_t hcfg;

	hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg);

	if (!hcfg.b.perschedena)
		/* already disabled */
		return;

	hcfg.b.perschedena = 0;

	DWC_DEBUGPL(DBG_HCD, "Disabling Periodic schedule\n");
	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32);
}

/*
 * Activates/Deactivates FrameList entries for the channel
 * based on endpoint servicing period.
 */
void update_frame_list(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, uint8_t enable)
{
	uint16_t i, j, inc;
	dwc_hc_t *hc = NULL;

	if (!qh->channel) {
		DWC_ERROR("qh->channel = %p", qh->channel);
		return;
	}

	if (!hcd) {
		DWC_ERROR("------hcd = %p", hcd);
		return;
	}

	if (!hcd->frame_list) {
		DWC_ERROR("-------hcd->frame_list = %p", hcd->frame_list);
		return;
	}

	hc = qh->channel;
	inc = frame_incr_val(qh);
	if (qh->ep_type == UE_ISOCHRONOUS)
		i = frame_list_idx(qh->sched_frame);
	else
		i = 0;

	j = i;
	do {
		if (enable)
			hcd->frame_list[j] |= (1 << hc->hc_num);
		else
			hcd->frame_list[j] &= ~(1 << hc->hc_num);
		j = (j + inc) & (MAX_FRLIST_EN_NUM - 1);
	} while (j != i);
	if (!enable)
		return;
	hc->schinfo = 0;
	if (qh->channel->speed == DWC_OTG_EP_SPEED_HIGH) {
		j = 1;
		/* TODO - check this */
		inc = (8 + qh->interval - 1) / qh->interval;
		for (i = 0; i < inc; i++) {
			hc->schinfo |= j;
			j = j << qh->interval;
		}
	} else {
		hc->schinfo = 0xff;
	}
}


#if 1
void dump_frame_list(dwc_otg_hcd_t *hcd)
{
	int i = 0;
	DWC_PRINTF("--FRAME LIST (hex) --\n");
	for (i = 0; i < MAX_FRLIST_EN_NUM; i++) {
		DWC_PRINTF("%x\t", hcd->frame_list[i]);
		if (!(i % 8) && i)
			DWC_PRINTF("\n");
	}
	DWC_PRINTF("\n----\n");

}
#endif

static void release_channel_ddma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	dwc_hc_t *hc = qh->channel;
	if (dwc_qh_is_non_per(qh))
		hcd->non_periodic_channels--;
	else
		update_frame_list(hcd, qh, 0);

	/*
	 * The condition is added to prevent double cleanup try in case of device
	 * disconnect. See channel cleanup in dwc_otg_hcd_disconnect_cb().
	 */
	if (hc->qh) {
		dwc_otg_hc_cleanup(hcd->core_if, hc);
		DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry);
		hc->qh = NULL;
	}

	qh->channel = NULL;
	qh->ntd = 0;

	if (qh->desc_list)
		dwc_memset(qh->desc_list, 0x00,
			   sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh));
}

/**
 * Initializes a QH structure's Descriptor DMA related members.
 * Allocates memory for descriptor list.
 * On first periodic QH, allocates memory for FrameList
 * and enables periodic scheduling.
 *
 * @param hcd The HCD state structure for the DWC OTG controller.
 * @param qh The QH to init.
 *
 * @return 0 if successful, negative error code otherwise.
 */
int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	int retval = 0;

	if (qh->do_split) {
		DWC_ERROR("SPLIT Transfers are not supported in Descriptor DMA.\n");
		return -1;
	}

	retval = desc_list_alloc(qh);

	if ((retval == 0)
	    && (qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)) {
		if (!hcd->frame_list) {
			retval = frame_list_alloc(hcd);
			/* Enable periodic schedule on first periodic QH */
			if (retval == 0)
				per_sched_enable(hcd, MAX_FRLIST_EN_NUM);
		}
	}

	qh->ntd = 0;

	return retval;
}

/**
 * Frees descriptor list memory associated with the QH.
 * If QH is periodic and the last, frees FrameList memory
 * and disables periodic scheduling.
 *
 * @param hcd The HCD state structure for the DWC OTG controller.
 * @param qh The QH to init.
 */
void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	desc_list_free(qh);

	/*
	 * Channel still assigned due to some reasons.
	 * Seen on Isoc URB dequeue. Channel halted but no subsequent
	 * ChHalted interrupt to release the channel. Afterwards
	 * when it comes here from endpoint disable routine
	 * channel remains assigned.
	 */
	if (qh->channel)
		release_channel_ddma(hcd, qh);

	if ((qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)
	    && !hcd->periodic_channels && hcd->frame_list) {

		per_sched_disable(hcd);
		frame_list_free(hcd);
	}
}

static uint8_t frame_to_desc_idx(dwc_otg_qh_t *qh, uint16_t frame_idx)
{
	if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH)
		/*
		 * Descriptor set(8 descriptors) index
		 * which is 8-aligned.
		 */
		return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8;
	else
		return frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1);
}

/*
 * Determine starting frame for Isochronous transfer.
 * Few frames skipped to prevent race condition with HC.
 */
static uint8_t calc_starting_frame(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh,
				   uint8_t *skip_frames)
{
	uint16_t frame = 0;
	hcd->frame_number = dwc_otg_hcd_get_frame_number(hcd);

	/* sched_frame is always frame number(not uFrame) both in FS and HS !! */

	/*
	 * skip_frames is used to limit activated descriptors number
	 * to avoid the situation when HC services the last activated
	 * descriptor firstly.
	 * Example for FS:
	 * Current frame is 1, scheduled frame is 3. Since HC always fetches the descriptor
	 * corresponding to curr_frame+1, the descriptor corresponding to frame 2
	 * will be fetched. If the number of descriptors is max=64 (or greather) the
	 * list will be fully programmed with Active descriptors and it is possible
	 * case(rare) that the latest descriptor(considering rollback) corresponding
	 * to frame 2 will be serviced first. HS case is more probable because, in fact,
	 * up to 11 uframes(16 in the code) may be skipped.
	 */
	if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) {
		/*
		 * Consider uframe counter also, to start xfer asap.
		 * If half of the frame elapsed skip 2 frames otherwise
		 * just 1 frame.
		 * Starting descriptor index must be 8-aligned, so
		 * if the current frame is near to complete the next one
		 * is skipped as well.
		 */

		if (dwc_micro_frame_num(hcd->frame_number) >= 5) {
			*skip_frames = 2 * 8;
			frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames);
		} else {
			*skip_frames = 1 * 8;
			frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames);
		}

		frame = dwc_full_frame_num(frame);
	} else {
		/*
		 * Two frames are skipped for FS - the current and the next.
		 * But for descriptor programming, 1 frame(descriptor) is enough,
		 * see example above.
		 */
		*skip_frames = 1;
		frame = dwc_frame_num_inc(hcd->frame_number, 2);
	}

	return frame;
}

/*
 * Calculate initial descriptor index for isochronous transfer
 * based on scheduled frame.
 */
static uint8_t recalc_initial_desc_idx(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	uint16_t frame = 0, fr_idx, fr_idx_tmp;
	uint8_t skip_frames = 0;
	/*
	 * With current ISOC processing algorithm the channel is being
	 * released when no more QTDs in the list(qh->ntd == 0).
	 * Thus this function is called only when qh->ntd == 0 and qh->channel == 0.
	 *
	 * So qh->channel != NULL branch is not used and just not removed from the
	 * source file. It is required for another possible approach which is,
	 * do not disable and release the channel when ISOC session completed,
	 * just move QH to inactive schedule until new QTD arrives.
	 * On new QTD, the QH moved back to 'ready' schedule,
	 * starting frame and therefore starting desc_index are recalculated.
	 * In this case channel is released only on ep_disable.
	 */

	/* Calculate starting descriptor index. For INTERRUPT endpoint it is always 0. */
	if (qh->channel) {
		frame = calc_starting_frame(hcd, qh, &skip_frames);
		/*
		 * Calculate initial descriptor index based on FrameList current bitmap
		 * and servicing period.
		 */
		fr_idx_tmp = frame_list_idx(frame);
		fr_idx =
		    (MAX_FRLIST_EN_NUM + frame_list_idx(qh->sched_frame) -
		     fr_idx_tmp)
		    % frame_incr_val(qh);
		fr_idx = (fr_idx + fr_idx_tmp) % MAX_FRLIST_EN_NUM;
	} else {
		qh->sched_frame = calc_starting_frame(hcd, qh, &skip_frames);
		fr_idx = frame_list_idx(qh->sched_frame);
	}

	qh->td_first = qh->td_last = frame_to_desc_idx(qh, fr_idx);

	return skip_frames;
}

#define	ISOC_URB_GIVEBACK_ASAP

#define MAX_ISOC_XFER_SIZE_FS 1023
#define MAX_ISOC_XFER_SIZE_HS 3072
#define DESCNUM_THRESHOLD 4

static void init_isoc_dma_desc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh,
			       uint8_t skip_frames)
{
	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
	dwc_otg_qtd_t *qtd;
	dwc_otg_host_dma_desc_t *dma_desc;
	uint16_t idx, inc, n_desc, ntd_max, max_xfer_size;

	idx = qh->td_last;
	inc = qh->interval;
	n_desc = 0;

	ntd_max = (max_desc_num(qh) + qh->interval - 1) / qh->interval;
	if (skip_frames && !qh->channel)
		ntd_max = ntd_max - skip_frames / qh->interval;

	max_xfer_size =
	    (qh->dev_speed ==
	     DWC_OTG_EP_SPEED_HIGH) ? MAX_ISOC_XFER_SIZE_HS :
	    MAX_ISOC_XFER_SIZE_FS;

	DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) {
		while ((qh->ntd < ntd_max)
		       && (qtd->isoc_frame_index_last <
			   qtd->urb->packet_count)) {

			dma_desc = &qh->desc_list[idx];
			dwc_memset(dma_desc, 0x00, sizeof(dwc_otg_host_dma_desc_t));

			frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last];

			if (frame_desc->length > max_xfer_size)
				qh->n_bytes[idx] = max_xfer_size;
			else
				qh->n_bytes[idx] = frame_desc->length;
			dma_desc->status.b_isoc.n_bytes = qh->n_bytes[idx];
			dma_desc->status.b_isoc.a = 1;
			dma_desc->status.b_isoc.sts = 0;

			dma_desc->buf = qtd->urb->dma + frame_desc->offset;

			qh->ntd++;

			qtd->isoc_frame_index_last++;

#ifdef	ISOC_URB_GIVEBACK_ASAP
			/*
			 * Set IOC for each descriptor corresponding to the
			 * last frame of the URB.
			 */
			if (qtd->isoc_frame_index_last ==
			    qtd->urb->packet_count)
				dma_desc->status.b_isoc.ioc = 1;

#endif
			idx = desclist_idx_inc(idx, inc, qh->dev_speed);
			n_desc++;

		}
		qtd->in_process = 1;
	}

	qh->td_last = idx;

#ifdef	ISOC_URB_GIVEBACK_ASAP
	/* Set IOC for the last descriptor if descriptor list is full */
	if (qh->ntd == ntd_max) {
		idx = desclist_idx_dec(qh->td_last, inc, qh->dev_speed);
		qh->desc_list[idx].status.b_isoc.ioc = 1;
	}
#else
	/*
	 * Set IOC bit only for one descriptor.
	 * Always try to be ahead of HW processing,
	 * i.e. on IOC generation driver activates next descriptors but
	 * core continues to process descriptors followed the one with IOC set.
	 */

	if (n_desc > DESCNUM_THRESHOLD)
		/*
		 * Move IOC "up". Required even if there is only one QTD
		 * in the list, cause QTDs migth continue to be queued,
		 * but during the activation it was only one queued.
		 * Actually more than one QTD might be in the list if this function called
		 * from XferCompletion - QTDs was queued during HW processing of the previous
		 * descriptor chunk.
		 */
		idx = dwc_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), qh->dev_speed);
	else
		/*
		 * Set the IOC for the latest descriptor
		 * if either number of descriptor is not greather than threshold
		 * or no more new descriptors activated.
		 */
		idx = dwc_desclist_idx_dec(qh->td_last, inc, qh->dev_speed);

	qh->desc_list[idx].status.b_isoc.ioc = 1;
#endif
}

static void init_non_isoc_dma_desc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{

	dwc_hc_t *hc;
	dwc_otg_host_dma_desc_t *dma_desc;
	dwc_otg_qtd_t *qtd;
	int num_packets, len, n_desc = 0;

	hc = qh->channel;

	/*
	 * Start with hc->xfer_buff initialized in
	 * assign_and_init_hc(), then if SG transfer consists of multiple URBs,
	 * this pointer re-assigned to the buffer of the currently processed QTD.
	 * For non-SG request there is always one QTD active.
	 */

	DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) {

		if (n_desc) {
			/* SG request - more than 1 QTDs */
			hc->xfer_buff = (uint8_t *)qtd->urb->dma + qtd->urb->actual_length;
			hc->xfer_len = qtd->urb->length - qtd->urb->actual_length;
		}

		qtd->n_desc = 0;

		do {
			dma_desc = &qh->desc_list[n_desc];
			len = hc->xfer_len;

			if (len > MAX_DMA_DESC_SIZE)
				len = MAX_DMA_DESC_SIZE - hc->max_packet + 1;

			if (hc->ep_is_in) {
				if (len > 0)
					num_packets = (len + hc->max_packet - 1) / hc->max_packet;
				else
					/* Need 1 packet for transfer length of 0. */
					num_packets = 1;
				/* Always program an integral # of max packets for IN transfers. */
				len = num_packets * hc->max_packet;
			}

			dma_desc->status.b.n_bytes = len;

			qh->n_bytes[n_desc] = len;

			if ((qh->ep_type == UE_CONTROL)
			    && (qtd->control_phase == DWC_OTG_CONTROL_SETUP))
				dma_desc->status.b.sup = 1;	/* Setup Packet */

			dma_desc->status.b.a = 1;	/* Active descriptor */
			dma_desc->status.b.sts = 0;

			dma_desc->buf =
			    ((unsigned long)hc->xfer_buff & 0xffffffff);

			/*
			 * Last descriptor(or single) of IN transfer
			 * with actual size less than MaxPacket.
			 */
			if (len > hc->xfer_len) {
				hc->xfer_len = 0;
			} else {
				hc->xfer_buff += len;
				hc->xfer_len -= len;
			}

			qtd->n_desc++;
			n_desc++;
		} while ((hc->xfer_len > 0) && (n_desc != MAX_DMA_DESC_NUM_GENERIC));


		qtd->in_process = 1;

		if (qh->ep_type == UE_CONTROL)
			break;

		if (n_desc == MAX_DMA_DESC_NUM_GENERIC)
			break;
	}

	if (n_desc) {
		/* Request Transfer Complete interrupt for the last descriptor */
		qh->desc_list[n_desc - 1].status.b.ioc = 1;
		/* End of List indicator */
		qh->desc_list[n_desc - 1].status.b.eol = 1;

		hc->ntd = n_desc;
	}
}


/**
 * For Control and Bulk endpoints initializes descriptor list
 * and starts the transfer.
 *
 * For Interrupt and Isochronous endpoints initializes descriptor list
 * then updates FrameList, marking appropriate entries as active.
 * In case of Isochronous, the starting descriptor index is calculated based
 * on the scheduled frame, but only on the first transfer descriptor within a session.
 * Then starts the transfer via enabling the channel.
 * For Isochronous endpoint the channel is not halted on XferComplete
 * interrupt so remains assigned to the endpoint(QH) until session is done.
 *
 * @param hcd The HCD state structure for the DWC OTG controller.
 * @param qh The QH to init.
 *
 * @return 0 if successful, negative error code otherwise.
 */
void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	/* Channel is already assigned */
	dwc_hc_t *hc = qh->channel;
	uint8_t skip_frames = 0;

	switch (hc->ep_type) {
	case DWC_OTG_EP_TYPE_CONTROL:
	case DWC_OTG_EP_TYPE_BULK:
		init_non_isoc_dma_desc(hcd, qh);

		dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
		break;
	case DWC_OTG_EP_TYPE_INTR:
		init_non_isoc_dma_desc(hcd, qh);

		update_frame_list(hcd, qh, 1);

		dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
		break;
	case DWC_OTG_EP_TYPE_ISOC:

		if (!qh->ntd)
			skip_frames = recalc_initial_desc_idx(hcd, qh);

		init_isoc_dma_desc(hcd, qh, skip_frames);

		if (!hc->xfer_started) {

			update_frame_list(hcd, qh, 1);

			/*
			 * Always set to max, instead of actual size.
			 * Otherwise ntd will be changed with
			 * channel being enabled. Not recommended.
			 *
			 */
			hc->ntd = max_desc_num(qh);
			/* Enable channel only once for ISOC */
			dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
		}

		break;
	default:

		break;
	}
}

static void complete_isoc_xfer_ddma(dwc_otg_hcd_t *hcd,
				    dwc_hc_t *hc,
				    dwc_otg_hc_regs_t *hc_regs,
				    dwc_otg_halt_status_e halt_status)
{
	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
	dwc_otg_qtd_t *qtd, *qtd_tmp;
	dwc_otg_qh_t *qh;
	dwc_otg_host_dma_desc_t *dma_desc;
	uint16_t idx, remain;
	uint8_t urb_compl;

	qh = hc->qh;
	idx = qh->td_first;

	if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry)
		    qtd->in_process = 0;
		return;
	} else if ((halt_status == DWC_OTG_HC_XFER_AHB_ERR) ||
		   (halt_status == DWC_OTG_HC_XFER_BABBLE_ERR)) {
		/*
		 * Channel is halted in these error cases.
		 * Considered as serious issues.
		 * Complete all URBs marking all frames as failed,
		 * irrespective whether some of the descriptors(frames) succeeded or no.
		 * Pass error code to completion routine as well, to
		 * update urb->status, some of class drivers might use it to stop
		 * queing transfer requests.
		 */
		int err = (halt_status == DWC_OTG_HC_XFER_AHB_ERR)
		    ? (-DWC_E_IO)
		    : (-DWC_E_OVERFLOW);

		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {
			for (idx = 0; idx < qtd->urb->packet_count; idx++) {
				frame_desc = &qtd->urb->iso_descs[idx];
				frame_desc->status = err;
			}
			hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, err);
			dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
		}
		return;
	}

	DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {

		if (!qtd->in_process)
			break;

		urb_compl = 0;

		do {

			dma_desc = &qh->desc_list[idx];

			frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
			remain = hc->ep_is_in ? dma_desc->status.b_isoc.n_bytes : 0;

			if (dma_desc->status.b_isoc.sts == DMA_DESC_STS_PKTERR) {
				/*
				 * XactError or, unable to complete all the transactions
				 * in the scheduled micro-frame/frame,
				 * both indicated by DMA_DESC_STS_PKTERR.
				 */
				qtd->urb->error_count++;
				frame_desc->actual_length = qh->n_bytes[idx] - remain;
				frame_desc->status = -DWC_E_PROTOCOL;
			} else {
				/* Success */

				frame_desc->actual_length = qh->n_bytes[idx] - remain;
				frame_desc->status = 0;
			}

			if (++qtd->isoc_frame_index == qtd->urb->packet_count) {
				/*
				 * urb->status is not used for isoc transfers here.
				 * The individual frame_desc status are used instead.
				 */

				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
				dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);

				/*
				 * This check is necessary because urb_dequeue can be called
				 * from urb complete callback(sound driver example).
				 * All pending URBs are dequeued there, so no need for
				 * further processing.
				 */
				if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE)
					return;

				urb_compl = 1;

			}

			qh->ntd--;

			/* Stop if IOC requested descriptor reached */
			if (dma_desc->status.b_isoc.ioc) {
				idx = desclist_idx_inc(idx, qh->interval, hc->speed);
				goto stop_scan;
			}

			idx = desclist_idx_inc(idx, qh->interval, hc->speed);

			if (urb_compl)
				break;
		} while (idx != qh->td_first);
	}
stop_scan:
	qh->td_first = idx;
}


uint8_t update_non_isoc_urb_state_ddma(dwc_otg_hcd_t *hcd,
				       dwc_hc_t *hc,
				       dwc_otg_qtd_t *qtd,
				       dwc_otg_host_dma_desc_t *dma_desc,
				       dwc_otg_halt_status_e halt_status,
				       uint32_t n_bytes, uint8_t *xfer_done)
{

	uint16_t remain = hc->ep_is_in ? dma_desc->status.b.n_bytes : 0;
	dwc_otg_hcd_urb_t *urb = qtd->urb;

	if (halt_status == DWC_OTG_HC_XFER_AHB_ERR) {
		urb->status = -DWC_E_IO;
		return 1;
	}
	if (dma_desc->status.b.sts == DMA_DESC_STS_PKTERR) {
		switch (halt_status) {
		case DWC_OTG_HC_XFER_STALL:
			urb->status = -DWC_E_PIPE;
			break;
		case DWC_OTG_HC_XFER_BABBLE_ERR:
			urb->status = -DWC_E_OVERFLOW;
			break;
		case DWC_OTG_HC_XFER_XACT_ERR:
			urb->status = -DWC_E_PROTOCOL;
			break;
		default:
			DWC_ERROR("%s: Unhandled descriptor error status (%d)\n", __func__,
				  halt_status);
			break;
		}
		return 1;
	}

	if (dma_desc->status.b.a == 1) {
		DWC_DEBUGPL(DBG_HCDV,
			    "Active descriptor encountered on channel %d\n",
			    hc->hc_num);
		return 0;
	}

	if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL) {
		if (qtd->control_phase == DWC_OTG_CONTROL_DATA) {
			urb->actual_length += n_bytes - remain;
			if (remain || urb->actual_length == urb->length)
				/*
				 * For Control Data stage do not set urb->status=0 to prevent
				 * URB callback. Set it when Status phase done. See below.
				 */
				*xfer_done = 1;

		} else if (qtd->control_phase == DWC_OTG_CONTROL_STATUS) {
			urb->status = 0;
			*xfer_done = 1;
		}
		/* No handling for SETUP stage */
	} else {
		/* BULK and INTR */
		urb->actual_length += n_bytes - remain;
		if (remain || urb->actual_length == urb->length) {
			urb->status = 0;
			*xfer_done = 1;
		}
	}

	return 0;
}

static void complete_non_isoc_xfer_ddma(dwc_otg_hcd_t *hcd,
					dwc_hc_t *hc,
					dwc_otg_hc_regs_t *hc_regs,
					dwc_otg_halt_status_e halt_status)
{
	dwc_otg_hcd_urb_t *urb = NULL;
	dwc_otg_qtd_t *qtd, *qtd_tmp;
	dwc_otg_qh_t *qh;
	dwc_otg_host_dma_desc_t *dma_desc;
	uint32_t n_bytes, n_desc, i;
	uint8_t failed = 0, xfer_done;

	n_desc = 0;

	qh = hc->qh;

	if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {
			qtd->in_process = 0;
		}
		return;
	}

	DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) {

		urb = qtd->urb;

		n_bytes = 0;
		xfer_done = 0;

		for (i = 0; i < qtd->n_desc; i++) {
			dma_desc = &qh->desc_list[n_desc];

			n_bytes = qh->n_bytes[n_desc];

			failed =
			    update_non_isoc_urb_state_ddma(hcd, hc, qtd,
							   dma_desc,
							   halt_status, n_bytes,
							   &xfer_done);

			if (failed
			    || (xfer_done
				&& (urb->status != -DWC_E_IN_PROGRESS))) {

				hcd->fops->complete(hcd, urb->priv, urb,
						    urb->status);
				dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
				qtd = NULL;
				goto stop_scan;
			} else if (qh->ep_type == UE_CONTROL) {
				if (qtd->control_phase == DWC_OTG_CONTROL_SETUP) {
					if (urb->length > 0)
						qtd->control_phase = DWC_OTG_CONTROL_DATA;
					else
						qtd->control_phase = DWC_OTG_CONTROL_STATUS;
					DWC_DEBUGPL(DBG_HCDV, "  Control setup transaction done\n");
				} else if (qtd->control_phase == DWC_OTG_CONTROL_DATA) {
					if (xfer_done) {
						qtd->control_phase = DWC_OTG_CONTROL_STATUS;
						DWC_DEBUGPL(DBG_HCDV, "  Control data transfer done\n");
					} else if (i + 1 == qtd->n_desc) {
						/*
						 * Last descriptor for Control data stage which is
						 * not completed yet.
						 */
						dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
					}
				}
			}

			n_desc++;
		}

	}

stop_scan:

	if (qh->ep_type != UE_CONTROL) {
		/*
		 * Resetting the data toggle for bulk
		 * and interrupt endpoints in case of stall. See handle_hc_stall_intr()
		 */
		if (halt_status == DWC_OTG_HC_XFER_STALL)
			qh->data_toggle = DWC_OTG_HC_PID_DATA0;
		else
			dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
	}

	if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
		hcint_data_t hcint;
		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
		if (hcint.b.nyet) {
			/*
			 * Got a NYET on the last transaction of the transfer. It
			 * means that the endpoint should be in the PING state at the
			 * beginning of the next transfer.
			 */
			qh->ping_state = 1;
			clear_hc_int(hc_regs, nyet);
		}

	}

}

/**
 * This function is called from interrupt handlers.
 * Scans the descriptor list, updates URB's status and
 * calls completion routine for the URB if it's done.
 * Releases the channel to be used by other transfers.
 * In case of Isochronous endpoint the channel is not halted until
 * the end of the session, i.e. QTD list is empty.
 * If periodic channel released the FrameList is updated accordingly.
 *
 * Calls transaction selection routines to activate pending transfers.
 *
 * @param hcd The HCD state structure for the DWC OTG controller.
 * @param hc Host channel, the transfer is completed on.
 * @param hc_regs Host channel registers.
 * @param halt_status Reason the channel is being halted,
 *		      or just XferComplete for isochronous transfer
 */
void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t *hcd,
				    dwc_hc_t *hc,
				    dwc_otg_hc_regs_t *hc_regs,
				    dwc_otg_halt_status_e halt_status)
{
	uint8_t continue_isoc_xfer = 0;
	dwc_otg_transaction_type_e tr_type;
	dwc_otg_qh_t *qh = hc->qh;

	if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {

		complete_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status);

		/* Release the channel if halted or session completed */
		if (halt_status != DWC_OTG_HC_XFER_COMPLETE ||
		    DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {

			/* Halt the channel if session completed */
			if (halt_status == DWC_OTG_HC_XFER_COMPLETE)
				dwc_otg_hc_halt(hcd->core_if, hc, halt_status);

			release_channel_ddma(hcd, qh);
			dwc_otg_hcd_qh_remove(hcd, qh);
		} else {
			/* Keep in assigned schedule to continue transfer */
			DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
					   &qh->qh_list_entry);
			continue_isoc_xfer = 1;

		}
		/** @todo Consider the case when period exceeds FrameList size.
		 *  Frame Rollover interrupt should be used.
		 */
	} else {
		/* Scan descriptor list to complete the URB(s), then release the channel */
		complete_non_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status);

		release_channel_ddma(hcd, qh);
		dwc_otg_hcd_qh_remove(hcd, qh);

		if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list))
			/* Add back to inactive non-periodic schedule on normal completion */
			dwc_otg_hcd_qh_add(hcd, qh);
	}
	tr_type = dwc_otg_hcd_select_transactions(hcd);
	if (tr_type != DWC_OTG_TRANSACTION_NONE || continue_isoc_xfer) {
		if (continue_isoc_xfer) {
			if (tr_type == DWC_OTG_TRANSACTION_NONE)
				tr_type = DWC_OTG_TRANSACTION_PERIODIC;
			else if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC)
				tr_type = DWC_OTG_TRANSACTION_ALL;
		}
		dwc_otg_hcd_queue_transactions(hcd, tr_type);
	}
}

#endif /* DWC_DEVICE_ONLY */
