diff options
Diffstat (limited to 'drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c')
-rw-r--r-- | drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c new file mode 100644 index 000000000000..bd8a20403713 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c @@ -0,0 +1,1134 @@ +/*========================================================================== + * $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" + +extern bool microframe_schedule; + +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(struct device *dev, dwc_otg_qh_t * qh) +{ + int retval = 0; + + qh->desc_list = (dwc_otg_host_dma_desc_t *) + DWC_DMA_ALLOC(dev, 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__); + + } + + 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(struct device *dev, dwc_otg_qh_t * qh) +{ + if (qh->desc_list) { + DWC_DMA_FREE(dev, 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) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + int retval = 0; + + if (hcd->frame_list) + return 0; + + hcd->frame_list = DWC_DMA_ALLOC(dev, 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__); + } + + dwc_memset(hcd->frame_list, 0x00, 4 * MAX_FRLIST_EN_NUM); + + return retval; +} + +static void frame_list_free(dwc_otg_hcd_t * hcd) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + if (!hcd->frame_list) + return; + + DWC_DMA_FREE(dev, 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)) { + if (!microframe_schedule) + hcd->non_periodic_channels--; + else + hcd->available_host_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) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + int retval = 0; + + if (qh->do_split) { + DWC_ERROR("SPLIT Transfers are not supported in Descriptor DMA.\n"); + return -1; + } + + retval = desc_list_alloc(dev, 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) +{ + struct device *dev = dwc_otg_hcd_to_dev(hcd); + + desc_list_free(dev, 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) + && (microframe_schedule || !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); + + if (failed) + 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 */ |